当前位置:   article > 正文

[Linux_IMX6ULL驱动开发]-LED驱动

[Linux_IMX6ULL驱动开发]-LED驱动

LED驱动

其实在本人的理解看来,在驱动上面操控LED,和使用STM32在操控LED是大同小异的,因为本质都是控制引脚的输出电平,来达到点亮或者熄灭LED的作用,在这里,我们想要操控LED,我们首先要先清除它的原理图是什么样的。

如上图所示可知,想要点亮LED,那么我们需要控制引脚,使其输出低电平,方可打开LED,输出高电平,关闭LED。同时由图可知,LED对应的引脚在GPIO5_3,下面我们来详细分析操控LED的步骤。


LED驱动的操控步骤

如果我们想要操控LED,那么我们首先需要打开它对应GPIO的时钟,然后,由于芯片上面的很多引脚一般被设置了很多功能,有可能是UART、IIC等,我们需要设置我们需要的GPUO引脚为GPIO功能。设置引脚为GPIO功能后,我们需要设置引脚为输出/输入模式,现在我们想要操控LED灯,那么我们需要设置其为输出模式。此时,LED就已经初始化完成了,我们只需要控制对应寄存器,就可以控制LED对应引脚输出高电平或者低电平了。

引脚对应寄存器

想要实现如上步骤,那么我们需要通过查阅芯片手册,了解到对应引脚的寄存器,以及对应的物理地址(如果不知道引脚对应的物理地址,是无法操控寄存器的),如下,我们需要使用到总共四个寄存器。

第一步我们先查找CCM,也就是打开总线时钟的寄存器的基地址,LED的引脚为GPIO5_3,那么我们直接在文档里面搜索GPIO5,可以找到如下

然后直接搜索 CCGR1 寄存器,就可以找到对应的打开时钟的功能寄存器。

如图我们可知,我们需要把这个32位的寄存器的第 30-31 位设置为1,才可以打开时钟,同时我们也可以知道这个寄存器的地址,地址是一定要使用的,否则我们无法操控。

第二步我们寻找操控引脚复用的寄存器,首先我们搜索GPIO5,得到如下

因为我们是GPIO5的引脚3,所以我们找到如上信息,再次进行搜索,我们可以得到如下两个寄存器:

        SW_MUX_CTL_PAD_SNVS_TAMPER3 SW MUX Control Register

        SW_PAD_CTL_PAD_SNVS_TAMPER3 SW PAD Control Register

SW_MUX_CTL_PAD_SNVS_TAMPER3 这个寄存器才是用来配置引脚复用功能的,我们仅需找到它的地址就好。

可知设置低四位为 0101 时,表示为GPIO功能

第三步我们需要设置GPIO的引脚为输出功能,同样的查看芯片手册,搜索GPIO5

此寄存器的全称为,GPIO5的方向寄存器,那么就是设置它为输出或者输入了,点击跳转到此寄存器的详细界面

地址为基地址加上0x4,也就是0x20AC000加上0x4

可知,设置为1表示输出模式,我们需要设置bit3为1

最后就是我们设置引脚输出高电平或者低电平的寄存器了,文档中搜索GPIO5

当设置bit3为1,则输出高电平,反之则输出低电平

寄存器名称寄存器地址寄存器功能
CCM_CCGR10x20C406C打开时钟
SW_MUX_CTL_PAD_SNVS_TAMPER3 0x2290014引脚复用
GPIO5_GDIR0x20AC004设置为输出模式
GPIO5_DR0x20AC000设置输出电平高低

具体代码实现

我们需要先设置四个指针,稍后用来存储物理地址映射过来的虚拟地址

(为什么这里要用到volatile,是为了防止编译器对变量进行优化)

  1. /* 使能时钟 */
  2. static volatile unsigned int* CCM_CCGR1 = NULL;
  3. /* 引脚复用 */
  4. static volatile unsigned int* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = NULL;
  5. /* 设置为输出模式 */
  6. static volatile unsigned int* GPIO5_GDIR = NULL;
  7. /* 设置输出高电平/低电平 */
  8. static volatile unsigned int* GPIO5_DR = NULL;

