【嵌入式Linux】嵌入式Linux驱动开发基础知识之按键驱动框架

文章目录

  • 前言
  • 1、APP怎么读取按键值
    • 1.1、查询方式
    • 1.2、休眠-唤醒方式
    • 1.3、poll方式
    • 1.3、异步通知方式
    • 1.5、 驱动程序提供能力,不提供策略
  • 2、按键驱动程序框架--查询方式
    • 2.1、通用驱动
    • 2.2、单板个性化驱动
    • 2.3、 APP

前言

韦东山嵌入式Linux驱动开发基础知识学习笔记
文章中大多内容来自韦东山老师的文档,还有部分个人根据自己需求补充的内容
视频教程地址:https://www.bilibili.com/video/BV14f4y1Q7ti

1、APP怎么读取按键值

APP去读按键的方法有4种:
① 查询方式
② 休眠-唤醒方式
③ poll方式
④ 异步通知方式
APP读取按键值,需要有按键驱动程序。
通过这4种方式的学习,我们可以掌握如下知识:
① 驱动的基本技能:中断、休眠、唤醒、poll等机制。
这些基本技能是驱动开发的基础,其他大型驱动复杂的地方是它的框架及设计思想,但是基本技术就这些。

关于这APP中四种方法的介绍在前面的【嵌入式Linux】嵌入式Linux应用开发基础知识之输入系统应用编程中已经介绍,在此不做介绍

1.1、查询方式

【嵌入式Linux】嵌入式Linux驱动开发基础知识之按键驱动框架_第1张图片

▲查询方式

  驱动程序中构造、注册一个file_operations结构体,里面提供有对应的open,read函数。APP调用open时,导致驱动中对应的open函数被调用,在里面配置GPIO为输入引脚。APP调用read时,导致驱动中对应的read函数被调用,它读取寄存器,把引脚状态直接返回给APP。

1.2、休眠-唤醒方式

【嵌入式Linux】嵌入式Linux驱动开发基础知识之按键驱动框架_第2张图片

▲休眠-唤醒方式

  驱动程序中构造、注册一个file_operations结构体,里面提供有对应的open,read函数。
  APP调用open时,导致驱动中对应的open函数被调用,在里面配置GPIO为输入引脚;并且注册GPIO的中断处理函数。
  APP调用read时,导致驱动中对应的read函数被调用,如果有按键数据则直接返回给APP;否则APP在内核态休眠。
  当用户按下按键时,GPIO中断被触发,导致驱动程序之前注册的中断服务程序被执行。它会记录按键数据,并唤醒休眠中的APP。
  APP被唤醒后继续在内核态运行,即继续执行驱动代码,把按键数据返回给APP(的用户空间)。

1.3、poll方式

上面的休眠-唤醒方式有个缺点:如果用户一直没操作按键,那么APP就会永远休眠。
我们可以给APP定个闹钟,这就是poll方式。
【嵌入式Linux】嵌入式Linux驱动开发基础知识之按键驱动框架_第3张图片

▲poll方式

  驱动程序中构造、注册一个file_operations结构体,里面提供有对应的open,read,poll函数。
  APP调用open时,导致驱动中对应的open函数被调用,在里面配置GPIO为输入引脚;并且注册GPIO的中断处理函数。
  APP调用poll或select函数,意图是“查询”是否有数据,这2个函数都可以指定一个超时时间,即在这段时间内没有数据的话就返回错误。这会导致驱动中对应的poll函数被调用,如果有按键数据则直接返回给APP;否则APP在内核态休眠一段时间。
  当用户按下按键时,GPIO中断被触发,导致驱动程序之前注册的中断服务程序被执行。它会记录按键数据,并唤醒休眠中的APP。
  如果用户没按下按键,但是超时时间到了,内核也会唤醒APP。
  所以APP被唤醒有2种原因:用户操作了按键,超时。被唤醒的APP在内核态继续运行,即继续执行驱动代码,把“状态”返回给APP(的用户空间)。
  APP得到poll/select函数的返回结果后,如果确认是有数据的,则再调用read函数,这会导致驱动中的read函数被调用,这时驱动程序中含有数据,会直接返回数据。

