嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十五)最简单的LED驱动程序

本文档详细介绍了如何根据芯片手册编写LED驱动程序,从查看原理图到配置GPIO,再到设置输出电平。同时,提供了最简单的LED驱动程序代码示例,包括字符设备驱动程序框架,以及LED的打开和写入操作。此外,还包含了测试程序的编写和编译流程,以及在开发板上的加载和测试方法。
摘要由CSDN通过智能技术生成


怎么根据芯片手册去写一个点亮led驱动程序。

1.写LED驱动程序

1.1第一步 先看原理图

在这里插入图片描述

1.2第二步 再看芯片手册:使能GPIO

在这里插入图片描述

1.3 第三步 设置引脚是GPIO功能

在这里插入图片描述

1.4 第四步 设置引脚是输出

在这里插入图片描述

1.5 第五步 设置输出电平

在这里插入图片描述

2.最简单的LED驱动程序

后面的LED驱动程序为了容易扩展,引入了很多数据结构。对C语言的要求有点高,所以我们基于Hello驱动程序先写出最简单的LED驱动程序。

2A.1 LED操作方法_基于IMX6ULL

视频中的文档,放在GIT仓库中:

01_all_series_quickstart\
05_嵌入式Linux驱动开发基础知识\doc_pic\pic\6A.最简单的LED驱动程序\
    03_IMX6ULL的LED操作方法.pptx

2A.2 最简单的LED驱动程序编程_基于IMX6ULL

视频中的源码文档,放在GIT仓库中:

01_all_series_quickstart\
05_嵌入式Linux驱动开发基础知识\source\
    02_led_drv\
00_led_simple\imx6ull

2A.2.1 字符设备驱动程序框架

字符设备驱动程序的框架:

在这里插入图片描述

编写驱动程序的套路:
① 确定主设备号,也可以让内核分配
② 定义自己的file_operations结构体
③实现对应的drv_open/drv_read/drv_write等函数,填入file_operations结构体
④把file_operations结构体告诉内核:register_chrdev
⑤谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
⑥有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
⑦其他完善:提供设备信息,自动创建设备节点:class_create, device_create

驱动怎么操作硬件?通过ioremap映射寄存器的物理地址得到虚拟地址,读写虚拟地址。
驱动怎么和APP传输数据?通过copy_to_usercopy_from_user这2个函数。

下面就是写驱动程序的模板!!

********************************************************
//这个是驱动程序的框架,以后可以直接套用,在上面添加和修改
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>

#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/uaccess.h>

#include <asm/io.h>

/*定义主设备号*/
static struct major; 
/*定义led class类*/
static struct class *led_class;


/*结构体中led_open函数*/
static int led_open(struct inode *inode, struct file *file) 
{

	/*
	*enable gpio
	*configure pin as gpio
	*configure gpio as output
	*/

	return 0}

/*结构体中led_write函数*/	
static ssize_t led_write(struct file *file ,const char __user * buf, size_t count, loff_t *ppos)
{
	char val;//val来自内核定义
	/*copy_from_user:get data from app,会涉及到寄存器,那么涉及到物理层,需要用到虚拟地址*/
	copy_from_user(&val, buf, 1);//把app(buf)中的数据拷贝至内核空间(val),拷贝一个字节。
	
	/*to set gpio register :out 1/0,会涉及到寄存器,那么涉及到物理层,需要用到虚拟地址*/
	if(val)
	{
		/*set gpio to let led on */	
	}
	else
	{
		/*set gpio to let led off */	
	}
	return 0;
}
/*定义结构体*/
static 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, "100ask_led", &led_fops);//unsigned int major;设置为0.const char * name设置为“100ask_led”
	/*ioremap*/
	led_class=class_create(THIS_MODULE, "myled");//这个变量的结构体名字被称为class,给这个模块创造一个名字为class类的结构。
	device_create(led_class, NULL, MKDEV(major, 0),NULL, "myled");/**设备就会给我们创建一个/dev/myled设备结点。*/
	
	return 0;	
}

/*出口函数*/
static void  led_exit(void)
{
	device_destroy(led_class, NULL);
	class_destroy(led_class);
	unregister_chrdev(major,"100ask_led" )

}

/*完善入口函数*/
module_init(led_init);
/*完善出口函数*/
module_exit(led_init);
/*设置为GPL协议*/
MODULE_LICENSE("GPL");//指定GPL协议

2A.2.2 实现什么功能

先编写驱动程序:
实现led_open函数,在里面初始化LED引脚。 实现led_write函数,在里面根据APP传来的值控制LED



#include <linux/kernel.h>


#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>

