[Linux_IMX6ULL驱动开发]-LED驱动

这篇具有很好参考价值的文章主要介绍了[Linux_IMX6ULL驱动开发]-LED驱动。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

LED驱动

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

[Linux_IMX6ULL驱动开发]-LED驱动,IMX6ULL,驱动开发,LED驱动,imx6ull,嵌入式驱动,led

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


LED驱动的操控步骤

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

[Linux_IMX6ULL驱动开发]-LED驱动,IMX6ULL,驱动开发,LED驱动,imx6ull,嵌入式驱动,led

引脚对应寄存器

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

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

[Linux_IMX6ULL驱动开发]-LED驱动,IMX6ULL,驱动开发,LED驱动,imx6ull,嵌入式驱动,led

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

[Linux_IMX6ULL驱动开发]-LED驱动,IMX6ULL,驱动开发,LED驱动,imx6ull,嵌入式驱动,led

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

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

[Linux_IMX6ULL驱动开发]-LED驱动,IMX6ULL,驱动开发,LED驱动,imx6ull,嵌入式驱动,led

因为我们是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功能

[Linux_IMX6ULL驱动开发]-LED驱动,IMX6ULL,驱动开发,LED驱动,imx6ull,嵌入式驱动,led

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

[Linux_IMX6ULL驱动开发]-LED驱动,IMX6ULL,驱动开发,LED驱动,imx6ull,嵌入式驱动,led

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

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

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

[Linux_IMX6ULL驱动开发]-LED驱动,IMX6ULL,驱动开发,LED驱动,imx6ull,嵌入式驱动,led

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

[Linux_IMX6ULL驱动开发]-LED驱动,IMX6ULL,驱动开发,LED驱动,imx6ull,嵌入式驱动,led

当设置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的时钟默认是打开的,所以我在这里没有在对此寄存器进行操作文章来源地址https://www.toymoban.com/news/detail-851087.html

#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

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

到了这里,关于[Linux_IMX6ULL驱动开发]-LED驱动的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【IMX6ULL驱动开发学习】05.IMX6ULL驱动开发_编写第一个hello驱动【熬夜肝】

    经过以下四个步骤,终于可以开始驱动开发了 01.安装交叉编译环境【附下载地址】 02.IMX6ULL烧写Linux系统 03.设置IMX6ULL开发板与虚拟机在同一网段 04.IMX6ULL开发板与虚拟机互传文件 一、获取内核、编译内核 二、创建vscode工作区,添加内核目录和个人目录 三、了解驱动程序编写

    2024年02月06日
    浏览(48)
  • iMX6ULL驱动开发 | 让imx6ull开发板支持usb接口FC游戏手柄

    手边有一闲置的linux开发板iMX6ULL一直在吃灰,不用来搞点事情,总觉得对不住它。业余打发时间就玩起来吧,总比刷某音强。从某多多上买来一个usb接口的游戏手柄,让开发板支持以下它,后续就可以接着在上面玩童年经典游戏啦。  我使用的是正点原子的I.MX6U-ALPHA 开发板,

    2024年02月14日
    浏览(51)
  • 【IMX6ULL驱动开发学习】12.Linux SPI驱动实战:DAC驱动设计流程

    基础回顾: 【IMX6ULL驱动开发学习】10.Linux I2C驱动实战:AT24C02驱动设计流程_阿龙还在写代码的博客-CSDN博客 【IMX6ULL驱动开发学习】11.Linux之SPI驱动_阿龙还在写代码的博客-CSDN博客 查看芯片手册,有两种DAC数据格式,12位和16位,这里选用16位数据(2字节)编写驱动。  重点在

    2024年02月11日
    浏览(52)
  • 【IMX6ULL驱动开发学习】22.IMX6ULL开发板读取ADC(以MQ-135为例)

    IMX6ULL一共有两个ADC,每个ADC都有八个通道,但他们共用一个ADC控制器 在imx6ull.dtsi文件中已经帮我们定义好了adc1的节点部分信息 注意 num-channels = 2; ,这个表示指定使用ADC1的两个通道,即通道1和通道2 如果你要使用多个ADC通道,修改这个值即可 配置ADC引脚的 pinctrl ,在自己的

    2024年02月12日
    浏览(57)
  • 【IMX6ULL驱动开发学习】15.IMX6ULL驱动开发问题记录(sleep被kill_fasync打断)

    发现问题的契机: 学习异步通知的时候,自己实现一个功能:按键控制蜂鸣器,同时LED灯在闪烁 结果:LED好像也同时被按键控制了 最后调试结果发现: 应用层的 sleep 被驱动层的 kill_fasync 打断,所以sleep没有执行完就重新进入下一次循环了 修改代码后解决该问题 解决逻辑就

    2024年02月13日
    浏览(49)
  • 【IMX6ULL驱动开发学习】14.Linux驱动开发 - GPIO中断(设备树 + GPIO子系统)

    代码自取 【14.key_tree_pinctrl_gpios_interrupt】: https://gitee.com/chenshao777/imx6-ull_-drivers 主要接口函数: 1. of_gpio_count (获得GPIO的数量) 2. kzalloc (向内核申请空间) 3. of_get_gpio (获取GPIO子系统标号) 4. gpio_to_irq (根据GPIO子系统标号得到软件中断号) 5. request_irq (根据软件中断号

    2024年02月12日
    浏览(49)
  • 嵌入式linux之iMX6ULL驱动开发 | 移远4G模块EC800驱动移植指南

    回顾下移远4G模块移植过程, 还是蛮简单的。一通百通,无论是其他4G模块都是一样的。这里记录下过程,分享给有需要的人。环境使用正点原子的imax6ul开发板,板子默认支持中兴和移远EC20的驱动,这里要移植使用的是移远4G模块EC800。 imax6ul开发板 虚拟机(Ubuntu18.04) 交叉编译

    2024年02月12日
    浏览(56)
  • Linux下的IMX6ULL——开发板的第一个APP和驱动实验(三)

    前言: 万事开头难,如果我们在开发板上开发出第一个应用程序,第一个驱动程序,那么后续的开发就会稍微简单点,下面让我们来进行第一个应用程序和第一驱动程序的开发吧。 目录 一、开发板的第1个APP实验 1.通过Git仓库 2.通过windows上传  二、开发板的第1个驱动实验

    2024年02月08日
    浏览(37)
  • 【IMX6ULL驱动开发学习】03.设置IMX6ULL开发板与虚拟机在同一网段(设置开发板静态IP)

    为什么要设置IMX6ULL与虚拟机通信? 因为要把在虚拟机下编译的文件传到IMX6ULL开发板上运行 设置好同一网段,可以互ping后,可以参考这篇博客,实现开发板与虚拟机的文件互传 IMX6ULL开发板与虚拟机互传文件 一、设置windows有线网卡 二、配置虚拟机双网卡(原本有一个NAT网卡

    2024年02月07日
    浏览(52)
  • 【IMX6ULL驱动开发学习】09.Linux驱动之GPIO中断(附SR501人体红外感应驱动代码)

    Linux驱动的GPIO中断编程主要有以下几个步骤: 1、 通过GPIO号获取 软件中断号 (中断编程不需要设置GPIO输入输出,当然申请GPIO,设置输入也没问题) 参数 含义 gpio GPIO引脚编号 2、 注册 中断处理函数 ,设置中断 触发方式 (上升沿、下降沿等) 参数 含义 irq 软件中断号(通过

    2024年02月11日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包