赞
踩
其实在本人的理解看来,在驱动上面操控LED,和使用STM32在操控LED是大同小异的,因为本质都是控制引脚的输出电平,来达到点亮或者熄灭LED的作用,在这里,我们想要操控LED,我们首先要先清除它的原理图是什么样的。
如上图所示可知,想要点亮LED,那么我们需要控制引脚,使其输出低电平,方可打开LED,输出高电平,关闭LED。同时由图可知,LED对应的引脚在GPIO5_3,下面我们来详细分析操控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_CCGR1 | 0x20C406C | 打开时钟 |
SW_MUX_CTL_PAD_SNVS_TAMPER3 | 0x2290014 | 引脚复用 |
GPIO5_GDIR | 0x20AC004 | 设置为输出模式 |
GPIO5_DR | 0x20AC000 | 设置输出电平高低 |
我们需要先设置四个指针,稍后用来存储物理地址映射过来的虚拟地址
(为什么这里要用到volatile,是为了防止编译器对变量进行优化)
- /* 使能时钟 */
- static volatile unsigned int* CCM_CCGR1 = NULL;
- /* 引脚复用 */
- static volatile unsigned int* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = NULL;
- /* 设置为输出模式 */
- static volatile unsigned int* GPIO5_GDIR = NULL;
- /* 设置输出高电平/低电平 */
- static volatile unsigned int* GPIO5_DR = NULL;
然后,我们进行映射,把物理地址通过MMU映射到虚拟地址上,在这里我们使用ioremap这个函数,就是为了映射物理地址到虚拟地址上,好让我们可以操控这些寄存器。需要注意的是,ioremap映射的单元并不是以字节算的,而是以页表算的,也就是4096字节,如下填入4也就是映射4个页表的字节
- CCM_CCGR1 = ioremap(0x20C406C , 4 );
- IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014 , 4 );
- GPIO5_GDIR = ioremap(0x020AC004, 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,表示设置为输出模式
- *CCM_CCGR1 |= (3 << 30);
- /* 因为引脚复用为GPIO为0101,包含0,所以先清零,防止原本的位包含1,导致|1后,还会是1 */
- *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~(0xf);
- *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 5;
- *GPIO5_GDIR |= (1 << 3);
经过如上三个寄存器的设置之后,此时只需要设置GPIO5_DR寄存器就可以控制引脚的输入输出了,控制此引脚的bit3,置0表示输出低电平,反之输出高电平
- /* 输出低电平 */
- *GPIO5_DR &= ~(1 << 3);
- /* 输出高电平 */
- *GPIO5_DR |= (1 << 3);
完整的代码分为 应用层程序、驱动程序、Makefile
通过open打开设备节点,然后对设备节点进行操作来达到点灯和熄灯的目的
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <stdio.h>
- #include <string.h>
- #include <unistd.h>
-
- int main(int argc, char** argv)
- {
-
- char status = 0;
-
- if( argc != 3 )
- {
- printf("./ledtest /dev/myled on \n");
- printf("./ledtest /dev/myled off \n");
- return -1;
- }
-
- int fd;
-
- //open
- fd = open(argv[1] , O_RDWR);
- if( fd < 0 )
- {
- printf("open %s file \n",argv[1]);
- return -1;
- }
- //write
- if( 0 == strcmp(argv[2] , "on") )
- {
- status = 1;
- write(fd , &status , 1);
- }
- else
- {
- status = 0;
- write(fd , &status , 1);
- }
-
- return 1;
-
- }
其实GPIO5的时钟默认是打开的,所以我在这里没有在对此寄存器进行操作
- #include <linux/module.h>
-
- #include <linux/fs.h>
- #include <linux/errno.h>
- #include <linux/miscdevice.h>
- #include <linux/kernel.h>
- #include <linux/major.h>
- #include <linux/mutex.h>
- #include <linux/proc_fs.h>
- #include <linux/seq_file.h>
- #include <linux/stat.h>
- #include <linux/init.h>
- #include <linux/device.h>
- #include <linux/tty.h>
- #include <linux/kmod.h>
- #include <linux/gfp.h>
- #include <linux/io.h>
-
-
-
- /********************** 无法卸载驱动,要先释放设备device在释放class ***************************/
-
- /*
- 如果要使用到物理设备的话,需要把外设的地址映射到虚拟地址上
- LED的流程
- 1、使能
- 2、设置引脚为GPIO
- 3、设置为输出模式
- 4、设置值
- */
-
- /*
- IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 地址:0x02290000 + 0x14
- 设置引脚复用
- */
- static volatile unsigned int* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
- /*
- GPIO5_GDIR 地址:0x020AC004
- 设置引脚输出模式
- */
- static volatile unsigned int* GPIO5_GDIR;
- /*
- GPIO5_DR 地址:0x020AC000
- 设置引脚输出高电平/低电平
- */
- static volatile unsigned int* GPIO5_DR;
-
-
-
-
-
- //主设备号
- static int major;
- //节点
- static struct class *led_class;
-
-
-
-
- ssize_t led_write (struct file *file, const char __user *buf, size_t size, loff_t *ppos)
- {
- /* 判断应用层想要写入的数据 */
- int ret;
- char val;
- ret = copy_from_user(&val, buf, 1);
- if(val)
- {
- /* open led */
- *GPIO5_DR &= ~(1 << 3);
- }
- else
- {
-
- /* off led */
- *GPIO5_DR |= (1 << 3);
- }
- return 1;
-
- }
-
- int led_open (struct inode *inode, struct file *file)
- {
-
-
- /* 设置引脚服用为GPIO */
- *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;
- *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x5;
-
- /* 设置为输出模式 */
- *GPIO5_GDIR |= (1 << 3);
- return 0;
- }
-
-
- static const struct file_operations led_fops = {
- .owner = THIS_MODULE,
- .open = led_open,
- .write = led_write,
-
- };
-
-
- /* 入口函数 */
- static int __init led_init(void)
- {
-
- printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);
-
- /* 注册结构体到内核 */
- major = register_chrdev(0, "myled", &led_fops);
-
- /* 映射物理地址到虚拟地址上 */
-
- IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14 , 4);
- GPIO5_GDIR = ioremap(0x020AC004, 4);
- GPIO5_DR = ioremap(0x020AC000, 4);
-
-
-
- /* 创建节点 */
- led_class = class_create(THIS_MODULE, "myled");
- /* 创建设备 */
- device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled");
-
- return 0;
- }
-
-
- /* 有注册函数就有卸载函数 */
- static void __exit led_exit(void)
- {
- /* 清除物理地址的映射 */
- iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
- iounmap(GPIO5_GDIR);
- iounmap(GPIO5_DR);
-
-
- /* 设备卸载 */
- device_destroy(led_class, MKDEV(major, 0));
- /* 卸载节点 */
- class_destroy(led_class);
-
-
-
- /* 卸载结构体从内核 */
- unregister_chrdev( major, "myled");
-
- }
-
- /* 需要用某些宏表示上述两个函数分别是入口函数和出口函数 */
- module_init(led_init);
- module_exit(led_exit);
-
- /* 遵循GPL协议 */
- MODULE_LICENSE("GPL");
- KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88
-
- all:
- make -C $(KERN_DIR) M=`pwd` modules
- $(CROSS_COMPILE)gcc -o led_driver_test led_driver_test.c
-
- clean:
- make -C $(KERN_DIR) M=`pwd` modules clean
- rm -rf modules.order
- rm -f led_driver_test
-
- obj-m += led_driver.o
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。