然后,我们进行映射,把物理地址通过MMU映射到虚拟地址上,在这里我们使用ioremap这个函数,就是为了映射物理地址到虚拟地址上,好让我们可以操控这些寄存器。需要注意的是,ioremap映射的单元并不是以字节算的,而是以页表算的,也就是4096字节,如下填入4也就是映射4个页表的字节        

  1. CCM_CCGR1 = ioremap(0x20C406C , 4 );
  2. IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014 , 4 );
  3. GPIO5_GDIR = ioremap(0x020AC004, 4);
  4. GPIO5_DR = ioremap(0x020AC000, 4);

那么具体的操作就如下所示

        CCM_CCGR1用来开启时钟,如上我们可知,我们需要设置寄存器的bit 30-31 为1 ,那么 (3 << 30)也就是二进制 0011 左移三十位

        IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3因为需要设置的bit包含0,那么为了防止之前寄存器已经被设置过,我们需要首先对需要设置的那几个bit清零,然后在进行设置,(|=5 , 由于低四位此时为0000,当|=5也就是0101后,低四位变为0101,此时bit0-3为101)

        GPIO5_GDIR ,设置bit3为1,表示设置为输出模式

  1. *CCM_CCGR1 |= (3 << 30);
  2. /* 因为引脚复用为GPIO为0101,包含0,所以先清零,防止原本的位包含1,导致|1后,还会是1 */
  3. *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~(0xf);
  4. *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 5;
  5. *GPIO5_GDIR |= (1 << 3);

经过如上三个寄存器的设置之后,此时只需要设置GPIO5_DR寄存器就可以控制引脚的输入输出了,控制此引脚的bit3,置0表示输出低电平,反之输出高电平

  1. /* 输出低电平 */
  2. *GPIO5_DR &= ~(1 << 3);
  3. /* 输出高电平 */
  4. *GPIO5_DR |= (1 << 3);

完整代码实现

完整的代码分为 应用层程序、驱动程序、Makefile

应用层程序

通过open打开设备节点,然后对设备节点进行操作来达到点灯和熄灯的目的

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <stdio.h>
  5. #include <string.h>
  6. #include <unistd.h>
  7. int main(int argc, char** argv)
  8. {
  9. char status = 0;
  10. if( argc != 3 )
  11. {
  12. printf("./ledtest /dev/myled on \n");
  13. printf("./ledtest /dev/myled off \n");
  14. return -1;
  15. }
  16. int fd;
  17. //open
  18. fd = open(argv[1] , O_RDWR);
  19. if( fd < 0 )
  20. {
  21. printf("open %s file \n",argv[1]);
  22. return -1;
  23. }
  24. //write
  25. if( 0 == strcmp(argv[2] , "on") )
  26. {
  27. status = 1;
  28. write(fd , &status , 1);
  29. }
  30. else
  31. {
  32. status = 0;
  33. write(fd , &status , 1);
  34. }
  35. return 1;
  36. }

驱动层程序

