超简单易用的 “在 pcduino 开发板上写 Linux 驱动控制板载 LED 的闪烁”


这里转载一篇 linux 下的驱动程序开发的很基础和有用的文章 在pcduino开发板上写驱动控制板载LED的闪烁 ,实际是一个linux的驱动,该篇文章基础且够用;后续找到 android 下的驱动开发相关文章,再补充进来,希望该文作者能再接再励,感谢于先。

这里用 原创 模式,以便能推荐给更多的爱好者,转载是无法推荐的,敬请谅解。

以下仅是对原作者文章的整版复制,由于工作较忙,尚无时间细整理其中的代码,急用的可通过上面的链接跳转至原作者博客。


     由于关于pcduino的资料比较少,所以这篇文章是参考了pcduino爱好者论坛的一篇教程《手把手教你用A10点灯》,并且系统的结合了linux驱动的开发步骤。读完这篇文章,你不但可以对pcduino开发板的硬件结构有所了解,更重要的是可以对linux的驱动开发步骤有一个系统的认识。我也是一个linux驱动的新手,所以,写的不对的地方,请大家指正。

1.Linux驱动框架

     这一部分将会手把手教你创建一个Linux的驱动程序框架,在下一部分,我们只需要将控制pcduino硬件部分的代码填入这个框架就可以了。像所有的应用程序都有一个main函数作为函数的入口一样,linux驱动程序的入口是驱动的初始化函数。这个初始化函数是 module_init 来指定的,同样,与初始化函数对应的驱动程序的退出函数是由  module_exit函数来指定的。下面就让我们动手写第一个版本的驱动程序吧。

[cpp]  view plain copy
  1. #include <linux/module.h>  
  2. #include <linux/init.h>  
  3. static int __init led_init(void)  
  4. {  
  5.  printk("led init\n");  
  6.  return 0;  
  7. }  
  8.   
  9. static void __exit led_exit(void)  
  10. {  
  11.  printk("led exit\n");  
  12. }  
  13.   
  14. module_init( led_init );  
  15. module_exit( led_exit );  


将上面代码保存为 led.c,接下来就要编写Makefile文件对刚刚编写的驱动程序进行编译了。新建Makefile文件,在里面输入:

[cpp]  view plain copy
  1. obj-m := led.o  
  2. all:   
  3.      make -C /usr/src/linux-headers-3.8.0-35-generic/ M=/home/asus/drive/  
  4. clean:   
  5.      rm *.o   
  6.      rm *.ko   
  7.      rm *.order   
  8.      rm *.symvers   
  9.      rm *.mod.c  

注意,Makefile  中的第三行,-C 后面的参数为你当前使用的内核的头文件所在的目录,你只需要修改为  "/usr/src/linux-headers-你的内核版本/"  即可,如果你不知道,当前使用的内核版本,可以输入:

[cpp]  view plain copy
  1. uname -r  

来进行查看。M 后面表示你的驱动所在的目录。改好之后保存,注意,这个文件的名字一定得是  "Makefile"  才行,make 和 rm命令前面一定是一个TAB符才行。输入命令:

[cpp]  view plain copy
  1. make  

进行编译,完成之后,使用ls查看,可以看到得到的文件如下:

[cpp]  view plain copy
  1. built-in.o  led.c  led.ko  led.mod.c  led.mod.o  led.o  Makefile  modules.order  Module.symvers  

这里面的  led.ko  是我们得到的驱动文件,使用:
[cpp]  view plain copy
  1. sudo insmod led.ko  


安装驱动。使用
[cpp]  view plain copy
  1. dmesg  

命令,会看到最后一行输出的是   “led init”    ,这句话就是在  led_init  函数中输出的。使用命令:
[cpp]  view plain copy
  1. sudo rmmod led.ko  

