【韦东山驱动入门实验班】通用驱动框架1之LED驱动

在学习完通用驱动框架1之后,下面就是使用通用驱动框架去写各种硬件的驱动程序了。
小插曲:就是本来已经买了通用模块,不过收到货发现多发了个stm32mp157的开发板,我就联系客服退回开发板,然后准备使用转接板的时候又发现转接板发成了6ull的,最终结果就是估计还有两天我才能完成后面的实验。

1. LED驱动程序的简单分析

首先,分析一下我们假设想使用GPIO控制LED灯,需要对GPIO进行哪些相关设置。GPIO和LED的硬件连接关系如下图所示:
Alt

  1. 驱动程序的入口函数中:请求GPIO引脚,并将GPIO设置为输出引脚,默认为高电平(灯灭);
  2. 在应用层调用write函数,使驱动程序中的write函数被调用。驱动程序中,根据用户传递数据,决定并控制GPIO,最终控制LED的开和关;
  3. 在应用层调用read函数,使驱动程序的read函数被调用。根据用户传递的数据,读取GPIO的引脚电平,最终获得LED的状态是开还是关。

2. LED驱动代码的实现

2.1 入口函数 & 出口函数

入口函数中 ,主要完成初始化GPIO设置,将GPIO设置为输出引脚,采用的是GPIO子系统提供的函数接口。

static int __init gpio_drv_init(void)
{
    int err;
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* set pin as output*/
	for (i = 0; i < count; i++)
	{	
        err = gpio_request(gpios[i].gpio, gpios[i].name);
		if (err) {
			printk("can not request gpio %s %d\n", gpios[i].name, gpios[i].gpio);
			return -ENODEV;
		}
		gpio_direction_output(gpios[i].gpio, 1);//默认输出为高电平,灯关
	}

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

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

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

出口函数,完成入口函数的反操作。比如在入口函数中创建类,就需要在出口函数中销毁类。

static void __exit gpio_drv_exit(void)
{
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

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

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

2.2 驱动程序之read()函数实现

假设用户通过buf传递数据,那么 buf[0]用来表示需要读哪个LED灯,比如传递0,就代表led0

static ssize_t gpio_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	char temp_buf[2];
	int err;
	int count = sizeof(gpios) / sizeof(gpios[0]);

	if (size != 2)
		return -EINVAL;
	
	/*接收app传递的数据,读哪个引脚*/
	err = copy_from_user(temp_buf, buf, 1);

	/*
	* temp_buf[0] = 0 ---- led0
	* temp_buf[0] = 1 ---- led1
	*/
	if (temp_buf[0] >= count)
		return -EINVAL;
	/* 获得引脚状态 */
	temp_buf[1] = gpio_get_value(gpios[temp_buf[0]].gpio);
	/* 将temp_buf中的数据传递给app */
	err = copy_to_user(buf, temp_buf, 2);
	return 2;
}

2.3 驱动程序之write()函数实现

假设用户通过buf传递数据,其中:

  • buf[0]用来表示控制哪个LED灯,比如传递0,就代表led0
  • buf[1]就用来表示LED的状态,比如传递0,就表示灯开,传递1就表示灯灭。(这是根据原理图来的,低电平LED就打开,高电平LED就关闭)
static ssize_t gpio_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;
	/* 接收app传递的数据,控制哪个LED的开关状态 */
	err = copy_from_user(ker_buf, buf, size);
	/* 判断LED编号是否超出控制范围 */
	if (ker_buf[0] >= sizeof(gpios)/sizeof(gpios[0]))
		return -EINVAL;
	/* 设置GPIO值 */
	gpio_set_value(gpios[ker_buf[0]].gpio, ker_buf[1]);
	return 2;
}

3. 上机测试

3.1 确定引脚,计算引脚编号

Alt
原理图分析结果:

  • LED2 的引脚为 PA10,引脚编号为:0+10=10;
  • LED3 的引脚为 PG8, 引脚编号为:96+8=104;
  • LED 外接 VDD_3V3 电压,当引脚输出低电平灯亮,当引脚输出高电平灯灭;
    Alt

【公式计算】
细心的同学可能发现了在软件层面上,GPIO 组和 GPIO 编号存在一定的规律。比如 GPIOA 对应 0~15 ,GPIOB 对应 16 ~ 31…并以此类推。详细地, PA1 的编号就等于 1,PB1 的编号 16+1=17;

