zoukankan      html  css  js  c++  java
  • 超简单易用的 “在 pcduino 开发板上写 Linux 驱动控制板载 LED 的闪烁”

    版权声明:本文为博主原创文章,未经博主同意不得转载。转载联系 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 ");  
    6.  return 0;  
    7. }  
    8.   
    9. static void __exit led_exit(void)  
    10. {  
    11.  printk("led exit ");  
    12. }  
    13.   
    14. module_init( led_init );  
    15. 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. //驱动名  
    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 ");  
    19.   
    20.         //申请设备号  
    21.         err = alloc_chrdev_region(&dev_number,0,DEV_COUNT,DEV_NAME);  
    22.         if(err)  
    23.         {  
    24.                 printk("alloc device number fail ");  
    25.                 return err;  
    26.         }  
    27.         //假设申请成功,打印主设备号  
    28.         printk("major number : %d ",MAJOR(dev_number));  
    29.   
    30.         return 0;  
    31. }  
    32.   
    33. static void __exit led_exit(void)  
    34. {  
    35.         printk("led exit ");  
    36.         //注销申请的设备号  
    37.         unregister_chrdev_region(dev_number,DEV_COUNT);  
    38. }  

    这个程序申请了一个设备号,而且打印出来。相同使用   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. //驱动名  
    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 ");  
    40.   
    41.         //申请设备号  
    42.     err = alloc_chrdev_region(&dev_number,0,DEV_COUNT,DEV_NAME);          
    43.     if(err)  
    44.     {  
    45.         printk("alloc device number fail ");  
    46.         return err;  
    47.     }  
    48.     //假设申请成功,打印主设备号  
    49.     printk("major number : %d ",MAJOR(dev_number));  
    50.   
    51.     //给cdev结构体在内存中分配空间  
    52.     cdevp = cdev_alloc();  
    53.     //假设分配失败  
    54.     if( cdevp==NULL )  
    55.     {  
    56.         printk("cdev alloc failure ");  
    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 ");  
    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 ");  
    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 ",DEV_NAME);  
    93.   
    94.         return 0;  
    95. }  
    96.   
    97. static void __exit led_exit(void)  
    98. {  
    99.         printk("led exit ");  
    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 ");  
    123.     return 0;  
    124. }  
    125.   
    126. //当在应用程序中运行  close  函数时,  
    127. //会调用以下的函数  
    128. int led_release(struct inode* pinode,struct file* pfile)  
    129. {  
    130.     printk("led release ");  
    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");  

    最后一行是指定採用的协议,一定得写上,否则会造成尽管编译通过,可是在安装时,会出现
    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开发板,例如以下图:

    这是一款开源硬件。採用的是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  这个引脚。


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


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

    再往前面查阅就会发现

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

    1. 30  29  28  
    2. 0    0   1  

    就能够了。

         当控制寄存器配置完毕之后,我们就须要向数据寄存器写入数据来控制  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. //驱动名  
    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 ");  
    55.   
    56.         //申请设备号  
    57.     err = alloc_chrdev_region(&dev_number,0,DEV_COUNT,DEV_NAME);          
    58.     if(err)  
    59.     {  
    60.         printk("alloc device number fail ");  
    61.         return err;  
    62.     }  
    63.     //假设申请成功,打印主设备号  
    64.     printk("major number : %d ",MAJOR(dev_number));  
    65.   
    66.     //给cdev结构体在内存中分配空间  
    67.     cdevp = cdev_alloc();  
    68.     //假设分配失败  
    69.     if( cdevp==NULL )  
    70.     {  
    71.         printk("cdev alloc failure ");  
    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 ");  
    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 ");  
    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 ",DEV_NAME);  
    108.   
    109.         return 0;  
    110. }  
    111.   
    112. static void __exit led_exit(void)  
    113. {  
    114.         printk("led exit ");  
    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 ");  
    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 ");  
    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 ");  
    179.   
    180.     //从用户空间读取数据  
    181.     copy_from_user(&val,buf,count);   
    182.     printk("write %d ",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 闪烁了,測试的代码例如以下:

    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 ");  
    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,安装新版的驱动程序后,使用
    1. sudo ./a.out  

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



查看全文
  • 相关阅读:
    《软件方法》读书笔记2
    《代码阅读方法与实践》读书笔记3
    课堂讨论记录
    《代码阅读方法与实践》读书笔记2
    [洛谷] P1948 [USACO08JAN]Telephone Lines S(二分+SPFA)
    2020 CCPC秦皇岛 正式赛题解
    [洛谷] P3146 [USACO16OPEN]248 G (区间DP)
    [进阶指南] 最大子序和
    [训练] 图的K步移动最大收获
    [计蒜客] 受力平衡(组合数学 + 乘法逆元)
  • 原文地址:https://www.cnblogs.com/ldxsuanfa/p/10601964.html
  • Copyright © 2011-2022 走看看