• 驱动开发---基于gpio子系统编写LED灯的驱动


    一、GPIO子系统相关API

    1.解析GPIO相关的设备树节点

    1. struct device_node *of_find_node_by_path(const char *path)
    2. 功能:根据设备树节点路径解析设备树节点信息
    3. 参数:
    4. path:设备树所在的节点路径 /mynode@0X12345678
    5. 返回值:成功返回目标节点首地址,失败返回NULL

    2.根据解析的GPIO相关节点信息获取GPIO编号

    1. #include
    2. int of_get_named_gpio(struct device_node *np,const char *propname, int index)
    3. 功能:获取GPIO编号
    4. 参数:
    5. np:设备树节点指针
    6. proname:gpio编号信息对应到的键名
    7. index:管脚在这个属性键值对中的索引号
    8. 返回值:成功返回GPIO编号,失败返回错误码

    3.向内核申请要使用的GPIO编号

    1. #include
    2. int gpio_request(unsigned gpio, const char *label)
    3. 功能:申请GPIO编号
    4. 参数:
    5. gpio:要申请的GPIO编号
    6. label:标签,填NULL
    7. 返回值:成功返回0,失败返回错误码
    1. int gpio_direction_input(unsigned gpio)
    2. 功能:将gpio编号对应的gpio管脚设置为输入
    3. 参数:
    4. gpio:gpio编号
    5. 返回值:成功返回0,失败返回错误码
    1. int gpio_direction_output(unsigned gpio, int value)
    2. 功能:将gpio编号对应的gpio管脚设置为是输出
    3. 参数:
    4. gpio:gpio编号
    5. value:默认的输出值 (1)输出高电平 (0)输出低电平
    6. 返回值:成功返回0,失败返回错误码
    1. void gpio_set_value(unsigned gpio, int value)
    2. 功能:设置gpio编号对应的gpio管脚 输出高低电平
    3. 参数:
    4. gpio:gpio编号
    5. value:默认的输出值 (1)输出高电平 (0)输出低电平
    6. 返回值:无
    1. int gpio_get_value(unsigned gpio)
    2. 功能:获取gpio编号对应到的GPIO引脚状态值
    3. 参数:
    4. gpio:gpio编号
    5. 返回值:1(高电平) 0(低电平状态)
    1. void gpio_free(unsigned gpio)
    2. 功能:释放GPIO编号
    3. 参数:要释放的gpio编号

    二、添加LED的设备树节点信息

    在stm32mp157a-fsmp1a.dts文件的根节点中添加如下内容

    1. myled{
    2. led1-gpio=<&gpioe 10 0>;//10表示使用的gpioe第几个管脚 0,表示gpio默认属性
    3. led2-gpio=<&gpiof 10 0>;
    4. led3-gpio=<&gpioe 8 0>;
    5. };

    三、基于gpio子系统编写LED灯的驱动

    1.头文件

    1. #ifndef __HEAD_H__
    2. #define __HEAD_H__
    3. typedef struct{
    4. unsigned int MODER;
    5. unsigned int OTYPER;
    6. unsigned int OSPEEDR;
    7. unsigned int PUPDR;
    8. unsigned int IDR;
    9. unsigned int ODR;
    10. }gpio_t;
    11. #define PHY_LED1_ADDR 0X50006000
    12. #define PHY_LED2_ADDR 0X50007000
    13. #define PHY_LED3_ADDR 0X50006000
    14. #define PHY_RCC_ADDR 0X50000A28
    15. //构建LED开关的功能码
    16. #define LED_ON _IO('l',1)
    17. #define LED_OFF _IO('l',0)
    18. #endif

    2.测试文件

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include "head.h"
    10. int main(int argc, const char *argv[])
    11. {
    12. char buf[128] = "";
    13. int a;
    14. int fd;
    15. while (1)
    16. {
    17. printf("请选择要打开的灯(1,2,3)\n");
    18. scanf(" %d", &a);
    19. switch (a)
    20. {
    21. case 1:
    22. fd = open("/dev/myled0", O_RDWR);
    23. if (fd < 0)
    24. {
    25. printf("设备文件打开失败\n");
    26. exit(-1);
    27. }
    28. printf("打开文件myled0成功\n");
    29. break;
    30. case 2:
    31. fd = open("/dev/myled1", O_RDWR);
    32. if (fd < 0)
    33. {
    34. printf("设备文件打开失败\n");
    35. exit(-1);
    36. }
    37. printf("打开文件myled1成功\n");
    38. break;
    39. case 3:
    40. fd = open("/dev/myled2", O_RDWR);
    41. if (fd < 0)
    42. {
    43. printf("设备文件打开失败\n");
    44. exit(-1);
    45. }
    46. printf("打开文件myled2成功\n");
    47. break;
    48. default:
    49. printf("请输入范围内的数\n");
    50. }
    51. int b;
    52. printf("请开灯关灯(0/1)\n");
    53. scanf(" %d",&b);
    54. switch(b)
    55. {
    56. case 1:
    57. ioctl(fd,LED_ON);
    58. break;
    59. case 0:
    60. ioctl(fd,LED_OFF);
    61. break;
    62. default:
    63. printf("请输入'0'或'1'\n");
    64. }
    65. close(fd);
    66. printf("关闭文件\n");
    67. }
    68. return 0;
    69. }

    3.驱动文件

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include "head.h"
    13. struct cdev *cdev;
    14. char kbuf[128]={0};
    15. unsigned int major=0;
    16. unsigned int minor=0;
    17. dev_t devno;
    18. module_param(major,uint,0664);//方便在命令行传递major的值
    19. struct class*cls;
    20. struct device *dev;
    21. gpio_t *vir_led1;
    22. gpio_t *vir_led2;
    23. gpio_t *vir_led3;
    24. unsigned int *vir_rcc;
    25. struct device_node *dnode;
    26. struct gpio_desc *gpiono1;
    27. struct gpio_desc *gpiono2;
    28. struct gpio_desc *gpiono3;
    29. int mycdev_open(struct inode *inode, struct file *file)
    30. {
    31. int min=MINOR(inode->i_rdev);//根据打开的文件对应的设备号获取次设备号
    32. file->private_data=(void *)min;
    33. printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    34. return 0;
    35. }
    36. long mycdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    37. {
    38. int min=(int)file->private_data;
    39. switch(min)
    40. {
    41. case 0://控制LED1
    42. switch (cmd)
    43. {
    44. case LED_ON: // 开灯
    45. gpiod_set_value(gpiono1,1);
    46. break;
    47. case LED_OFF://关灯
    48. gpiod_set_value(gpiono1,0);
    49. break;
    50. }
    51. break;
    52. case 1://控制LED2
    53. switch (cmd)
    54. {
    55. case LED_ON: // 开灯
    56. gpiod_set_value(gpiono2,1);
    57. break;
    58. case LED_OFF://关灯
    59. gpiod_set_value(gpiono2,0);
    60. break;
    61. }
    62. break;
    63. case 2://控制LED3
    64. switch (cmd)
    65. {
    66. case LED_ON: // 开灯
    67. gpiod_set_value(gpiono3,1);
    68. break;
    69. case LED_OFF://关灯
    70. gpiod_set_value(gpiono3,0);
    71. break;
    72. }
    73. break;
    74. }
    75. return 0;
    76. }
    77. int mycdev_close(struct inode *inode, struct file *file)
    78. {
    79. printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    80. return 0;
    81. }
    82. // 定义操作方法结构体变量并赋值
    83. struct file_operations fops = {
    84. .open = mycdev_open,
    85. .unlocked_ioctl = mycdev_ioctl,
    86. .release = mycdev_close,
    87. };
    88. int all_led_init(void)
    89. {
    90. // 寄存器地址的映射
    91. vir_led1 = ioremap(PHY_LED1_ADDR, sizeof(gpio_t));
    92. if (vir_led1 == NULL)
    93. {
    94. printk("ioremap filed:%d\n", __LINE__);
    95. return -ENOMEM;
    96. }
    97. vir_led2 = ioremap(PHY_LED2_ADDR, sizeof(gpio_t));
    98. if (vir_led2 == NULL)
    99. {
    100. printk("ioremap filed:%d\n", __LINE__);
    101. return -ENOMEM;
    102. }
    103. vir_led3 = vir_led1;
    104. vir_rcc = ioremap(PHY_RCC_ADDR, 4);
    105. if (vir_rcc == NULL)
    106. {
    107. printk("ioremap filed:%d\n", __LINE__);
    108. return -ENOMEM;
    109. }
    110. printk("物理地址映射成功\n");
    111. // 寄存器的初始化
    112. // rcc
    113. (*vir_rcc) |= (0X3 << 4);
    114. // led1
    115. vir_led1->MODER &= (~(3 << 20));
    116. vir_led1->MODER |= (1 << 20);
    117. vir_led1->ODR &= (~(1 << 10));
    118. // led2
    119. vir_led2->MODER &= (~(3 << 20));
    120. vir_led2->MODER |= (1 << 20);
    121. vir_led2->ODR &= (~(1 << 10));
    122. // led3
    123. vir_led3->MODER &= (~(3 << 16));
    124. vir_led1->MODER |= (1 << 16);
    125. vir_led1->ODR &= (~(1 << 8));
    126. printk("寄存器初始化成功\n");
    127. return 0;
    128. }
    129. static int __init mycdev_init(void)
    130. {
    131. int ret;
    132. //为字符设备驱动对象申请空间
    133. cdev=cdev_alloc();
    134. if(cdev==NULL)
    135. {
    136. printk("字符设备驱动对象申请空间失败\n");
    137. ret=-EFAULT;
    138. goto out1;
    139. }
    140. printk("申请对象空间成功\n");
    141. //初始化字符设备驱动对象
    142. cdev_init(cdev,&fops);
    143. //申请设备号
    144. if(major>0)//静态指定设备号
    145. {
    146. ret=register_chrdev_region(MKDEV(major,minor),3,"myled");
    147. if(ret)
    148. {
    149. printk("静态申请设备号失败\n");
    150. goto out2;
    151. }
    152. }
    153. else if(major==0)//动态申请设备号
    154. {
    155. ret=alloc_chrdev_region(&devno,minor,3,"myled");
    156. if(ret)
    157. {
    158. printk("动态申请设备号失败\n");
    159. goto out2;
    160. }
    161. major=MAJOR(devno);//获取主设备号
    162. minor=MINOR(devno);//获取次设备号
    163. }
    164. printk("申请设备号成功\n");
    165. //注册字符设备驱动对象
    166. ret=cdev_add(cdev,MKDEV(major,minor),3);
    167. if(ret)
    168. {
    169. printk("注册字符设备驱动对象失败\n");
    170. goto out3;
    171. }
    172. printk("注册字符设备驱动对象成功\n");
    173. //寄存器映射及初始化
    174. all_led_init();
    175. //向上提交目录信息
    176. cls=class_create(THIS_MODULE,"myled");
    177. if(IS_ERR(cls))
    178. {
    179. printk("向上提交目录失败\n");
    180. ret=-PTR_ERR(cls);
    181. goto out4;
    182. }
    183. printk("向上提交目录成功\n");
    184. //向上提交设备节点信息
    185. int i;
    186. for(i=0;i<3;i++)
    187. {
    188. dev=device_create(cls,NULL,MKDEV(major,i),NULL,"myled%d",i);
    189. if(IS_ERR(dev))
    190. {
    191. printk("向上提交设备节点信息失败\n");
    192. ret=-PTR_ERR(dev);
    193. goto out5;
    194. }
    195. }
    196. printk("向上提交设备信息成功\n");
    197. //解析LED的设备树节点
    198. dnode=of_find_node_by_path("/myled");
    199. if(dnode==NULL)
    200. {
    201. printk("解析设备树节点失败\n");
    202. return -ENXIO;
    203. }
    204. printk("解析GPIO信息成功\n");
    205. gpiono1=gpiod_get_from_of_node(dnode,"led1-gpio",0,GPIOD_OUT_LOW,NULL);
    206. if(IS_ERR(gpiono1))
    207. {
    208. printk("申请gpio对象失败\n");
    209. return -ENXIO;
    210. }
    211. printk("申请gpio信息对象成功\n");
    212. gpiono2=gpiod_get_from_of_node(dnode,"led2-gpio",0,GPIOD_OUT_LOW,NULL);
    213. if(IS_ERR(gpiono2))
    214. {
    215. printk("申请gpio对象失败\n");
    216. return -ENXIO;
    217. }
    218. printk("申请gpio信息对象成功\n");
    219. gpiono3=gpiod_get_from_of_node(dnode,"led3-gpio",0,GPIOD_OUT_LOW,NULL);
    220. if(IS_ERR(gpiono3))
    221. {
    222. printk("申请gpio对象失败\n");
    223. return -ENXIO;
    224. }
    225. printk("申请gpio信息对象成功\n");
    226. return 0;
    227. out5:
    228. //释放前一次提交成功的设备信息
    229. for(--i;i>=0;i--)
    230. {
    231. device_destroy(cls,MKDEV(major,i));
    232. }
    233. class_destroy(cls);//释放目录
    234. out4:
    235. cdev_del(cdev);
    236. out3:
    237. unregister_chrdev_region(MKDEV(major,minor),3);
    238. out2:
    239. kfree(cdev);
    240. out1:
    241. return ret;
    242. }
    243. static void __exit mycdev_exit(void)
    244. {
    245. //释放对象指针
    246. gpiod_put(gpiono1);
    247. gpiod_put(gpiono2);
    248. gpiod_put(gpiono3);
    249. /*销毁设备节点信息*/
    250. int i;
    251. for (i = 0; i < 3; i++)
    252. {
    253. device_destroy(cls, MKDEV(major, i));
    254. }
    255. // 销毁目录信息
    256. class_destroy(cls);
    257. //注销驱动对象
    258. cdev_del(cdev);
    259. //释放设备号
    260. unregister_chrdev_region(MKDEV(major,minor),3);
    261. //释放对象空间
    262. kfree(cdev);
    263. // 取消地址映射
    264. iounmap(vir_led1);
    265. iounmap(vir_led2);
    266. iounmap(vir_rcc);
    267. }
    268. module_init(mycdev_init);
    269. module_exit(mycdev_exit);
    270. MODULE_LICENSE("GPL");

    4.Makefile

    1. modname?=demo #给定一个默认的模块名
    2. arch?=arm #给定一个架构的模块名
    3. ifeq ($(arch),arm)#判断要编译的架构进而使用不同的Makefile规则进行编译
    4. #定义变量存放内核源码顶层路径
    5. KERNELDIR:= /home/ubuntu/FSMP1A/linux-stm32mp-5.10.61-stm32mp-r2-r0/linux-5.10.61 #用于编译生成ARM架构的模块
    6. else
    7. KERNELDIR := /lib/modules/$(shell uname -r)/build #用于生成x86架构的模块
    8. endif
    9. #定义变量保存模块化编译的文件的路径
    10. PWD:= $(shell pwd)
    11. all:
    12. #make modules是模块化编译的命令
    13. #make -C $(KERNELDIR)表示读取KERNELDIR路径下的Makefile进行make编译
    14. #M=$(PWD)指定模块化编译的路径
    15. make -C $(KERNELDIR) M=$(PWD) modules
    16. clean: #编译清除
    17. make -C $(KERNELDIR) M=$(PWD) clean
    18. obj-m:=$(modname).o #指定将demo.o独立链接生成内核模块文件

    5.测试

    1.make arch=arm modname=myled

    2.arm-linux-gnueabihf-gcc main.c

    3.cp a.out ~/nfs/rootfs

    4.cp myled.ko ~/nfs/rootfs

     6.测试结果

  • 相关阅读:
    本地虚拟机centos7通过docker安装主从redis3.2
    搭建git私人仓库
    【排序24:俄罗斯套娃信封问题】(动态规划 / 二维最长公共子序列 / 基于二分查找的动态规划)
    【黑马程序员】mysql进阶再进阶篇笔记
    Going Deeper With Convolution(GoogLeNet的理解)
    plv8-使用JavaScript编写存储过程,初体验
    推荐大家一个比较好用的接口测试工具
    【Tools】了解人工通用智能 (AGI):未来的智能体
    芒果YOLOv8改进106:卷积Conv篇:DO-DConv卷积提高性能涨点,使用over-parameterized卷积层提高CNN性能
    多御安全浏览器宝藏功能全新升级,建议低调使用
  • 原文地址:https://blog.csdn.net/m0_62774039/article/details/132993673