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  就開始闪烁了。



查看全文
  • 相关阅读:
    HAproxy 1.5 dev14 发布
    IBM/DW 使用 Java 测试网络连通性的几种方法
    Skype 4.1 Linux 发布,支持微软帐号登录
    Dorado 7.1.20 发布,Ajax的Web开发平台
    Aspose.Slides for Java 3.0 发布
    开发版本 Wine 1.5.18 发布
    BitNami Rubystack 开始支持 Ruby 2.0
    XWiki 4.3 正式版发布
    Silverlight实例教程 Out of Browser的Debug和Notifications窗口
    Silverlight实例教程 Out of Browser与Office的互操作
  • 原文地址:https://www.cnblogs.com/ldxsuanfa/p/10601964.html
  • Copyright © 2011-2022 走看看