[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_CCGR1 | 0x20C406C | 打开时钟 |
| SW_MUX_CTL_PAD_SNVS_TAMPER3 | 0x2290014 | 引脚复用 |
| GPIO5_GDIR | 0x20AC004 | 设置为输出模式 |
| GPIO5_DR | 0x20AC000 | 设置输出电平高低 |


具体代码实现

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

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

cpp 复制代码
/* 使能时钟 */
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个页表的字节

cpp 复制代码
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,表示设置为输出模式

cpp 复制代码
*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表示输出低电平,反之输出高电平

cpp 复制代码
/* 输出低电平 */
*GPIO5_DR &= ~(1 << 3);
/* 输出高电平 */
*GPIO5_DR |= (1 << 3);

完整代码实现

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

应用层程序

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

cpp 复制代码
#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的时钟默认是打开的,所以我在这里没有在对此寄存器进行操作

cpp 复制代码
#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");

Makefile

bash 复制代码
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
相关推荐
嵌入(师)16 小时前
嵌入式驱动开发详解1(系统调用)
驱动开发
昵称p16 小时前
杂项驱动开发
驱动开发·gpio子系统·杂项驱动开发
7yewh21 小时前
嵌入式硬件杂谈(四)-高速板PCB设计 高速信号全面讲解 蛇形线 等长线 差分对 阻抗对
驱动开发·嵌入式硬件·mcu·物联网·硬件工程·pcb工艺·精益工程
lishing61 天前
Linux驱动开发(9):pinctrl子系统和gpio子系统--led实验
linux·运维·驱动开发
lishing61 天前
Linux驱动开发(7):使用设备树实现RGB 灯驱动
linux·驱动开发
TeYiToKu2 天前
笔记整理—linux驱动开发部分(13)块设备
linux·c语言·驱动开发·笔记·嵌入式硬件·arm
网易独家音乐人Mike Zhou2 天前
【Linux驱动开发】irq中断配置API及中断应用 阻塞休眠和非阻塞的驱动操作
linux·c语言·驱动开发·stm32·单片机·mcu·iot
believe、悠闲2 天前
GetVolumeInformation函数使用记录
c++·windows·驱动开发
不怕犯错,就怕不做2 天前
修复kernel编译栈帧大小异常问题error: the frame size of 1928 bytes is larger than 1024 bytes
linux·arm开发·驱动开发
bigbig猩猩2 天前
Gin 框架中的表单处理与数据绑定
驱动开发·gin