1.3、异步通知方式

【嵌入式Linux】嵌入式Linux驱动开发基础知识之按键驱动框架_第4张图片

▲异步通知方式

  异步通知的实现原理是:内核给APP发信号。信号有很多种,这里发的是SIGIO。
  驱动程序中构造、注册一个file_operations结构体,里面提供有对应的open,read,fasync函数。
  APP调用open时,导致驱动中对应的open函数被调用,在里面配置GPIO为输入引脚;并且注册GPIO的中断处理函数。
  APP给信号SIGIO注册自己的处理函数:my_signal_fun。
  APP调用fcntl函数,把驱动程序的flag改为FASYNC,这会导致驱动程序的fasync函数被调用,它只是简单记录进程PID。
  当用户按下按键时,GPIO中断被触发,导致驱动程序之前注册的中断服务程序被执行。它会记录按键数据,然后给进程PID发送SIGIO信号。
  APP收到信号后会被打断,先执行信号处理函数:在信号处理函数中可以去调用read函数读取按键值。
  信号处理函数返回后,APP会继续执行原先被打断的代码。

1.5、 驱动程序提供能力,不提供策略

  我们的驱动程序可以实现上述4种提供按键的方法,但是驱动程序不应该限制APP使用哪种方法。
  这就是驱动设计的一个原理:提供能力,不提供策略。就是说,你想用哪种方法都行,驱动程序都可以提供;但是驱动程序不能限制你使用哪种方法。

2、按键驱动程序框架–查询方式

【嵌入式Linux】嵌入式Linux驱动开发基础知识之按键驱动框架_第5张图片

▲GPIO on STM32MP157PRO

2.1、通用驱动

button_drv.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "button_drv.h"


static int major = 0;

static struct button_operations *p_button_opr;
static struct class *button_class;

static int button_open (struct inode *inode, struct file *file)
{
	int minor = iminor(inode);
	p_button_opr->init(minor);
	return 0;
}

static ssize_t button_read (struct file *file, char __user *buf, size_t size, loff_t *off)
{
	unsigned int minor = iminor(file_inode(file));
	char level;
	int err;
	
	level = p_button_opr->read(minor);
	err = copy_to_user(buf, &level, 1);
	return 1;
}


static struct file_operations button_fops = {
	.open = button_open,
	.read = button_read,
};

void register_button_operations(struct button_operations *opr)
{
	int i;

	p_button_opr = opr;
	for (i = 0; i < opr->count; i++)
	{
		device_create(button_class, NULL, MKDEV(major, i), NULL, "100ask_button%d", i);
	}
}

void unregister_button_operations(void)
{
	int i;

	for (i = 0; i < p_button_opr->count; i++)
	{
		device_destroy(button_class, MKDEV(major, i));
	}
}


EXPORT_SYMBOL(register_button_operations);
EXPORT_SYMBOL(unregister_button_operations);


int __init button_init(void)
{
	major = register_chrdev(0, "100ask_button", &button_fops);

	button_class = class_create(THIS_MODULE, "100ask_button");
	if (IS_ERR(button_class))
		return -1;
	
	return 0;
}

void __exit button_exit(void)
{
	class_destroy(button_class);
	unregister_chrdev(major, "100ask_button");
}

module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");

2.2、单板个性化驱动

board_100ask_stm32mp157.c

#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "button_drv.h"


