这个开发板已经很久没有动了,这一次辞职后想来想去还是选择去做驱动吧。以前写的那些驱动代码早就不知道哪里去了,当然更不记得了。所以现在从头开始学习,也顺便记录下笔记;
首先看看LED的电路图:
不难看出,LED1==GPB5 LED2==GPB6 LED3==GPB7 LED4==GPB8
然后就去看看IO端口图:
要设置的非常简单,就是把GPBCON设置为输出,GPBDAT设置为0时,则灯亮;设置为1时,则灯灭;
其实上一篇中s3c2440系统自带的管脚宏和函数已经分析过那些引脚宏;
S3C2410_GPB(5):表示GPB5的虚拟地址;
s3c2410_gpio_setpin(S3C2410_GPB(5), 0):表示给GPB5数据位的虚拟地址上设置为0;其实就是GPBDAT中对应位设置为0;
s3c2410_gpio_cfgpin(S3C2410_GPB(5), S3C2410_GPIO_OUTPUT):表示给GPB5控制位的虚拟地址上设置为输出;其实就是GPBCON中对应位设置为输出状态;
下面涉及的结构体和函数都在 include/cdev.h中可以找到
字符设备结构体cdev:
struct cdev { struct kobject kobj; struct module *owner;// 模块属于谁 const struct file_operations *ops;// 对应的操作函数 struct list_head list; dev_t dev;// 设备号 unsigned int count; };
struct cdev *cdev_alloc(void);为字符设备结构体申请内存;
int cdev_add(struct cdev *, dev_t, unsigned);把设备号和字符设备结构体绑定起来,其实这里就相当于把我们自己实现的操作函数和设备号对接起来。上层应用打开的文件是根据设备号,也就是说上层操作的仅仅是和设备号有关系,而要上层应用去使用我们自己设计的函数,就必须把实现的函数和设备号绑定起来。这样他们之间就有关系了。(语言表达有限,自己可以体会下)
void cdev_del(struct cdev *);把字符设备卸载下来;
void cdev_put(struct cdev *p);对字符设备结构体内存的释放;(我程序中使用到这个却是说未定义,但在源代码cdev.h中却能找到这个函数,不知道为什么会这样)
还有些函数等使用到的时候再去分析;
驱动实现代码
#include<linux/init.h> #include<linux/module.h> #include<linux/fs.h> #include<linux/cdev.h> #include<linux/errno.h> #include<mach/regs-gpio.h> #include<linux/gpio.h> #define MAJOR_NUM 250 // 主设备号 #define MINOR_NUM 0 // 次设备号 #define DEV_NAME "yzh_led_test" //驱动名称,加载完后用命令 cat /proc/devices可以查看到这个 #define led_on 1 #define led_off 2 #define all_led_on 3 #define all_led_off 4 static unsigned long led_IO_port[]= { S3C2410_GPB(5), S3C2410_GPB(6), S3C2410_GPB(7), S3C2410_GPB(8), }; static struct cdev *cdevp = NULL; // 字符设备结构体cdev static int devNum; int myOpen(struct inode *inode, struct file *file) { printk(KERN_INFO"in myOpen() will open led!\n"); if (file->f_mode & FMODE_READ) printk(KERN_INFO"file is readonly!\n"); else if (file->f_mode & FMODE_WRITE) printk(KERN_WARNING"file is writeonly!\n"); else printk(KERN_DEBUG"file is read and write!\n"); return 0; } static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { int i; if (0 > arg || 4 < arg) return -EINVAL; printk(KERN_INFO"cmd:%u, ledNum:%ld\n", cmd, arg); switch(cmd){ case led_on: s3c2410_gpio_setpin(led_IO_port[arg], 0); break; //case led_off: case 5: s3c2410_gpio_setpin(led_IO_port[arg], 1); break; case all_led_on: for (i = 0; i < 4; i++) s3c2410_gpio_setpin(led_IO_port[i], 0); break; case all_led_off: for (i = 0; i < 4; i++) s3c2410_gpio_setpin(led_IO_port[i], 1); break; default: return -EINVAL; } return 0; } struct file_operations fops = { .owner = THIS_MODULE, .open = myOpen, .ioctl = led_ioctl, }; static int __init myDrive_init(void) { int ret, i; dev_t allocDevNum; printk(KERN_INFO"led init!\n"); devNum = MKDEV(MAJOR_NUM, MINOR_NUM);//根据主设备号和次设备号得到设备号 ret = register_chrdev_region(devNum, 1, DEV_NAME);// 静态申请设备号,设备号不变 if (ret < 0){ printk(KERN_INFO"register_chrdev_region error!\n"); ret = alloc_chrdev_region(&allocDevNum, 0, 1, DEV_NAME);// 动态申请设备号,设备号是系统分配的 if (ret < 0){ printk(KERN_INFO"alloc_chrdev_region error!\n"); return -EINVAL; } devNum = allocDevNum; } printk(KERN_INFO"devNum:%d, major:%d, minor:%d\n", devNum, MAJOR(devNum), MINOR(devNum)); cdevp = cdev_alloc(); cdev_init(cdevp, &fops); cdevp->owner = THIS_MODULE; ret = cdev_add(cdevp, devNum, 1); if (ret){ printk(KERN_NOTICE"Error %d adding cdev", ret); return -EINVAL; } for (i = 0; i < 4; i++){// 初始化LED GPBCON和熄灭所有灯 s3c2410_gpio_cfgpin(led_IO_port[i], S3C2410_GPIO_OUTPUT); s3c2410_gpio_setpin(led_IO_port[i], 1); } return 0; } static void __exit myDrive_exit(void) { printk(KERN_INFO"myDrive exit!\n"); cdev_del(cdevp); unregister_chrdev_region(devNum, 1); } module_init(myDrive_init); module_exit(myDrive_exit); MODULE_LICENSE("Dual BSD/GPL");上面的代码都通过测试的,我用ioctl(fd, 2, n)总是报错误地址,所以在代码中我用 5 来替换 led_off(其实这是2)。其他的就没问题,当然这是这基本的一个代码,至于流水灯,跑马灯啊,有兴趣就自己去实现,这里就给个点灯的代码思路;
编译驱动有两种方法,可以参考上一篇blog:驱动的两种编译方法;我两种方法都实验过,我们可以先用动态加载,然后测试,最后确定好了,再把该驱动放到内核中;
首先把 .ko文件用nfs共享给开发板,开发板中insmod xx.ko,把驱动加载进内核,用命令 cat /proc/devices可以查看到加载的驱动,用lsmod也可以看到。当然我们要的是主设备号。
接下来就在 /dev/下创建一个设备文件,用来对应你的驱动。 mknod /dev/myLed c 250 0;根据主设备号在/dev下创建个驱动对应的设备文件。我们测试的时候就是操作这个文件的;
下面看看测试代码:
#include<stdio.h> #include<stdlib.h> #include<fcntl.h> #include<errno.h> int main(int argc, char* argv[]) { int flag, ledNum, ret; int fd = open("/dev/charDevice", O_RDWR); if (fd < 0){ printf("open fild!\n"); printf("errno:%d\n", errno); return -1; } sscanf(argv[1], "%d", &flag); sscanf(argv[2], "%d", &ledNum); printf("fd:%d, argv[1]--flag:%d, argv[2]--ledNum:%d\n\n", fd, flag, ledNum); ret = ioctl(fd, flag, ledNum); if (ret < 0){ printf("error in ioctl, errno:%d\n", errno); } return 0; }可能有些多余的语句,那是我开始用来调试驱动的。这里有个errno比较好用,当出错时,可以查看下errno的错误码,方便调试;
当然也可以用write()函数来实现点灯操作,或者其他方法,总之都是大同小异。
原文地址:http://blog.csdn.net/yuzhihui_no1/article/details/45116185