从LED驱动程序中看简单驱动编程思想与框架

LED驱动程序中看简单驱动编程思想框架

写在前面:

上次写过一个不牵涉硬件的helloworld简单驱动程序,今天来一个涉及硬件的相对来说比较简单的LED驱动,并对驱动的编程思想,简述自己的看法与见解。为什么选LED呢?因为我是点灯工程师啊!不点灯点什么。

为什么要有驱动程序?

首先我们要明白,驱动程序是针对那些可以跑操作系统的设备而言的。

像我在学校帮导师做的一些小项目,那里用得到驱动程序,直接一个程序从头执行到位。这就叫单片机开发,走的是简单粗暴的路子,能运行就行。

一旦我们要针对那些有着很复杂外设丰富功能要求以及极大的稳定性的设备时,就必不可少的要用到驱动程序。比如,手机、平板、电脑。

防止用户或者应用开发者,直接对硬件进行操作,造成不必要的麻烦。

同时,也提高了复制性,只要硬件相同,做好驱动之后,不管用户需求怎么变,只需要修改应用程序即可,避免重复性劳动。

所以说,驱动程序即是桥梁,又是保安,同时还是保姆。

驱动程序的分层思想

上面提到,驱动是硬件与应用之间的桥梁,所以最关键的就是沟通,要两面俱到。

呈上,面对用户层,要提供足够的接口(API)让其调用,也就是函数。

启下,面对硬件,要以点击面,直接对硬件的寄存器进行操作。

看下面这个图:
image.png

也就是说,我们需要对应用层APP最少提供drv_open、drv_read、drv_write三个函数接口,在这三个接口函数中有着对硬件寄存器的直接操作

当然应用层是没法直接调用这三个函数的,要进行封装。

驱动程序及其相关细节

1.file_operations结构体

static struct file_operations led_fops = {
	.owner		= THIS_MODULE,
	.write		= led_write,
	.open		= led_open,
	};

把所需函数接口封装进一个结构体中。

2.led_write、led_open函数

static ssize_t led_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
	char val;
	/* copy_from_user : get date from app  */
	copy_from_user(&val, buf, 1);
	/* to set gpio register: out 1/0 */
	if (val)
	{
		/* set gpio to let led on */
		*GPIO3_DR &= ~(1<<3);
	}
	else
	{
		/* set gpio to let led off */
		*GPIO3_DR |= (1<<3);
	}
}
			 
static int led_open(struct inode *inode, struct file *filp)
{
	/*enable gpio 
	 *configure pin as gpio
	 *configer gpio as output 
	 */
	 
	//使能gpio
	*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;//清空寄存器值

	//复用为gpio
	*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x5;//设置101

	//配置为输出
	*GPIO3_DR |= (1<<3);//寄存器第三位设置为1
	
	return 0;
}

直接在这两个函数中,对硬件寄存器进行操作。

3.提供入口与出口函数

static int __init led_init(void)
{
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	major = register_chrdev(0, "100ask_led", &led_fops);

	/* ioremap 地址映射*/

	//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 地址:
	IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);

	//GPIO3_GDIR 地址:
	GPIO3_GDIR = ioremap(0x020A4004, 4);

	//GPIO3_DR 地址:
	GPIO3_DR = ioremap(0x020A4000, 4);
	
	led_class = class_create(THIS_MODULE, "myled");
	device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled");/* /dev/myled */
	return 0;
}
/*出口函数*/
static void __exit led_exit(void)
{
	iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
	iounmap(GPIO3_GDIR);	
	iounmap(GPIO3_DR);
	
	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class);
	unregister_chrdev(major, "100ask_led");
}

这里有一个特别关键的步骤:地址映射 ioremap

大家要知道,硬件的寄存器名字,实际上是不存在的,是我们给它取的名字,所以我们要把寄存器地址与寄存器名字对应起来。

当然别忘了,在出口函数那里,把它释放出来iounmap。

对于设备节点的创立、还有led_class、以及进出口函数的声明、还有协议,前边的博文说过,这里就不提了。

5.附上完整驱动程序和驱动测试程序

驱动程序

 
#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 <linux/device.h>
#include <asm/io.h>

static int major;
static struct class *led_class;

/* register */

//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 地址:
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;

//GPIO3_GDIR 地址:
static volatile unsigned int *GPIO3_GDIR;

//GPIO3_DR 地址:
static volatile unsigned int *GPIO3_DR;


static ssize_t led_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
	char val;
	/* copy_from_user : get date from app  */
	copy_from_user(&val, buf, 1);
	/* to set gpio register: out 1/0 */
	if (val)
	{
		/* set gpio to let led on */
		*GPIO3_DR &= ~(1<<3);
	}
	else
	{
		/* set gpio to let led off */
		*GPIO3_DR |= (1<<3);
	}
}
			 
static int led_open(struct inode *inode, struct file *filp)
{
	/*enable gpio 
	 *configure pin as gpio
	 *configer gpio as output 
	 */
	 
	//使能gpio
	*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;//清空寄存器值

	//复用为gpio
	*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x5;//设置101

	//配置为输出
	*GPIO3_DR |= (1<<3);//寄存器第三位设置为1
	
	return 0;
}

static struct file_operations led_fops = {
	.owner		= THIS_MODULE,
	.write		= led_write,
	.open		= led_open,
	};

/*入口函数*/
static int __init led_init(void)
{
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	major = register_chrdev(0, "100ask_led", &led_fops);

	/* ioremap 地址映射*/

	//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 地址:
	IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);

	//GPIO3_GDIR 地址:
	GPIO3_GDIR = ioremap(0x020A4004, 4);

	//GPIO3_DR 地址:
	GPIO3_DR = ioremap(0x020A4000, 4);
	
	led_class = class_create(THIS_MODULE, "myled");
	device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled");/* /dev/myled */
	return 0;
}
/*出口函数*/
static void __exit led_exit(void)
{
	iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
	iounmap(GPIO3_GDIR);	
	iounmap(GPIO3_DR);
	
	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class);
	unregister_chrdev(major, "100ask_led");
}
/*声明那些事入口函数与出口函数,以及GPL协议*/
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");


驱动测试程序

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <unistd.h>
#include <string.h>



/*
	使用方法:
	led_test /dev/myled on
	led_test /dev/myled off
*/
int main(int argc, char **argv)
{
	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);
	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);
	return 0;
}

最后

其实这篇博文,最重要的目的和要表达的思想,就是分层。这也是驱动程序存在的意义,而且切记要学会借鉴Linux内核那些经过考验的优秀的驱动程序。

最后,祝大家早安、午安、晚安!给导师搬砖去啦!拜拜

posted @ 2021-12-03 21:56  iron2222  阅读(175)  评论(0编辑  收藏  举报