来卸载  led  驱动。再使用: dmesg 命令,会发现,最后一行为  “led exit”。

     上面写的这个驱动程序是没有什么作用的,在linux中,应用程序是通过设备文件来和驱动程序进行交互的。所以我们需要在驱动程序中建立设备文件,这个设备文件建立之后,就会存在于   /dev/   目录下,应用程序就是通过对这个文件的读写,来向驱动程序发送命令,并通过驱动程序控制硬件的动作。每一个驱动程序对应着一个设备文件。要建立一个设备文件,首先必须拥有设备号才行,这个设备号就需要我们向linux系统提出申请,由linux系统为我们分配。设备号有主设备号和从设备号之分,主设备号使用来表示驱动的类型,从设备号表示使用同一个驱动的设备的编号,这里要申请的就是主设备号。使用   alloc_chrdev_region   函数来申请一个设备号。设备号的类型为   dev_t   ,它是一个 32 位的数,其中 12 位用来表示主设备号,另外 20 位用来表示从设备号。可以使用   MAJOR   宏和   MINOR   宏来直接获取主设备号和从设备号。我们第二个版本的程序如下:

[cpp]  view plain copy
  1. #include <linux/module.h>  
  2. #include <linux/init.h>  
  3. #include <linux/fs.h>  
  4.   
  5. //驱动名  
  6. #define DEV_NAME "led"  
  7. //从设备的个数  
  8. #define DEV_COUNT 1  
  9.   
  10. //声明设备号  
  11. static dev_t dev_number;  
  12.   
  13. //初始化  
  14. static int __init led_init(void)  
  15. {  
  16.         //错误标记  
  17.         int err;  
  18.         printk("led init\n");  
  19.   
  20.         //申请设备号  
  21.         err = alloc_chrdev_region(&dev_number,0,DEV_COUNT,DEV_NAME);  
  22.         if(err)  
  23.         {  
  24.                 printk("alloc device number fail\n");  
  25.                 return err;  
  26.         }  
  27.         //如果申请成功,打印主设备号  
  28.         printk("major number : %d\n",MAJOR(dev_number));  
  29.   
  30.         return 0;  
  31. }  
  32.   
  33. static void __exit led_exit(void)  
  34. {  
  35.         printk("led exit\n");  
  36.         //注销申请的设备号  
  37.         unregister_chrdev_region(dev_number,DEV_COUNT);  
  38. }  

这个程序申请了一个设备号,并且打印出来,同样使用   dmesg   命令来查看,程序的注释已经很详细了,就不再多解释了。 保存之后,编译,安装新的驱动程序。在安装新的驱动程序之前,需要使用命令   sudo  rmmod  led.ko   将之前安装的驱动程序卸载,使用   dmesg   命令查看输出的结果:
[cpp]  view plain copy
  1. [  384.225850] led init  
  2. [  384.225854] major number : 250  


还可以使用命令   cat  /proc/devices | grep  ‘led’  查看获得的设备号。

     设备号申请完毕后,就可以在   /dev/   目录下创建设备文件了。需要了解的是设备在内存中,使用结构体   cdev   来表示,并且将我们申请的设备号,以及对文件操作的回调函数,统统的关联起来。最后使用这个结构体,用函数   class_create   和   device_create   来创建一个设备文件。说了一下基本思路,还是先看程序吧:
