本文基于华清4412开发板,讲解如何从零开始编写led驱动程序和测试程序。首先介绍一下该4412开发板的led硬件原理图。
从原理图上我们可以看出,让led点亮的条件是往对应端口送高电平,熄灭的条件是送低电平。
从上面这幅图中可以看到对应引脚的寄存器配置,这里我们选择对LED2进行闪烁实验。我们需要把GPX2CON【7】配置寄存器设置为输出模式,也就是设置为0x1。我们还需要通过设置数据寄存器来控制LED的亮和灭,从下面这幅图可以看出我们往GPX2DAT【7】送1就能点亮LED2,送0就能使LED2熄灭。我们可以从这两幅图上得到对应寄存器的地址。
下面我们开始写代码:
#include
#include
#include
#include
#include
#include
#include
#define GPX2CON 0x11000c40
//封装设备信息,面向对象编程思想
struct led_desc{
struct class *class;
struct device *device;
unsigned long *reg_virt_addr;//声明GPX2CON的虚拟地址
};
//定义设备
struct led_desc *led_dev;
int led_open (struct inode *inode, struct file *filp)
{
printk("---------%s----------\n",__FUNCTION__);
return 0;
}
ssize_t led_read (struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
printk("---------%s----------\n",__FUNCTION__);
return 0;
}
ssize_t led_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
int value;
int res;
printk("---------%s----------\n",__FUNCTION__);
res = copy_from_user(&value,buf, count);
if(res > 0){
printk("copy to user error\n");
return -EFAULT;
}
printk("value from user is %d\n",value);
//如果用户空间传过来的数据非0,则点亮LED2
if(value){
writel(readl(led_dev->reg_virt_addr+1) | (0x1<<7), led_dev->reg_virt_addr+1);
}else{
writel(readl(led_dev->reg_virt_addr+1) & ~(0x1<<7), led_dev->reg_virt_addr+1);
}
return 0;
}
int led_close (struct inode *inode, struct file *filp)
{
printk("---------%s----------\n",__FUNCTION__);
return 0;
}
const struct file_operations myfops = {
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_close,
};
int ret;
static int __init led_drv_init(void)
{
int err;
//u32 val;
printk("---------%s----------\n",__FUNCTION__);
//给设备对象申请空间
led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
if(led_dev == NULL){
printk(KERN_ERR "kmalloc error\n");
return -ENOMEM;
}
//动态申请设备号
ret = register_chrdev(ret, "led_drv_test", &myfops);
if(ret < 0){
printk(KERN_ERR "register chr_dev error\n");
err = -ENODEV;
goto err_0;
}
//申请设备节点
led_dev->class = class_create(THIS_MODULE, "led_drv_class");
if(IS_ERR(led_dev->class)){
printk(KERN_ERR "class create error\n");
err = PTR_ERR(led_dev->class);
goto err_1;
}
led_dev->device = device_create(led_dev->class,NULL, MKDEV(ret,0), NULL, "led%d",2);
if(IS_ERR(led_dev->device)){
printk(KERN_ERR "device create error\n");
err = PTR_ERR(led_dev->device);
goto err_2;
}
//地址映射
led_dev->reg_virt_addr = ioremap(GPX2CON, 8);
if(led_dev->reg_virt_addr == NULL){
printk(KERN_ERR "ioremap error\n");
err = -ENOMEM;
goto err_3;
}
//配置GPXC2CON寄存器为输出模式
*led_dev->reg_virt_addr &= ~(0xf<<28);
*led_dev->reg_virt_addr |= (0x1<<28);
/*
val = readl(led_dev->reg_virt_addr);
val &= ~(0xf<<28);]
val |= (0x1<<28);
writel(val,led_dev->reg_virt_addr);
*/
return 0;
//错误处理
err_3:
device_destroy(led_dev->class, MKDEV(ret, 0));
err_2:
class_destroy(led_dev->class);
err_1:
unregister_chrdev(ret, "led_drv_test");
err_0:
kfree(led_dev);
return err;
}
static void __exit led_drv_exit(void)
{
printk("---------%s----------\n",__FUNCTION__);
iounmap(led_dev->reg_virt_addr);
device_destroy(led_dev->class, MKDEV(ret, 0));
class_destroy(led_dev->class);
unregister_chrdev(ret, "led_drv_test");
kfree(led_dev);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
以上就是led的驱动编写框架。分为这几个部分:
1、封装设备信息。这里把led的有关信息封装成一个结构体,然后用此结构体定义一个设备(led)。
2、入口函数里先给上一步定义的设备(led)申请空间,然后是动态申请设备号,当然也可以静态申请设备号,可以自定义设备号,只要不和已有的设备号重复就行。接下去是申请设备节点,也可以采用手动申请的方式,这里不多作介绍。最后是初始化映射地址和配置寄存器。
3、初始化完毕,我们就可以在空户空间和内核空间就是数据传递,这里我们采用从用户空间传递过来的数据来控制led2的亮和灭。
大致的思路就是这样。整个框架搭好了就好办了。下面我们编写测试程序,先上代码。
#include
#include
#include
#include
int main(int argc,char **argv)
{
int fd;
int value = 0;
fd = open("/dev/led2",O_RDWR);
if(fd < 0){
perror("open");
exit(1);
}
while(1){
value = 0;
write(fd,&value,4);
usleep(50000);
value = 1;
write(fd,&value,4);
usleep(50000);
}
close(fd);
return 0;
}
好了,下面编写makefile
#当前目录
CUR_DIR = `pwd`
#根文件系统所在目录
ROOTFS_DIR = /source/rootfs
#内核所在目录
KERNEL_DIR = /home/linux/linux-3.14.1
#应用程序名称
APP_NAME = led_test
#要编译的模块名称
MODULE_NAME = led_drv
#交叉编译工具链
CROSS_COMPILE = /home/linux/toolchain/gcc-4.6.4/bin/arm-none-linux-gnueabi-
CC = $(CROSS_COMPILE)gcc
ifeq ($(KERNELRELEASE),)
all:
#编译模块
make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
#编译上层APP应用程序
$(CC) $(APP_NAME).c -o $(APP_NAME)
clean:
#清除模块
make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
#清除app
rm -rf $(APP_NAME)
install:
#更新驱动模块和测试程序
cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module
else
obj-m += $(MODULE_NAME).o
endif
启动开发板,查看设备号。
加载led模块再看设备号。
设备节点也有了/dev/led2
程序开始执行,输出对应信息,开发板led2每隔50ms闪烁一次。
这里的测试程序只是先打开设备节点文件,所以我们可以看到先执行驱动程序的led_open函数,然后我们往内核传数据,相应地就执行驱动程序的led_write函数,由于一直在while循环中,所以并没有执行驱动程序里的led_read和led_close函数。
开发板上led2闪烁现场就不发了,图片看不出效果。好了,先写到这里。。。