12074|12

3

帖子

0

TA的资源

一粒金砂(初级)

楼主
 

Linux下bb-black的GPIO驱动程序 [复制链接]

Linux下bb-black的GPIO驱动程序

作者:Mlo_lv
QQ:102578546
(如需转载,请注明出处)

        最近几天拿到了一块bb-black,参照这《LINUX设备驱动程序》想着用来练习Linux下的驱动程序编写。我也是新手,本文仅贡交流之用,有写的不好的地方,还望各位高手指正,不吝赐教!

本文参照了:《Linux下AM335X的GPIO控制》:https://bbs.eeworld.com.cn/thread-327156-1-1.html
                        《Linux下TI omap芯片 MUX 配置分析(以AM335X芯片为例)》:http://blog.chinaunix.net/uid-20543672-id-3067021.html
                        《BeagleBone Black Linux驱动程序开发入门(1): LED驱动程序》:http://www.eeboard.com/bbs/forum ... id=15284&highlight=
还有其他的一些博文,对我的帮助也很大,在这就不在这一一列举了!

/*-------------------------------------------------------------------------------------------------------------------------------------------------------*/

        我相信各位开发环境搭建已经不成问题了,如果开发环境没有搭建好的话,可以参照:https://bbs.eeworld.com.cn/thread-375502-1-1.html
        说实话,我并没有采用TI提供的SDK包中的交叉编译器,而是采用了embest的arm-none-linux-gnueabi-4.5,其实我尝试了很多编译器,最后还是这个好用,网上可以下到,如果各位功力够深的话,可以自己下载linaro提供的GCC源码制作的交叉编译器!我就偷懒了!还有关于链工具命名规则,可以参照:http://blog.sina.com.cn/s/blog_76550fd70101bd43.html  没事可以了解一下!


说的太多了,进入主题吧:



/*-------------------------------------------------------------------------------------------------------------------------------------------------------*/