[cpp]  view plain copy
  1. #include <linux/module.h>  
  2. #include <linux/init.h>  
  3. #include <linux/fs.h>  
  4. #include <linux/cdev.h>  
  5. #include <linux/device.h>  
  6.   
  7. //驱动名  
  8. #define DEV_NAME "led"  
  9. //从设备的个数  
  10. #define DEV_COUNT 1  
  11.   
  12. //三个回调函数,当在应用程序执行相应的操作时  
  13. //驱动程序会调用相应的函数来进行处理  
  14. ssize_t led_write(struct file *, const char __user *, size_t, loff_t *);  
  15. int led_open(struct inode *, struct file *);  
  16. int led_release(struct inode *, struct file *);  
  17.   
  18. //声明设备号  
  19. static dev_t dev_number;  
  20. //设备在内存中表示的结构体  
  21. static struct cdev* cdevp;  
  22. //注册文件操作的回调函数的结构体  
  23. static struct file_operations fops =   
  24. {  
  25.     .owner = THIS_MODULE,  
  26.     //注册相应的回调函数  
  27.     .open = led_open,  
  28.     .release = led_release,  
  29.     .write = led_write,  
  30. };  
  31. //用来创建设备文件的class  
  32. static struct class* classp;  
  33.   
  34. //初始化  
  35. static int __init led_init(void)  
  36. {  
  37.     //错误标记  
  38.     int err;  
  39.     printk("led init\n");  
  40.   
  41.         //申请设备号  
  42.     err = alloc_chrdev_region(&dev_number,0,DEV_COUNT,DEV_NAME);          
  43.     if(err)  
  44.     {  
  45.         printk("alloc device number fail\n");  
  46.         return err;  
  47.     }  
  48.     //如果申请成功,打印主设备号  
  49.     printk("major number : %d\n",MAJOR(dev_number));  
  50.   
  51.     //给cdev结构体在内存中分配空间  
  52.     cdevp = cdev_alloc();  
  53.     //如果分配失败  
  54.     if( cdevp==NULL )  
  55.     {  
  56.         printk("cdev alloc failure\n");  
  57.         //注销前面申请的设备号  
  58.         unregister_chrdev_region(dev_number,DEV_COUNT);  
  59.         return -1;  
  60.     }  
  61.   
  62.     //将cdev结构体与  
  63.     //注册文件操作的回调函数的结构体file_operations关联起来  
  64.     cdev_init(cdevp,&fops);  
  65.       
  66.     //将cdev结构体和申请的设备号关联起来  
  67.     err = cdev_add(cdevp,dev_number,DEV_COUNT);  
  68.     if(err)  
  69.     {  
  70.         printk("cdev add failure\n");  
  71.         //释放申请的cdev空间  
  72.         cdev_del(cdevp);  
  73.         //注销申请的设备编号  
  74.         unregister_chrdev_region(dev_number,DEV_COUNT);  
  75.         return err;  
  76.     }  
  77.   
  78.     //给class分配空间  
  79.     classp = class_create(THIS_MODULE,DEV_NAME);  
  80.     if( classp==NULL )  
  81.     {  
  82.         printk("class create failure\n");  
  83.         //释放申请的cdev空间  
  84.         cdev_del(cdevp);  
  85.         //注销申请的设备编号  
  86.         unregister_chrdev_region(dev_number,DEV_COUNT);  
  87.         return -1;  
  88.     }  
  89.   
  90.     //创建设备文件  
  91.     device_create(classp,NULL,dev_number,"%s",DEV_NAME);  
  92.     printk("/dev/%s create success\n",DEV_NAME);  
  93.   
  94.         return 0;  
  95. }  
  96.   
  97. static void __exit led_exit(void)  
  98. {  
  99.         printk("led exit\n");  
  100.     //释放分配的class空间  
  101.     if( classp )  
  102.     {  
  103.         device_destroy(classp,dev_number);  
  104.         class_destroy(classp);  
  105.     }  
  106.     //释放分配的cdev空间  
  107.     if( cdevp )  
  108.     {  
  109.         cdev_del(cdevp);  
  110.     }  
  111.     //注销申请的设备号  
  112.     unregister_chrdev_region(dev_number,DEV_COUNT);  
  113. }  
  114.   
  115. module_init( led_init );  
  116. module_exit( led_exit );  
  117.   
  118. //当在应用程序中执行  open  函数时,  
  119. //会调用下面的这个函数  
  120. int led_open(struct inode* pinode,struct file* pfile)  
  121. {  
  122.     printk("led open\n");  
  123.     return 0;  
  124. }  
  125.   
  126. //当在应用程序中执行  close  函数时,  
  127. //会调用下面的函数  
  128. int led_release(struct inode* pinode,struct file* pfile)  
  129. {  
  130.     printk("led release\n");  
  131.     return 0;  
  132. }  
  133.   
  134. //当在应用程序中调用   write   函数时,  
  135. //会调用下面的这个函数  
  136. ssize_t led_write(struct file* pfile,const char __user* buf,size_t count,loff_t* l)  
  137. {  
  138.     printk("led write");  
  139.     return 0;  
  140. }  
  141.   
  142. //指定采用的协议  
  143. MODULE_LICENSE("GPL");  

最后一行是指定采用的协议,一定得写上,否则会造成虽然编译通过,但是在安装时,会出现
[cpp]  view plain copy
  1. insmod: error inserting 'led.ko': -1 Unknown symbol in module  
这个错误。编译,安装好,之后,我们就可以在   /dev/   目录下找到   led    文件,使用命令:
[cpp]  view plain copy
  1. ls -l /dev/led  