#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
#include <asm/io.h>


/*定义主设备号*/
static struct major; 
/*定义led class类*/
static struct class *led_class;
/*registers*/
// 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;


/*结构体中led_open函数*/
static int led_open(struct inode *inode, struct file *file) 
{

	/*
	*enable gpio
	*configure gpio5_3 as gpio
	*configure gpio5_3 as output
	*/
	//把引脚配置成gpio
	*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;
	*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= ~0x05;
	//把引脚配置成输出
	*GPIO5_GDIR |=(1<<3);
	return 0}


/*结构体中led_write函数*/	
static ssize_t led_write(struct file *file ,const char __user * buf, size_t count, loff_t *ppos)
{
	char val;//val来自内核定义
	/*copy_from_user:get data from app,会涉及到寄存器,那么涉及到物理层,需要用到虚拟地址*/
	copy_from_user(&val, buf, 1);//把app(buf)中的数据拷贝至内核空间(val),拷贝一个字节。
	
	/*to set gpio register :out 1/0,会涉及到寄存器,那么涉及到物理层,需要用到虚拟地址*/
	if(val)
	{
		/*set gpio to let led on */	
		*GPIO5_DR &=~(1<<3);
	}
	else
	{
		/*set gpio to let led off */
		*GPIO5_DR &=(1<<3);
	}
	return 0;
}


/*定义结构体*/
static 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, "100ask_led", &led_fops);//unsigned int major;设置为0.const char * name设置为“100ask_led”

	/*ioremap*/
	// IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3地址:0x02290000 + 0x14
	IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3=ioremap (0x02290000 + 0x14, 4);
	// GPIO5_GDIR地址:0x020AC004
	GPIO5_GDIR=ioremap (0x020AC004 + 0x14, 4);
	//GPIO5_DR地址:0x020AC000
	GPIO5_DR=ioremap (0x020AC000 + 0x14, 4);


	static inline void __iomem * ioremap (unsigned long offset, unsigned long size)
	{
		return __ioremap(offset, size, 0);
	}




	
	led_class=class_create(THIS_MODULE, "myled");//这个变量的结构体名字被称为class,给这个模块创造一个名字为class类的结构。
	device_create(led_class, NULL, MKDEV(major, 0),NULL, "myled");/**设备就会给我们创建一个/dev/myled设备结点。*/
	
	return 0;	
}



/*出口函数*/
static void  led_exit(void)
{
	device_destroy(led_class, NULL)
	class_destroy(led_class);
	unregister_chrdev(major,"100ask_led" )

}

/*完善入口函数*/
module_init(led_init);
/*完善出口函数*/
module_exit(led_init);
/*设置为GPL协议*/
MODULE_LICENSE("GPL");//指定GPL协议

再编写测试程序。

//ledtest /dev/myled on
//ledtest /dev/myled off
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main(int argc,char **argc)
{
	int fd;
	char status = 0;
	if(argc != 3)
		{
			printf("Usage: %s <dev> <on|off>\n",argv[0]);
			printf("	eg: %s /dev/myled on\n",argv[0]);
			printf("    eg: %s /dev/myled off\n",argv[0]);
			return -1;
			
		}
	
	//open 
	fd=open(argv[1],O_RDWR);//open函数怎么使用呢,打开虚拟机远程登录。man 2 open, 看参数和头文件 
	if(fd<0)
		{
		printf("can not open %s\n ",argv[0]);
		return -1;
		}

	
	// write
	if(strcmp(argv[2],"on")==0)
		{
			status =1;
		}
	write(fd,&status,1);//把status写入fd,写一个字节
	return 0;
	//write
}

最后编写makefile

KERN_DIR=/home/book/100ask_imx6ull-sdk/linux-4.9.88

all:
	make -C $(KERN_DIR) M= `pwd` modules
	$(CROSS_COMPILE)gcc -o ledtest ledtest.c

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean 
	rm -rf modules.order
	rm -f hello_drv_test

obj -m +=led_drv.o

2A.2.3 上机实验

编译过程实践看韦东山老师的开发手册

先设置工具链,参考第2篇第八章的内容。 再编译程序,把代码上传代服务器后执行make命令。没有问题后。
接着在开发板上挂载NFS,参考第2篇第六章的内容。 最后在开发板上加载驱动程序,执行测试程序,如下: echo "7 4 1 7" > /proc/sys/kernel/printk // 打开内核的打印信息,有些板子默认打开了

insmod /mnt/led_drv.ko
/mnt/ledtest /dev/myled on                      // 点灯
/mnt/ledtest /dev/myled off                     // 关灯
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值