内核空间
驱动程序gpioCtl.c:
  1. #include
  2. #include
  3. #include
  4. #include
  5. #include
  6. #include
  7. #include
  8. #include
  9. #include
  10. #include
  11. #include
  12. /*-----------------------------------------------------*/
  13. #define DEVICE_NAME        "gpioCtl"
  14. #define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))
  15. /*------------------------------------------------------*/

  16. struct gpio_qset;

  17. /*设备编号存储结构体*/
  18. struct dev_num
  19. {
  20.         dev_t devNum;
  21.         unsigned int major;
  22.         unsigned int minor;
  23.         unsigned int minor_first;
  24.         unsigned int count;
  25. };
  26. struct dev_num gpio_dev_num;

  27. /*设备描述结构体*/
  28. struct gpio_dev
  29. {
  30.         struct cdev cdev;
  31.         struct gpio_qset* dptr;        //设备数据存储链表第一项
  32.         unsigned long size;        //链表长度(随着申请的GPIO端口数增长)
  33. };
  34. struct gpio_dev *gpio_devp;

  35. /*设备数据存储结构体(采用链式存储,不理解的可以看《数据结构》)*/
  36. struct gpio_qset
  37. {
  38.         unsigned long port;        //端口号 #define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))
  39.         unsigned int ddr;        //方向(输入(0)或输出(1))
  40.         char value;                //高(1)低(0)电平
  41.         unsigned long num;        //当前编号(按照申请顺序编号)
  42.         char label[10];                //申请的GPIO使用名称
  43.         struct gpio_qset* next;        //指向链表下一项的指针
  44. };

  45. /**
  46. * 功能:初始化gpio_dev
  47. * *inode:
  48. * *filp:
  49. * 描述:用户空间调用open时运行
  50. *         int (*open) (struct inode *, struct file *);
  51. * 返回值:0
  52. */
  53. static int gpio_open(struct inode *inode, struct file *filp)
  54. {
  55.         struct gpio_dev *dev;
  56.         
  57.         //由container_of获得结构体指针inode中结构体cdev的指针,
  58.         //让结构体指针dev指向上述的指针所指的地址,
  59.         //再让file->private指向这一地址,
  60.         dev = container_of(inode->i_cdev, struct gpio_dev, cdev);
  61.         filp->private_data = dev;
  62.         dev->dptr = NULL;
  63.         dev->size = 0;
  64.         //printk(KERN_ERR "gpio_open success!\n");
  65.         return 0;
  66. }

  67. /**
  68. * 功能:释放申请的GPIO端口以及释放链表(gpio_qset)
  69. * *inode:
  70. * *filp:
  71. * 描述:用户空间调用close时运行
  72. *         int (*release) (struct inode *, struct file *);
  73. * 返回值:0
  74. */
  75. static int gpio_close(struct inode *inode, struct file *filp)
  76. {
  77.         struct gpio_dev *dev = filp->private_data;
  78.         struct gpio_qset *qset, *qsetTmp;
  79.         qsetTmp = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
  80.         for(qset = dev->dptr; qset->next !=NULL; qset=qsetTmp)
  81.         {
  82.                 qsetTmp = qset->next;
  83.                 gpio_free(qset->port);        //释放申请的端口
  84.                 kfree(qset);                //释放gpio_qset内存
  85.         }
  86.         gpio_free(qsetTmp->port);
  87.         kfree(qsetTmp);
  88.          //printk(KERN_ERR "gpio release!\n");
  89.         return 0;
  90. }

  91. /**
  92. * 功能:申请新的gpio_qset的内存,确定相应的GPIO端口功能
  93. * *inode:
  94. * *filp:
  95. * cmd:实现的功能,
  96. *     0:读,
  97. *     1:写,
  98. *     2:释放GPIO端口
  99. * arg:执行操作的GPIO端口
  100. * 描述:用户空间调用ioctl时运行
  101. *         long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
  102. * 返回值:成功返回操作的GPIO端口号
  103. *           错误返回相应的错误码
  104. */
  105. static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
  106. {
  107.         int ret;
  108.         struct gpio_dev *dev = filp->private_data;
  109.         struct gpio_qset *qset;

  110.         //cmd == 2 设计成为释放arg端口的功能,之后申请内存的任务便不必执行了,所以直接返回
  111.         if(cmd == 2)
  112.         {
  113.                 gpio_free(arg);
  114.                 return arg;
  115.         }

  116.         dev->size++;

  117.         if(dev->dptr == NULL)
  118.         {
  119.                 dev->dptr = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
  120.                 qset = dev->dptr;
  121.         }
  122.         else
  123.         {
  124.                 for(qset = dev->dptr; qset->next != NULL; qset = qset->next);                        //找到链表最后一项
  125.                 qset->next = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
  126.                 qset = qset->next;
  127.         }
  128.         /*链表数据*/
  129.         qset->num = dev->size;        //确定自己的编号
  130.         qset->ddr = cmd;        //确定方向
  131.         qset->port = arg;        //确定端口号
  132.         qset->next = NULL;        //最后一项地址清空

  133.         //printk(KERN_ERR "qset->num=%d,qset->ddr=%d,qset->port=%d\n", qset->num, qset->ddr, qset->port);
  134.         sprintf(qset->label, "gpio%ld", qset->port);                //确定申请的GPIO使用名称(和端口直接相关)
  135.         ret = gpio_request(qset->port, qset->label);                //申请端口
  136.         
  137.         /*由于gpio_requset会自己判断成功与否并且退出函数,故注释掉对ret的判断
  138.         if(ret < 0)
  139.                 printk(KERN_ERR "%s_requset failled!%d \n", qset->label, ret);
  140.         */

  141.         /*判断GPIO工作方向(输出或输出)*/        
  142.         switch(qset->ddr)
  143.         {
  144.                 case 0:        ret = gpio_direction_input(qset->port);
  145.                         if(ret < 0)
  146.                                 printk(KERN_ERR "gpio_direction_input failled!\n");
  147.                         break;
  148.                 case 1:        ret = gpio_direction_output(qset->port, 1);
  149.                         if(ret < 0)
  150.                                 printk(KERN_ERR "gpio_direction_output failled!\n");
  151.                         break;
  152.                 default:
  153.                         return -EPERM;        /* Operation not permitted */
  154.         }
  155.         return qset->num;
  156. }

  157. /**
  158. * 功能:获取相应端口电平,写入相应的qset_dev->value,写到用户空间的readBuf
  159. * *inode:
  160. * *filp:
  161. * *readBuf:读数据缓存指针,用户空间的指针
  162. * port:被读取的GPIO端口号
  163. * *offp:
  164. * 描述:用户空间调用read时运行
  165. *         ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
  166. * 返回值:成功返回qset->value
  167. *           错误返回相应的错误码
  168. */
  169. static ssize_t gpio_read (struct file *filp, char __user *readBuf, size_t port, loff_t *offp)
  170. {
  171.         long ret;
  172.         struct gpio_dev *dev = filp->private_data;
  173.         struct gpio_qset *qset;
  174.         
  175.         if(dev->dptr == NULL)        
  176.                 return -ENODEV;                /* No such device */
  177.                         
  178.         for(qset = dev->dptr; qset != NULL; qset = qset->next)
  179.         {
  180.                 if(qset->port == port)
  181.                         break;
  182.                 if(qset->next == NULL)        
  183.                         return -ENODEV;        /* No such device */
  184.         }

  185.         if(qset->ddr != 0)                //判断是否ioctl设置为读操作
  186.                 return -EPERM;                /* Operation not permitted */
  187.         qset->value = gpio_get_value(qset->port);
  188.         //printk(KERN_ERR "qset->port:%d, qset->value:%d\n", qset->port, qset->value);
  189.         switch(qset->value)
  190.         {
  191.                 case 0:        ret = copy_to_user(readBuf, "0", 1);
  192.                         break;
  193.                 case 1:        ret = copy_to_user(readBuf, "1", 1);
  194.                         break;
  195.         }
  196.         return qset->value;
  197. }
  198. /**
  199. * 功能:写入相应端口电平,写入相应的qset_dev->value,数据来自用户空间的writeBuf
  200. * *inode:
  201. * *filp:
  202. * *writeBuf:写数据缓存指针,用户空间的指针
  203. * port:被写入的GPIO端口号
  204. * *offp:
  205. * 描述:用户空间调用write时运行
  206. *         ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
  207. * 返回值:成功返回qset->value
  208. *           错误返回相应的错误码
  209. */
  210. static ssize_t gpio_write (struct file *filp, const char __user *writeBuf, size_t port, loff_t *offp)
  211. {
  212.         long ret;
  213.         struct gpio_dev *dev = filp->private_data;
  214.         struct gpio_qset *qset;

  215.         if(dev->dptr == NULL)        
  216.                 return -ENODEV;                /* No such device */
  217.                         
  218.         for(qset = dev->dptr; qset != NULL; qset = qset->next)
  219.         {
  220.         //        printk(KERN_ERR "qset->port=%d,port=%d\n", qset->port, port);
  221.                 if(qset->port == port)
  222.                         break;
  223.                 if(qset->next == NULL)        
  224.                         return -ENODEV;        /* No such device */
  225.         }

  226.         if(qset->ddr != 1)                //判断是否ioctl设置为写操作
  227.                 return -EPERM;                /* Operation not permitted */

  228.         ret = copy_from_user(&qset->value, writeBuf, 1);
  229.         //printk(KERN_ERR "write:%d\n", qset->value);
  230.         switch(qset->value)
  231.         {
  232.                 case '0': qset->value = 0;
  233.                           gpio_set_value(qset->port, 0);
  234.                           break;
  235.                 default : qset->value = 1;
  236.                           gpio_set_value(qset->port, 1);
  237.                           break;
  238.         }
  239.         return qset->value;
  240. }

  241. /*文件操作结构体*/
  242. static const struct file_operations gpio_fops = {
  243.         .owner = THIS_MODULE,
  244.         .open = gpio_open,
  245.         .unlocked_ioctl = gpio_ioctl,
  246.         .write = gpio_write,
  247.         .read = gpio_read,
  248.         .release = gpio_close,
  249. };

  250. /*cdev注册函数*/
  251. static void gpio_setup_cdev(struct gpio_dev *dev, int index)
  252. {
  253.         int ret,devno = gpio_dev_num.devNum + index;
  254.         cdev_init(&dev->cdev, &gpio_fops);
  255.         dev->cdev.owner = THIS_MODULE;
  256.         dev->cdev.ops = &gpio_fops;

  257.         ret = cdev_add(&dev->cdev, devno, 1);

  258.         if(ret)
  259.                 printk(KERN_ERR "error %d : adding gpioCtl%d",ret,index);
  260. }

  261. /*设备初始化函数*/
  262. static int __init omap3gpio_init(void)
  263. {
  264.         int ret;

  265.         gpio_dev_num.count = 1;
  266.         gpio_dev_num.minor_first = 0;
  267.         ret = alloc_chrdev_region(&gpio_dev_num.devNum, gpio_dev_num.minor_first, gpio_dev_num.count, DEVICE_NAME);
  268.         
  269.         if(ret < 0)        
  270.                 return ret;
  271.         
  272.         gpio_dev_num.major = MAJOR(gpio_dev_num.devNum);
  273.         gpio_dev_num.minor = MINOR(gpio_dev_num.devNum);

  274.         gpio_devp = kzalloc(sizeof(struct gpio_dev),GFP_KERNEL);

  275.         gpio_setup_cdev(gpio_devp, 0);

  276.         printk(KERN_ERR "gpio alloc_chrdev_region success, major = %d\n", gpio_dev_num.major);
  277.         return 0;
  278. }

  279. /*设备释放函数*/
  280. static void __exit omap3gpio_exit(void)
  281. {
  282.         /*test*/
  283.         //struct file *filp;
  284.         //struct inode *inode;
  285.         //inode = container_of(&gpio_devp->cdev, struct inode, i_cdev);
  286.         //filp = container_of(gpio_devp, struct file, private_data);
  287.         //gpio_close(inode, filp);
  288.         /*test*/

  289.         cdev_del(&gpio_devp->cdev);
  290.         kfree(gpio_devp);
  291.         unregister_chrdev_region(gpio_dev_num.devNum, 1);
  292.         printk(KERN_ERR "gpio unregister_chrdev_region success, major = %d\n", gpio_dev_num.major);
  293. }

  294. MODULE_LICENSE("Dual BSD/GPL");
  295. MODULE_AUTHOR("Mlo_Lv,Tute-421E-studio");
  296. MODULE_DESCRIPTION("This module is used to conrtol omap3 gpio");

  297. module_init(omap3gpio_init);
  298. module_exit(omap3gpio_exit);