struct stm32mp157_gpio {
  volatile unsigned int MODER;    /*!< GPIO port mode register,               Address offset: 0x00      */
  volatile unsigned int OTYPER;   /*!< GPIO port output type register,        Address offset: 0x04      */
  volatile unsigned int OSPEEDR;  /*!< GPIO port output speed register,       Address offset: 0x08      */
  volatile unsigned int PUPDR;    /*!< GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
  volatile unsigned int IDR;      /*!< GPIO port input data register,         Address offset: 0x10      */
  volatile unsigned int ODR;      /*!< GPIO port output data register,        Address offset: 0x14      */
  volatile unsigned int BSRR;     /*!< GPIO port bit set/reset,               Address offset: 0x18      */
  volatile unsigned int LCKR;     /*!< GPIO port configuration lock register, Address offset: 0x1C      */
  volatile unsigned int AFR[2];   /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
} ;


/* RCC_PLL4CR */
static volatile unsigned int *RCC_PLL4CR; 

/* RCC_MP_AHB4ENSETR */
static volatile unsigned int *RCC_MP_AHB4ENSETR; 

static struct stm32mp157_gpio *gpiog; /* KEY1: PG3, KEY2: PG2 */

static void board_stm32mp157_button_init (int which) /* 初始化button, which-哪个button */      
{
    if (!RCC_PLL4CR)
    {
        RCC_PLL4CR = ioremap(0x50000000 + 0x894, 4);
        RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28, 4);

        gpiog = ioremap(0x50008000, sizeof(struct stm32mp157_gpio));
    }

    if (which == 0)
    {
        /* 1. enable PLL4 
         * CG15, b[31:30] = 0b11
         */
		*RCC_PLL4CR |= (1<<0);
		while((*RCC_PLL4CR & (1<<1)) == 0);

		/* 2. enable GPIOG */
		*RCC_MP_AHB4ENSETR |= (1<<6);
		
		/* 3. 设置PG3为GPIO模式, 输入模式 
		 */
		gpiog->MODER &= ~(3<<6);
        
    }
    else if(which == 1)
    {
        /* 1. enable PLL4 
         * CG15, b[31:30] = 0b11
         */
		*RCC_PLL4CR |= (1<<0);
		while((*RCC_PLL4CR & (1<<1)) == 0);

		/* 2. enable GPIOG */
		*RCC_MP_AHB4ENSETR |= (1<<6);
		
		/* 3. 设置PG2为GPIO模式, 输入模式 
		 */
		gpiog->MODER &= ~(3<<4);
    }
    
}

static int board_stm32mp157_button_read (int which) /* 读button, which-哪个 */
{
    //printk("%s %s line %d, button %d, 0x%x\n", __FILE__, __FUNCTION__, __LINE__, which, *GPIO1_DATAIN);
    if (which == 0)
        return (gpiog->IDR & (1<<3)) ? 1 : 0;
    else
        return (gpiog->IDR & (1<<2)) ? 1 : 0;
}
    
static struct button_operations my_buttons_ops = {
    .count = 2,
    .init = board_stm32mp157_button_init,
    .read = board_stm32mp157_button_read,
};

int __init board_stm32mp157_button_drv_init(void)
{
    register_button_operations(&my_buttons_ops);
    return 0;
}

void __exit board_stm32mp157_button_drv_exit(void)
{
    unregister_button_operations();
}

module_init(board_stm32mp157_button_drv_init);
module_exit(board_stm32mp157_button_drv_exit);

MODULE_LICENSE("GPL");

button_drv.c

#ifndef _BUTTON_DRV_H
#define _BUTTON_DRV_H

struct button_operations {
	int count;
	void (*init) (int which);
	int (*read) (int which);
};

void register_button_operations(struct button_operations *opr);
void unregister_button_operations(void);

#endif

2.3、 APP

button_test.c


#include 
#include 
#include 
#include 
#include 
#include 

/*
 * ./button_test /dev/100ask_button0
 *
 */
int main(int argc, char **argv)
{
	int fd;
	char val;
	
	/* 1. 判断参数 */
	if (argc != 2) 
	{
		printf("Usage: %s \n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	/* 3. 写文件 */
	read(fd, &val, 1);
	printf("get button : %d\n", val);
	
	close(fd);
	
	return 0;
}

你可能感兴趣的:(#,嵌入式Linux,linux,STM32MP157,驱动开发)