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

时间:2024-01-07 10:14:20

版权声明:本文为博主原创文章,未经博主同意不得转载。转载联系 QQ 30952589,加好友请注明来意。

https://blog.csdn.net/sleks/article/details/25158121

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

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

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

因为关于pcduino的资料比較少。所以这篇文章是參考了pcduino爱好者论坛的一篇教程《手把手教你用A10点灯》。而且系统的结合了linux驱动的开发步骤。

读完这篇文章,你不但能够对pcduino开发板的硬件结构有所了解,更重要的是能够对linux的驱动开发步骤有一个系统的认识。我也是一个linux驱动的新手,所以。写的不正确的地方,请大家指正。

1.Linux驱动框架

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

  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. static void __exit led_exit(void)
  9. {
  10. printk("led exit\n");
  11. }
  12. module_init( led_init );
  13. module_exit( led_exit );

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

  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-你的内核版本号/"  就可以,假设你不知道,当前使用的内核版本号。能够输入:

  1. uname -r

来进行查看。M 后面表示你的驱动所在的文件夹。

改好之后保存,注意,这个文件的名字一定得是  "Makefile"  才行,make 和 rm命令前面一定是一个TAB符才行。输入命令:

  1. make

进行编译。完毕之后,使用ls查看。能够看到得到的文件例如以下:

  1. built-in.o  led.c  led.ko  led.mod.c  led.mod.o  led.o  Makefile  modules.order  Module.symvers

这里面的  led.ko  是我们得到的驱动文件。使用:

  1. sudo insmod led.ko

安装驱动。

使用

  1. dmesg

命令,会看到最后一行输出的是   “led init”    ,这句话就是在  led_init  函数中输出的。使用命令:

  1. sudo rmmod led.ko

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

上面写的这个驱动程序是没有什么作用的,在linux中,应用程序是通过设备文件来和驱动程序进行交互的。

所以我们须要在驱动程序中建立设备文件,这个设备文件建立之后。就会存在于   /dev/   文件夹下,应用程序就是通过对这个文件的读写。来向驱动程序发送命令,并通过驱动程序控制硬件的动作。每个驱动程序相应着一个设备文件。要建立一个设备文件。首先必须拥有设备号才行。这个设备号就须要我们向linux系统提出申请。由linux系统为我们分配。设备号有主设备号和从设备号之分,主设备号使用来表示驱动的类型,从设备号表示使用同一个驱动的设备的编号,这里要申请的就是主设备号。

使用  
alloc_chrdev_region   函数来申请一个设备号。设备号的类型为   dev_t   。它是一个 32 位的数,当中 12 位用来表示主设备号,另外 20 位用来表示从设备号。

能够使用   MAJOR   宏和   MINOR   宏来直接获取主设备号和从设备号。

我们第二个版本号的程序例如以下:

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

这个程序申请了一个设备号,而且打印出来。相同使用   dmesg   命令来查看,程序的凝视已经非常具体了。就不再多解释了。

保存之后,编译,安装新的驱动程序。在安装新的驱动程序之前,须要使用命令   sudo  rmmod  led.ko   将之前安装的驱动程序卸载。使用   dmesg   命令查看输出的结果:

  1. [  384.225850] led init
  2. [  384.225854] major number : 250

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

