Linux LED驱动程序框架分析

举报
yd_274589494 发表于 2023/07/27 17:36:16 2023/07/27
【摘要】 @TOC 前言本篇文章我将为大家分析LED驱动程序的框架,驱动程序框架是来自于韦东山老师所讲。 一、如何编写一个字符设备驱动程序1.确定主设备号,也可以让内核分配。每一个设备都有自己的主设备号和次设备号用于区分不同的设备,这里我们暂时不使用次设备号只使用到了主设备号。2.定义自己的file_operations结构体编写字符设备驱动程序时需要提供一个file_operations结构体,这个...

@TOC


前言

本篇文章我将为大家分析LED驱动程序的框架,驱动程序框架是来自于韦东山老师所讲。

一、如何编写一个字符设备驱动程序

1.确定主设备号,也可以让内核分配。
每一个设备都有自己的主设备号和次设备号用于区分不同的设备,这里我们暂时不使用次设备号只使用到了主设备号。
2.定义自己的file_operations结构体
编写字符设备驱动程序时需要提供一个file_operations结构体,这个结构体里面包含了read和write函数,在应用程序中调用read和write函数将会调用到驱动程序中的read和write函数。

static struct file_operations gpio_key_drv = {
	.owner	 = THIS_MODULE,
	.read    = led_drv_read,
	.write   = led_drv_write,
};

3.实现对应的drv_open/drv_read/drv_write等函数,填入file_operations结构体

static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	return 0;
}

static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	return 0;
}

4.把file_operations结构体告诉内核:register_chrdev

	major = register_chrdev(0, "100ask_led", &gpio_key_drv);

5.谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数

static int __init led_drv_init(void)
{
	return 0;
}	

6.有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev

static void __exit led_drv_exit(void)
{

}

7.其他完善:提供设备信息,自动创建设备节点:class_create, device_create

led_class = class_create(THIS_MODULE, "100ask_led_class");
if (IS_ERR(led_class)) {
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	unregister_chrdev(major, "100ask_led");
	return PTR_ERR(led_class);
}

device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led");

二、具体LED驱动程序编写

1.确定LED引脚标号

由原理图可知LED标号为GPIO5_3,由于我们使用的开发板是imx6ull,所以我们这里需要转换一下。
在这里插入图片描述
在这里插入图片描述
imx6ull内部对GPIO进行了分组32个引脚为一组,从gpiochip0开始计数,所以GPIO5_3对应的编号为:32*4+3=131,这样我们就得到了GPIO5_3的编号了。

2.定义描述GPIO的结构体

这里我们定义一个结构体,这个结构体里面含有gpio引脚的各种信息,一个gpio引脚就分别对应一个结构体。

struct gpio_desc{
	int gpio;//gpio编号
	int irq;//中断号
    char *name;//引脚名字
    int key;
	struct timer_list key_timer;
} ;

static struct gpio_desc gpios[2] = {
    {131, 0, "led1", },
    //{132, 0, "led2", },
};

3.入口函数与出口函数

通过class_create和device_create这些辅助信息我们就能创建出一个名为/dev/100ask_led 的设备节点了

/* 入口函数 */
static int __init led_drv_init(void)
{
    int err;
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);//计算共有几个LED灯
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	for (i = 0; i < count; i++)
	{		
		/* get gpio */
		err = gpio_request(gpios[i].gpio, gpios[i].name);//得到GPIO引脚
		if (err) {
			printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
			return -1;
		}		

		/* set gpio as output */
		gpio_direction_output(gpios[i].gpio, 1);//设置GPIO引脚为输出模式
	}

	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_led", &gpio_key_drv);  /* /dev/gpio_desc */

	led_class = class_create(THIS_MODULE, "100ask_led_class");
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_led");
		return PTR_ERR(led_class);
	}

	device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led"); /* /dev/100ask_gpio */
	
	return err;
}

/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 */
static void __exit led_drv_exit(void)
{
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class);
	unregister_chrdev(major, "100ask_led");

	for (i = 0; i < count; i++)
	{
		gpio_free(gpios[i].gpio);
	}
}

4.读写函数

在read和write函数中我们一般都是通过copy_to_user和copy_from_user这两个函数和应用程序进行数据的交互。

/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	unsigned char led_buf[2];
	int err;
	int count = sizeof(gpios)/sizeof(gpios[0]);

	if (size != 2)
		return -EINVAL;

	err = copy_from_user(led_buf, buf, 1);

	if(led_buf[0] > count)
		return -EINVAL;

	led_buf[1] = gpio_get_value(gpios[led_buf[0]].gpio);//读取引脚电平

	err = copy_to_user(buf, led_buf, 2);	
	
	return 2;
}

static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    unsigned char ker_buf[2];
    int err;

    if (size != 2)
        return -EINVAL;

    err = copy_from_user(ker_buf, buf, size);
    
    if (ker_buf[0] >= sizeof(gpios)/sizeof(gpios[0]))
        return -EINVAL;

    gpio_set_value(gpios[ker_buf[0]].gpio, ker_buf[1]);//设置引脚电平
    return 2;    
}

三、测试程序编写

应用程序比较简单大家自己就能看懂,这里就不多赘述。


#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>

static int fd;

/*
 * ./led_test <0|1|2...> on|off
 *
 */
int main(int argc, char **argv)
{
	int fd;
	char buf[2];
	int ret;

	if(argc < 2)
	{
		printf("Usage :%s <0|1|2...> [on|off]\n",argv[0]);
		return -1;
	}

	fd = open("/dev/100ask_led",O_RDWR);
	if(fd < 0)
	{
		printf("open /dev/100ask_led err\n");	
		return -1;
	}

	if(argc == 3)
	{
		buf[0] = strtol(argv[1], NULL, 0);
		
		if(strcmp(argv[2],"on") == 0)
		{
			buf[1] = 0;
		}
		else
		{
			buf[1] = 1;
		}

		write(fd, buf, 2);
	}
	else
	{
		buf[0] = strtol(argv[1], NULL, 0);
		ret = read(fd, buf, 2);
		if (ret == 2)
		{
			printf("led %d status is %s\n", buf[0], buf[1] == 0 ? "on" : "off");
		}		
	}


	
	close(fd);
	
	return 0;
}



四、上机实验

1.挂载网络文件系统,打开内核打印信息
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
echo 7 4 1 7 > /proc/sys/kernel/printk

2.装载驱动程序
在这里插入图片描述
3.查看设备节点和引脚信息
在这里插入图片描述
在这里插入图片描述
4.运行测试程序
在这里插入图片描述

总结

有了百问网韦老师讲的驱动程序框架一切都变得简单起来了。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。