led控制除了在裸机开发中使用寄存器编程以外,当板子运行linux操作系统时,需要在内核进行驱动,应用程序通过设备文件的IO接口,操作内核驱动中的相关函数,通过地址映射,进而控制寄存器的地址的值。
步骤:
1.实现模块加载和卸载入口函数
2.在模块加载函数中实现 a.申请设备号(register_chrdev()) b.常见设备文件(class_create()和device_create()) c.将寄存器物理地址映射为内核虚拟地址(ioremap())、申请中断以及配置寄存器(readl()和writel())
3.实现file_operations结构体中的相关open、read、 write等函数。
驱动代码
#include
#include
#include
#include
#include
#include
#include
//设计一个类型,描述一个设备的信息
struct led_desc{
unsigned int dev_major; //设备号
struct class *cls;
struct device *dev; //创建设备文件
void *reg_virt_base; //表示是寄存器地址到基准值
};
//物理地址
#define GPX2_CON 0x11000C40
#define GPX2_SIZE 8
struct led_desc *led_dev;//表示一个全局的设备对象
static int kernel_val = 555;
// read(fd, buf, size);
ssize_t led_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
printk("-------%s-------\n", __FUNCTION__);
int ret;
ret = copy_to_user(buf, &kernel_val, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
return 0;
}
ssize_t led_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
//printk("-------%s-------\n", __FUNCTION__);
int ret;
int value;
ret = copy_from_user(&value, buf, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
if(value){
writel( readl(led_dev->reg_virt_base + 4) | (1<<7), led_dev->reg_virt_base + 4 );
}else{
writel( readl(led_dev->reg_virt_base + 4) & ~(1<<7), led_dev->reg_virt_base + 4 );
}
return 0;
}
int led_drv_open(struct inode *inode, struct file *filp)
{
printk("-------%s-------\n", __FUNCTION__);
return 0;
}
int led_drv_close(struct inode *inode, struct file *filp)
{
printk("-------%s-------\n", __FUNCTION__);
return 0;
}
const struct file_operations my_fops = {
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
static int __init led_dev_init(void)
{
int ret;
// 0, 实例化全局的设备对象--分配空间
led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
if(led_dev == NULL)
{
printk(KERN_ERR "malloc error\n");
return -ENOMEM;
}
// 1, 一般都是申请设备号资源
// 申请设备号
led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
if(led_dev->dev_major < 0)
{
printk(KERN_ERR "register_chrdev error\n");
ret = -ENODEV;
goto err_0;
}
// 2,创建设备文件
led_dev->cls = class_create(THIS_MODULE, "led_cls");
if(IS_ERR(led_dev->cls))
{
printk(KERN_ERR "class_create error\n");
ret = PTR_ERR(led_dev->cls); //将指针出错的具体原因转换成一个出错码
goto err_1;
}
// /dev/led0
led_dev->dev = device_create(led_dev->cls, NULL,
MKDEV(led_dev->dev_major, 0), NULL, "led%d", 0);
if(IS_ERR(led_dev->dev))
{
printk(KERN_ERR "device_create error\n");
ret = PTR_ERR(led_dev->dev); //将指针出错的具体原因转换成一个出错码
goto err_2;
}
// 3,硬件初始化
// 对地址进行映射
led_dev->reg_virt_base = ioremap(GPX2_CON, GPX2_SIZE);
if(led_dev->reg_virt_base == NULL)
{
printk(KERN_ERR "ioremap error\n");
ret = -ENOMEM;
goto err_3;
}
// gpio的输出功能的配置
u32 value = readl(led_dev->reg_virt_base);
value &= ~(0xf<<28);
value |= (0x1<<28);
writel(value, led_dev->reg_virt_base);
return 0;
err_3:
device_destroy(led_dev->cls, MKDEV(led_dev->dev_major, 0));
err_2:
class_destroy(led_dev->cls);
err_1:
unregister_chrdev(led_dev->dev_major, "led_dev_test");
err_0:
kfree(led_dev);
return ret;
}
static void __exit led_dev_exit(void)
{
//一般都是释放资源
iounmap(led_dev->reg_virt_base);
device_destroy(led_dev->cls, MKDEV(led_dev->dev_major, 0));
class_destroy(led_dev->cls);
unregister_chrdev(led_dev->dev_major, "led_dev_test");
kfree(led_dev);
}
//驱动模块加载和卸载函数申明
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
编写Makefile将驱动程序编译成ko文件
Makefile
ROOTFS_DIR = /opt/4412/rootfs
MODULE_NAME = led_drv
APP_NAME = led_test
CROSS_COMPILE = /home/george/Linux_4412/toolchain/gcc-4.6.4/bin/arm-none-linux-gnueabi-
CC = $(CROSS_COMPILE)gcc
ifeq ($(KERNELRELEASE), )
KERNEL_DIR = /home/george/Linux_4412/kernel/linux-3.14
CUR_DIR = $(shell pwd)
all :
make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
$(CC) $(APP_NAME).c -o $(APP_NAME)
clean :
make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
rm -rf $(APP_NAME)
install:
cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module
else
obj-m += $(MODULE_NAME).o
endif
在板子上的linux系统上输入
insmod led_drv.ko 并且 cat /proc/devices查看设备号
应用程序
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
//调用驱动
int fd;
int value = 0;
fd = open("/dev/led0", O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}
read(fd, &value, 4);
printf("___USER___: value = %d\n", value);
//应用程序去控制灯到亮和灭
while(1)
{
value = 0;
write(fd, &value, 4);
sleep(1);
value = 1;
write(fd, &value, 4);
sleep(1);
}
close(fd);
return 0;
}