其实GPIO5的时钟默认是打开的,所以我在这里没有在对此寄存器进行操作

  1. #include <linux/module.h>
  2. #include <linux/fs.h>
  3. #include <linux/errno.h>
  4. #include <linux/miscdevice.h>
  5. #include <linux/kernel.h>
  6. #include <linux/major.h>
  7. #include <linux/mutex.h>
  8. #include <linux/proc_fs.h>
  9. #include <linux/seq_file.h>
  10. #include <linux/stat.h>
  11. #include <linux/init.h>
  12. #include <linux/device.h>
  13. #include <linux/tty.h>
  14. #include <linux/kmod.h>
  15. #include <linux/gfp.h>
  16. #include <linux/io.h>
  17. /********************** 无法卸载驱动,要先释放设备device在释放class ***************************/
  18. /*
  19. 如果要使用到物理设备的话,需要把外设的地址映射到虚拟地址上
  20. LED的流程
  21. 1、使能
  22. 2、设置引脚为GPIO
  23. 3、设置为输出模式
  24. 4、设置值
  25. */
  26. /*
  27. IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 地址:0x02290000 + 0x14
  28. 设置引脚复用
  29. */
  30. static volatile unsigned int* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
  31. /*
  32. GPIO5_GDIR 地址:0x020AC004
  33. 设置引脚输出模式
  34. */
  35. static volatile unsigned int* GPIO5_GDIR;
  36. /*
  37. GPIO5_DR 地址:0x020AC000
  38. 设置引脚输出高电平/低电平
  39. */
  40. static volatile unsigned int* GPIO5_DR;
  41. //主设备号
  42. static int major;
  43. //节点
  44. static struct class *led_class;
  45. ssize_t led_write (struct file *file, const char __user *buf, size_t size, loff_t *ppos)
  46. {
  47. /* 判断应用层想要写入的数据 */
  48. int ret;
  49. char val;
  50. ret = copy_from_user(&val, buf, 1);
  51. if(val)
  52. {
  53. /* open led */
  54. *GPIO5_DR &= ~(1 << 3);
  55. }
  56. else
  57. {
  58. /* off led */
  59. *GPIO5_DR |= (1 << 3);
  60. }
  61. return 1;
  62. }
  63. int led_open (struct inode *inode, struct file *file)
  64. {
  65. /* 设置引脚服用为GPIO */
  66. *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;
  67. *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x5;
  68. /* 设置为输出模式 */
  69. *GPIO5_GDIR |= (1 << 3);
  70. return 0;
  71. }
  72. static const struct file_operations led_fops = {
  73. .owner = THIS_MODULE,
  74. .open = led_open,
  75. .write = led_write,
  76. };
  77. /* 入口函数 */
  78. static int __init led_init(void)
  79. {
  80. printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);
  81. /* 注册结构体到内核 */
  82. major = register_chrdev(0, "myled", &led_fops);
  83. /* 映射物理地址到虚拟地址上 */
  84. IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14 , 4);
  85. GPIO5_GDIR = ioremap(0x020AC004, 4);
  86. GPIO5_DR = ioremap(0x020AC000, 4);
  87. /* 创建节点 */
  88. led_class = class_create(THIS_MODULE, "myled");
  89. /* 创建设备 */
  90. device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled");
  91. return 0;
  92. }
  93. /* 有注册函数就有卸载函数 */
  94. static void __exit led_exit(void)
  95. {
  96. /* 清除物理地址的映射 */
  97. iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
  98. iounmap(GPIO5_GDIR);
  99. iounmap(GPIO5_DR);
  100. /* 设备卸载 */
  101. device_destroy(led_class, MKDEV(major, 0));
  102. /* 卸载节点 */
  103. class_destroy(led_class);
  104. /* 卸载结构体从内核 */
  105. unregister_chrdev( major, "myled");
  106. }
  107. /* 需要用某些宏表示上述两个函数分别是入口函数和出口函数 */
  108. module_init(led_init);
  109. module_exit(led_exit);
  110. /* 遵循GPL协议 */
  111. MODULE_LICENSE("GPL");

Makefile

  1. KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88
  2. all:
  3. make -C $(KERN_DIR) M=`pwd` modules
  4. $(CROSS_COMPILE)gcc -o led_driver_test led_driver_test.c
  5. clean:
  6. make -C $(KERN_DIR) M=`pwd` modules clean
  7. rm -rf modules.order
  8. rm -f led_driver_test
  9. obj-m += led_driver.o

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/404264
推荐阅读
相关标签
  

闽ICP备14008679号