复制代码

        我的驱动程序文件名是gpioCtl.c,如果各位想直接使用这一驱动的话,请使用这个文件名,不要修改。还有就是其中对于GPIO操作的一些函数,例如"gpio_requset()","gpio_free()","gpio_set_value()",如果有不理解的可以参照:http://blog.csdn.net/beyondioi/article/details/6984406,这篇博文详细讲解了omap系列产品用于操作GPIO的函数,其实看看源码应该能够明白这写函数的意思。


Makefile:
  1. #Makefile for gpioCtl.c
  2. ARCH=arm
  3. CROSS_COMPILE=arm-none-linux-gnueabi-
  4. ifneq ($(KERNELRELEASE),)
  5.         obj-m := gpioCtl.o
  6. else
  7.         KERNELDIR ?= /usr/embeded/bb-black/sources/kernel/kernel
  8.         PWD := $(shell pwd)
  9. default:
  10.         make -C $(KERNELDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules

  11. app: app.c
  12.         $(CROSS_COMPILE)gcc -o app app.c
  13. clean:
  14.         $(MAKE) -C $(KERNELDIR) M=$(PWD) clean
  15.         rm -rf modules.order
  16. cleanr:        
  17.         rm *~
  18. endif
复制代码

        如果各位使用的交叉编译器和我的不一样的话,请修改CROSS_COMPILE(一样的话就免了),然后修改KERNELDIR成为自己内核的存储路径,至此所有有关驱动的程序就写完了,将上面的两短代码放在同一文件目录下,make(编译驱动之前,请先编译好内核),就会生成相应的驱动gpioCtl.ko





/*-------------------------------------------------------------------------------------------------------------------------------------------------------*/
用户空间
应用程序app.c:
  1. #include
  2. #include
  3. #include
  4. #include
  5. #include
  6. #include
  7. #include
  8. #include

  9. #define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))

  10. int main(int argc, char * argv)
  11. {
  12.         int i, n, fd;
  13.         char num;
  14.         int ret;
  15.         fd = open("/dev/gpioCtl", O_RDWR);                //打开设备
  16.         if (fd < 0)
  17.         {
  18.             printf("can't open /dev/gpioCtl!\n");
  19.             exit(1);
  20.         }
  21.         sleep(1);
  22.         ioctl(fd, 1, GPIO_TO_PIN(1,22));                //设置gpio1-22为输出(user:led3)
  23.         ioctl(fd, 0, GPIO_TO_PIN(2, 1));                //设置gpio2-1 为输入(p8-18)
  24.         while (1)
  25.         {
  26.                 num = 1;
  27.                 ret = write(fd,"1",GPIO_TO_PIN(1,22));
  28.                 if(ret < 0)
  29.                 {
  30.                         perror("write");
  31.                         return -1;
  32.                 }
  33.                 sleep(1);
  34.                 ret = write(fd,"0",GPIO_TO_PIN(1,22));
  35.                 if(ret < 0)
  36.                 {
  37.                     perror("write");
  38.                     return -1;
复制代码

        执行make app 交叉编译应用程序。此程序能实现user:led3闪烁效果以及读取gpio2-1的电平!




/*-------------------------------------------------------------------------------------------------------------------------------------------------------*/
shell gpioCtl.sh:
  1. #!/bin/sh
  2. insmod gpioCtl.ko
  3. mknod /dev/gpioCtl c 245 0
复制代码

        我在上面的驱动程序编号申请采用动态分配方式,但是每次分配的主设备号都是254,所以shell中的设备号采用了245,如果各位动态分配的结果不是245,可能需要修改shell,cat /proc/devices 可以察看到分配到的设备号。


        现在将所有文件拷贝到板子上
chmod +x gpioCtl.sh  为脚本增加执行权
./gpioCtl.sh                  运行脚本
./app                             运行程序

如果没有问题的话,现在就能看到 led 闪烁以及读取到的 p2-1的电平了!




        今天就写到这里吧!感谢各位捧场!

[ 本帖最后由 yisshll 于 2013-9-15 12:57 编辑 ]

最新回复

copy_to_user总是出错,在应用层没有获得相应的值,这可能是什么原因?  详情 回复 发表于 2014-12-25 17:16

赞赏

1

查看全部赞赏

点赞 关注(2)
 

回复
举报

2776

帖子

413

TA的资源

五彩晶圆(中级)

沙发
 
非常给力啊!

点评

这还是我头一次发这种帖子呢!呵呵..一起学习!!  详情 回复 发表于 2013-9-16 10:44
个人签名
 
 

回复

1149

帖子

3

TA的资源

五彩晶圆(初级)

板凳
 
 
 

回复

1172

帖子

3

TA的资源

五彩晶圆(初级)

4
 
一定要跟着学习啊

点评

恩!一同学习!加油加油..:)  详情 回复 发表于 2013-9-16 10:45
 
 
 

回复

3

帖子

0

TA的资源

一粒金砂(初级)

5
 

回复 沙发qinkaiabc 的帖子

这还是我头一次发这种帖子呢!呵呵..一起学习!!
 
 
 

回复

3

帖子

0

TA的资源

一粒金砂(初级)

6
 

回复 4楼ou513 的帖子

恩!一同学习!加油加油..
 
 
 

回复

1

帖子

0

TA的资源

一粒金砂(初级)

7
 
好文章!!!
 
 
 

回复

1

帖子

0

TA的资源

一粒金砂(初级)

8
 
楼主,app.c代码没有贴全啊,都贴出来看看啊
 
 
 

回复

1

帖子

0

TA的资源

一粒金砂(初级)

9
 
最后读按键状态的饿程序没有哦?怎么不贴出来呢??
 
 
 

回复

9

帖子

0

TA的资源

一粒金砂(初级)

10
 
求楼主源代码啊,新手很苦逼啊!
 
 
 

回复

9

帖子

0

TA的资源

一粒金砂(初级)

11
 
include一个都没有,搞毛啊!
 
 
 

回复

9

帖子

0

TA的资源

一粒金砂(初级)

12
 
修改测试发现,这个驱动是有问题的,并不能实现io口的控制!
 
 
 

回复

5

帖子

0

TA的资源

一粒金砂(初级)

13
 
copy_to_user总是出错,在应用层没有获得相应的值,这可能是什么原因?
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

关闭
站长推荐上一条 1/10 下一条
直播报名最后5天!
【直播主题】抗干扰、高性能:安世半导体 74HCS 逻辑芯片,加速优化系统成本
【直播时间】7月17日(下周四)下午14:00
【直播奖品】65W氮化镓充电器套装、每日坚果、定位防丢器、经典简约双肩包

查看 »

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网 7

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表