结果如下:
[cpp]  view plain copy
  1. crw------- 1 root root 250, 0 Dec 26 10:52 /dev/led  


     至此我们的linux设备驱动框架,已经完全建立起来了。接下来要做的工作,就是对   pcduino   开发板进行编程了。

2.对   pcduino   进行编程,控制  LED  闪烁

     所使用的开发板是pcduino开发板,如下图:

超简单易用的 “在 pcduino 开发板上写 Linux 驱动控制板载 LED 的闪烁”_第1张图片

这是一款开源硬件,采用的是cortex-A8的核心,板上可以安装ubuntu,android系统,我们使用的板子已经安装了   ubuntu   系统,通过   HDMI转VGA   线连接屏幕,并且通过usb接口,连接键盘和鼠标,直接在其自带的ubuntu系统上,编写驱动并运行。我们仔细的查看板子,会发现板上一共带有 3 个led灯,分别是  RX_LED,TX_LED,ON_LED,分别用来指示接收,发送和电源的状态。这里我们只控制  TX_LED  灯进行闪烁。查看  pcduino  的硬件原理图,查找  TX_LED  的连接位置,如下图:

会看到第三行   TX_LED   连接到  CPU  的PH15引脚,并且  L  即低电平时为激活状态,H 高电平时,为熄灭状态。得到这个信息说明,我们只需要控制  CPU  的引脚  PH15  的状态,就可以控制  TX_LED  的状态了。

     所以接下来就需要我们去查看  A10 的芯片手册,来看一看到底怎么控制  PH15  这个引脚。

超简单易用的 “在 pcduino 开发板上写 Linux 驱动控制板载 LED 的闪烁”_第2张图片

可以看到  A10  芯片的引脚有很多,而我们只关注  PH,因为我们要控制的就是  PH15  这个引脚。这里需要的一个概念就是,对一个引脚的控制至少需要有两个寄存器,一个是控制寄存器,一个是数据寄存器。控制寄存器用来控制引脚的工作模式,比如输出或者输入;数据寄存器用来向引脚输出数据或者从引脚读入数据。所以我们要先查看一下  PH15  的配置寄存器,如下图:

超简单易用的 “在 pcduino 开发板上写 Linux 驱动控制板载 LED 的闪烁”_第3张图片

我们发现   PH15   控制寄存器一共有3位28-30,共有 8 种工作模式,由于要控制 led 的状态,我们将它设置为输出模式,所以  PH15  控制寄存器的内容应该为 001。那么这个寄存器在哪个位置呢,在表上有   Offset:0x100   我们知道,PH寄存器的偏移地址是  0x100,但是基地址是多少呢。再往前面查阅就会发现

所以基地址就是  0x01C20800。基地址和偏移地址都有了,我们就可以定位  PH_CFG1  寄存器的地址就是(0x01C20800+0x100),我们只需要将这个寄存器的第28-30位置为:

[cpp]  view plain copy
  1. 30  29  28  
  2. 0    0   1  

就可以了。

     当控制寄存器配置完成之后,我们就需要向数据寄存器写入数据来控制  led  的闪烁。我们同样查看芯片手册:

超简单易用的 “在 pcduino 开发板上写 Linux 驱动控制板载 LED 的闪烁”_第4张图片

可以看到,PH的数据寄存器用每一位来表示一个引脚的状态。我们要控制  PH15 引脚,就需要对这个寄存器的第15位进行操作。所以,接下来就是,开始动手向驱动框架中添加对硬件操作的时候:

[cpp]  view plain copy
  1. #include <linux/module.h>  
  2. #include <linux/init.h>  
  3. #include <linux/fs.h>  
  4. #include <linux/cdev.h>  
  5. #include <linux/device.h>  
  6. #include <asm/io.h>  
  7. #include <asm/uaccess.h>  
  8.   
  9. //驱动名  
  10. #define DEV_NAME "led"  
  11. //从设备的个数  
  12. #define DEV_COUNT 1  
  13.   
  14. //定义与硬件相关的宏  
  15. //基地址  
  16. #define BASE_ADDRESS 0x01C20800  
  17. //PH_CFG1寄存器的地址  
  18. #define PH_CFG1     (BASE_ADDRESS+0x100)  
  19. //PH_DAT寄存器的地址  
  20. #define PH_DAT      (BASE_ADDRESS+0x10C)  
  21.   
  22. //三个回调函数,当在应用程序执行相应的操作时  
  23. //驱动程序会调用相应的函数来进行处理  
  24. ssize_t led_write(struct file *, const char __user *, size_t, loff_t *);  
  25. int led_open(struct inode *, struct file *);  
  26. int led_release(struct inode *, struct file *);  
  27.   
  28. //声明设备号  
  29. static dev_t dev_number;  
  30. //设备在内存中表示的结构体  
  31. static struct cdev* cdevp;  
  32. //注册文件操作的回调函数的结构体  
  33. static struct file_operations fops =   
  34. {  
  35.     .owner = THIS_MODULE,  
  36.     //注册相应的回调函数  
  37.     .open = led_open,  
  38.     .release = led_release,  
  39.     .write = led_write,  
  40. };  
  41. //用来创建设备文件的class  
  42. static struct class* classp;  
  43.   
  44. //声明用来表示PH_CFG1内存地址的变量  
  45. volatile static unsigned long* __ph_cfg1;  
  46. //用来表示PH_DAT内存地址的变量  
  47. volatile static unsigned long* __ph_dat;  
  48.   
  49. //初始化  
  50. static int __init led_init(void)  
  51. {  
  52.     //错误标记  
  53.     int err;  
  54.     printk("led init\n");  
  55.   
  56.         //申请设备号  
  57.     err = alloc_chrdev_region(&dev_number,0,DEV_COUNT,DEV_NAME);          
  58.     if(err)  
  59.     {  
  60.         printk("alloc device number fail\n");  
  61.         return err;  
  62.     }  
  63.     //如果申请成功,打印主设备号  
  64.     printk("major number : %d\n",MAJOR(dev_number));  
  65.   
  66.     //给cdev结构体在内存中分配空间  
  67.     cdevp = cdev_alloc();  
  68.     //如果分配失败  
  69.     if( cdevp==NULL )  
  70.     {  
  71.         printk("cdev alloc failure\n");  
  72.         //注销前面申请的设备号  
  73.         unregister_chrdev_region(dev_number,DEV_COUNT);  
  74.         return -1;  
  75.     }  
  76.   
  77.     //将cdev结构体与  
  78.     //注册文件操作的回调函数的结构体file_operations关联起来  
  79.     cdev_init(cdevp,&fops);  
  80.       
  81.     //将cdev结构体和申请的设备号关联起来  
  82.     err = cdev_add(cdevp,dev_number,DEV_COUNT);  
  83.     if(err)  
  84.     {  
  85.         printk("cdev add failure\n");  
  86.         //释放申请的cdev空间  
  87.         cdev_del(cdevp);  
  88.         //注销申请的设备编号  
  89.         unregister_chrdev_region(dev_number,DEV_COUNT);  
  90.         return err;  
  91.     }  
  92.   
  93.     //给class分配空间  
  94.     classp = class_create(THIS_MODULE,DEV_NAME);  
  95.     if( classp==NULL )  
  96.     {  
  97.         printk("class create failure\n");  
  98.         //释放申请的cdev空间  
  99.         cdev_del(cdevp);  
  100.         //注销申请的设备编号  
  101.         unregister_chrdev_region(dev_number,DEV_COUNT);  
  102.         return -1;  
  103.     }  
  104.   
  105.     //创建设备文件  
  106.     device_create(classp,NULL,dev_number,"%s",DEV_NAME);  
  107.     printk("/dev/%s create success\n",DEV_NAME);  
  108.   
  109.         return 0;  
  110. }  
  111.   
  112. static void __exit led_exit(void)  
  113. {  
  114.         printk("led exit\n");  
  115.     //释放分配的class空间  
  116.     if( classp )  
  117.     {  
  118.         device_destroy(classp,dev_number);  
  119.         class_destroy(classp);  
  120.     }  
  121.     //释放分配的cdev空间  
  122.     if( cdevp )  
  123.     {  
  124.         cdev_del(cdevp);  
  125.     }  
  126.     //注销申请的设备号  
  127.     unregister_chrdev_region(dev_number,DEV_COUNT);  
  128. }  
  129.   
  130. module_init( led_init );  
  131. module_exit( led_exit );  
  132.   
  133. //当在应用程序中执行  open  函数时,  
  134. //会调用下面的这个函数  
  135. int led_open(struct inode* pinode,struct file* pfile)  
  136. {  
  137.     //临时变量  
  138.     unsigned long tmp;   
  139.     printk("led open\n");  
  140.       
  141.     //将PH15管脚设置为输出状态  
  142.     //将PH_CFG1这个硬件寄存器的地址,映射到linux内存,并获取映射后的地址  
  143.     //通过对这个地址的操作,就可以控制PH_CFG1  
  144.     __ph_cfg1 = (volatile unsigned long*)ioremap(PH_CFG1,4);  
  145.     //将设置PH15寄存器  
  146.     tmp = *__ph_cfg1;  
  147.     tmp &= ~(0xf<<28);  
  148.     tmp |= (1<<28);  
  149.     *__ph_cfg1 = tmp;  
  150.   
  151.     //将灯初始化为熄灭的状态  
  152.     __ph_dat = (volatile unsigned long*)ioremap(PH_DAT,4);  
  153.     tmp = *__ph_dat;  
  154.     tmp |= (1<<15);  
  155.     *__ph_dat = tmp;      
  156.   
  157.     return 0;  
  158. }  
  159.   
  160. //当在应用程序中执行  close  函数时,  
  161. //会调用下面的函数  
  162. int led_release(struct inode* pinode,struct file* pfile)  
  163. {  
  164.     printk("led release\n");  
  165.     //注销分配的内存地址  
  166.     iounmap(__ph_dat);  
  167.     iounmap(__ph_cfg1);  
  168.   
  169.     return 0;  
  170. }  
  171.   
  172. //当在应用程序中调用   write   函数时,  
  173. //会调用下面的这个函数  
  174. ssize_t led_write(struct file* pfile,const char __user* buf,size_t count,loff_t* l)  
  175. {  
  176.     int val;  
  177.     volatile unsigned long tmp;  
  178.     printk("led write\n");  
  179.   
  180.     //从用户空间读取数据  
  181.     copy_from_user(&val,buf,count);   
  182.     printk("write %d\n",val);  
  183.       
  184.     //从应用程序读取命令  
  185.     //来控制led灯  
  186.     tmp = *__ph_dat;  
  187.     if( val==1 )  
  188.     {  
  189.         //灯亮  
  190.         tmp &= ~(1<<15);   
  191.     }  
  192.     else  
  193.     {  
  194.         //灯灭  
  195.         tmp |= (1<<15);  
  196.     }  
  197.     *__ph_dat = tmp;  
  198.     return 0;  
  199. }  
  200.   
  201. MODULE_LICENSE("GPL");  

     上面的是完整的控制pcduino上led闪烁的驱动程序,写完这个驱动程序之后,再写一个下面的测试程序就可以使 led 闪烁了,测试的代码如下:

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <unistd.h>  
  3. #include <fcntl.h>  
  4.   
  5. int main(void)  
  6. {  
  7.     int fd;  
  8.     int val = 1;  
  9.       
  10.     //打开驱动对应的设备文件  
  11.     fd = open("/dev/led",O_RDWR);  
  12.     if( fd<0 )  
  13.     {  
  14.         printf("open /dev/led error\n");  
  15.         return -1;  
  16.     }  
  17.   
  18.     while(1)  
  19.     {  
  20.         //写入高电平  
  21.         write(fd,&val,sizeof(int));  
  22.         //睡眠一秒  
  23.         sleep(1);  
  24.         //将电平反转  
  25.         val = 0;  
  26.         //写入低电平  
  27.         write(fd,&val,sizeof(int));  
  28.         //睡眠一秒  
  29.         sleep(1);  
  30.         val = 1;  
  31.     }  
  32.   
  33.     close(fd);  
  34.     return 0;  
  35. }  

使用  gcc testled.c 将该应用程序编译,假设生成a.out,安装新版的驱动程序后,使用
[cpp]  view plain copy
  1. sudo ./a.out  

就可以看到  pcduino  上的  led  就开始闪烁了。


你可能感兴趣的:(超简单易用的 “在 pcduino 开发板上写 Linux 驱动控制板载 LED 的闪烁”)