最后,可以总结为一个公式:GPIO 编号 = (X -1) * <32/16> + N

  • X可以解释为第几组,比如 GPIOA 可以解释为第一组,那么 GPIOB 就是第二组;
  • <32/16>可以解释为有的一组有 32位,而有的就只有 16 位;
  • N 表示一组里的第 (N+1)个引脚,比如 PA1 就是第二个引脚,所以 N 就是 1

那么,形如 LED3 的引脚 PG8,就可以通过下列公式获得:(7-1)*16+8=6*16+8=104

3.2 测试程序编写

#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>
#include <stdlib.h>

static int fd;

/*
 * ./led_test <0|1|2> on
 * ./led_test <0|1|2> off
 * ./led_test <0|1|2>
 */
int main(int argc, char **argv)
{
	int ret;
	char buf[2];
	/* 1. 判断参数 */
	if (argc < 2) 
	{
		printf("Usage: %s <0|1|2|...> [on|off]\n", argv[0]);
		return -1;
	}
	
	/* 2. 打开文件 */
	fd = open("/dev/100ask_led", O_RDWR);
	if (fd == -1)
	{
		printf("can not open file /dev/100ask_led\n");
		return -1;
	}
	
    /* 3.根据参数设置led */
    if(argc == 3)
    {
        /* write */
        buf[0] = strtol(argv[1], NULL, 0);
        if(strcmp(argv[2], "on") == 0)
        {
            buf[1] = 0;
        }
        else
        {
            buf[1] = 1;
        }
        ret = write(fd, buf, 2);
        /* 设置成功 */
        if(ret == 2)
        {
            printf("led %d status is %s\n", buf[0], buf[1] == 0 ? "on" : "off");
        }
    }
    else
    {
        /* read */
        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;
}

3.3 插入LED驱动模块并测试

Alt

4. 小结

  1. 首先,编写驱动程序之前就要确定好驱动程序的编写逻辑,我们要在驱动程序中做什么;
  2. 对于,类似GPIO子系统这种函数接口,内核代码中已经有很多现成的例子,要学会“抄”代码;
  3. 如果GPIO被占用了,一定要去查找原因,可能是内核其他驱动程序占用了。我们可以通过修改设备树等方法,解除占用。

附:完成LED驱动完成代码

#include <linux/module.h>
#include <linux/poll.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/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>

struct gpio_desc{
	int gpio;
    char *name;
};

static struct gpio_desc gpios[2] = {
    {10, "100ask_led0"},
    {104, "100ask_led1"},
};

/* 主设备号                                                                 */
static int major = 0;
static struct class *gpio_class;

/* 实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t gpio_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	char temp_buf[2];
	int err;
	int count = sizeof(gpios) / sizeof(gpios[0]);

	if (size != 2)
		return -EINVAL;
	
	/*接收app传递的数据,读哪个引脚*/
	err = copy_from_user(temp_buf, buf, 1);

	/*
	* temp_buf[0] = 0 ---- led0
	* temp_buf[1] = 1 ---- led1
	*/
	if (temp_buf[0] >= count) {
		return -EINVAL;
	}
	
	temp_buf[1] = gpio_get_value(gpios[temp_buf[0]].gpio);
	err = copy_to_user(buf, temp_buf, 2);
	return 2;
}

static ssize_t gpio_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;
}

/* 定义自己的file_operations结构体                                              */
static struct file_operations gpio_key_drv = {
	.owner	 = THIS_MODULE,
	.read    = gpio_drv_read,
	.write   = gpio_drv_write,
};

/* 在入口函数 */
static int __init gpio_drv_init(void)
{
    int err;
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	for (i = 0; i < count; i++)
	{	
		/* set pin as output*/
        err = gpio_request(gpios[i].gpio, gpios[i].name);
		if (err) {
			printk("can not request gpio %s %d\n", gpios[i].name, gpios[i].gpio);
			return -ENODEV;
		}
		gpio_direction_output(gpios[i].gpio, 1);//默认输出为高电平,灯关
	}

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

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

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

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

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

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

module_init(gpio_drv_init);
module_exit(gpio_drv_exit);
MODULE_LICENSE("GPL");

以上是本文全部内容,如有问题欢迎评论区留言!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cifeng79

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值