设备号申请完成后,就能够在   /dev/   文件夹下创建设备文件了。须要了解的是设备在内存中,使用结构体   cdev   来表示,而且将我们申请的设备号,以及对文件操作的回调函数,统统的关联起来。最后使用这个结构体。用函数   class_create   和   device_create   来创建一个设备文件。说了一下基本思路,还是先看程序吧:

  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. #define DEV_NAME "led"
  8. //从设备的个数
  9. #define DEV_COUNT 1
  10. //三个回调函数,当在应用程序运行对应的操作时
  11. //驱动程序会调用对应的函数来进行处理
  12. ssize_t led_write(struct file *, const char __user *, size_t, loff_t *);
  13. int led_open(struct inode *, struct file *);
  14. int led_release(struct inode *, struct file *);
  15. //声明设备号
  16. static dev_t dev_number;
  17. //设备在内存中表示的结构体
  18. static struct cdev* cdevp;
  19. //注冊文件操作的回调函数的结构体
  20. static struct file_operations fops =
  21. {
  22. .owner = THIS_MODULE,
  23. //注冊对应的回调函数
  24. .open = led_open,
  25. .release = led_release,
  26. .write = led_write,
  27. };
  28. //用来创建设备文件的class
  29. static struct class* classp;
  30. //初始化
  31. static int __init led_init(void)
  32. {
  33. //错误标记
  34. int err;
  35. printk("led init\n");
  36. //申请设备号
  37. err = alloc_chrdev_region(&dev_number,0,DEV_COUNT,DEV_NAME);
  38. if(err)
  39. {
  40. printk("alloc device number fail\n");
  41. return err;
  42. }
  43. //假设申请成功,打印主设备号
  44. printk("major number : %d\n",MAJOR(dev_number));
  45. //给cdev结构体在内存中分配空间
  46. cdevp = cdev_alloc();
  47. //假设分配失败
  48. if( cdevp==NULL )
  49. {
  50. printk("cdev alloc failure\n");
  51. //注销前面申请的设备号
  52. unregister_chrdev_region(dev_number,DEV_COUNT);
  53. return -1;
  54. }
  55. //将cdev结构体与
  56. //注冊文件操作的回调函数的结构体file_operations关联起来
  57. cdev_init(cdevp,&fops);
  58. //将cdev结构体和申请的设备号关联起来
  59. err = cdev_add(cdevp,dev_number,DEV_COUNT);
  60. if(err)
  61. {
  62. printk("cdev add failure\n");
  63. //释放申请的cdev空间
  64. cdev_del(cdevp);
  65. //注销申请的设备编号
  66. unregister_chrdev_region(dev_number,DEV_COUNT);
  67. return err;
  68. }
  69. //给class分配空间
  70. classp = class_create(THIS_MODULE,DEV_NAME);
  71. if( classp==NULL )
  72. {
  73. printk("class create failure\n");
  74. //释放申请的cdev空间
  75. cdev_del(cdevp);
  76. //注销申请的设备编号
  77. unregister_chrdev_region(dev_number,DEV_COUNT);
  78. return -1;
  79. }
  80. //创建设备文件
  81. device_create(classp,NULL,dev_number,"%s",DEV_NAME);
  82. printk("/dev/%s create success\n",DEV_NAME);
  83. return 0;
  84. }
  85. static void __exit led_exit(void)
  86. {
  87. printk("led exit\n");
  88. //释放分配的class空间
  89. if( classp )
  90. {
  91. device_destroy(classp,dev_number);
  92. class_destroy(classp);
  93. }
  94. //释放分配的cdev空间
  95. if( cdevp )
  96. {
  97. cdev_del(cdevp);
  98. }
  99. //注销申请的设备号
  100. unregister_chrdev_region(dev_number,DEV_COUNT);
  101. }
  102. module_init( led_init );
  103. module_exit( led_exit );
  104. //当在应用程序中运行  open  函数时,
  105. //会调用以下的这个函数
  106. int led_open(struct inode* pinode,struct file* pfile)
  107. {
  108. printk("led open\n");
  109. return 0;
  110. }
  111. //当在应用程序中运行  close  函数时,
  112. //会调用以下的函数
  113. int led_release(struct inode* pinode,struct file* pfile)
  114. {
  115. printk("led release\n");
  116. return 0;
  117. }
  118. //当在应用程序中调用   write   函数时,
  119. //会调用以下的这个函数
  120. ssize_t led_write(struct file* pfile,const char __user* buf,size_t count,loff_t* l)
  121. {
  122. printk("led write");
  123. return 0;
  124. }
  125. //指定採用的协议
  126. MODULE_LICENSE("GPL");

最后一行是指定採用的协议,一定得写上,否则会造成尽管编译通过,可是在安装时,会出现

  1. insmod: error inserting 'led.ko': -1 Unknown symbol in module

这个错误。编译。安装好,之后,我们就能够在   /dev/   文件夹下找到   led    文件,使用命令:

  1. ls -l /dev/led

结果例如以下:

  1. crw------- 1 root root 250, 0 Dec 26 10:52 /dev/led

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

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

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

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

这是一款开源硬件。採用的是cortex-A8的核心。板上能够安装ubuntu,android系统。我们使用的板子已经安装了   ubuntu   系统,通过   HDMI转VGA   线连接屏幕。而且通过usb接口,连接键盘和鼠标。直接在其自带的ubuntu系统上。编写驱动并执行。我们细致的查看板子。会发现板上一共带有 3 个led灯。各自是  RX_LED,TX_LED,ON_LED,分别用来指示接收。发送和电源的状态。这里我们仅仅控制  TX_LED  灯进行闪烁。

查看  pcduino  的硬件原理图。查找 
TX_LED  的连接位置。例如以下图:

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

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGlhb2NhaW5pYW9zaGFuZ3hpYW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" style="border:none; max-width:100%">

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

所以接下来就须要我们去查看  A10 的芯片手冊,来看一看究竟怎么控制  PH15  这个引脚。

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

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

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

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGlhb2NhaW5pYW9zaGFuZ3hpYW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" style="border:none; max-width:100%">

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

再往前面查阅就会发现超简单易用的 “在 pcduino 开发板上写 Linux 驱动控制板载 LED 的闪烁”

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

  1. 30  29  28
  2. 0    0   1

就能够了。

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

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

能够看到,PH的数据寄存器用每一位来表示一个引脚的状态。我们要控制  PH15 引脚。就须要对这个寄存器的第15位进行操作。

所以,接下来就是。開始动手向驱动框架中加入对硬件操作的时候:

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

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

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

使用  gcc testled.c 将该应用程序编译,如果生成a.out,安装新版的驱动程序后,使用

  1. sudo ./a.out

就能够看到  pcduino  上的  led  就開始闪烁了。