百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 优雅编程 > 正文

linux按键驱动设计(V3S开发板)(键盘在linux驱动层属于什么设备)

sinye56 2024-10-30 03:56 9 浏览 0 评论

1.前言

本文描述了基于全志V3S开发板的按键驱动程序和测试应用程序的设计流程。


本次设计系统内核是基于linux3.4。

2.设计流程概述

本次设计的步骤是:
步骤一、编写一个driver_button.c的驱动程序。

步骤二、编写makefile文件,编译得到ko。

步骤三、编写一个app_button.c的测试应用程序。

步骤四、在V3S开发板中安装demo_driver驱动程序,并测试app_button应用程序。

3.编写驱动程序

3.1硬件电路

V3S开发板按键的硬件电路原理图如下:


根据原理图可知不同的按键按下对应不同的电压值,使用ADC采样LRADC0处的电压就可以识别是否有按键被按下,例如按键KEY3按下时LRADC0处的电压为0.6V。

我们查看V3S芯片数据手册,看看如何操作V3S芯片LRADC,V3S芯片关于LRADC的关键信息如下(详细数据请参考数据手册):



LRADC的控制寄存器信息如下(LRADC寄存器详细数据请参考数据手册):


我们只需要把LRADC配置成连续采样模式即可,具体配置如下:

LRADC_CTRL = 0x00C00041
LRADC_INTC = 0x00000013

3.2设计要点

本次按键驱动设计使用了以下两个知识点:

1、内核定时器使用
2、阻塞操作 (等待队列)

我们先学习一下这两个知识点。

3.2.1内核定时器

Linux内核定时器是一种基于时间点的计时方式,它以当前时刻为时间起点,以未来的某一时刻为终点,这种策略类似于闹钟。


Linux内核定时器的精度不高,不能作为高精度定时器使用。内核定时器并不是周期性运行,如果要想实现周期性的定时,就需要在定时中断处理函数中重新开启定时器。

使用Linux内核定时器需要增加如下头文件:

#include <linux/timer.h>  		
#include <linux/jiffies.h>

Linux内核定时器使用分为以下4个步骤:

1、声明定义一个定时器

struct timer_list timer; 

2、初始化定时器

/* 初始化定时器 */
init_timer(&timer); 
/* 设置定时处理函数 */
timer.function = time_callback; 
/* 设置定时时间 */
timer.expires=jiffies + msecs_to_jiffies(100); 

3、声明定义定时器中断回调函数
在回调函数中再次设置定时时间,实现周期定时

void time_callback(unsigned long arg)
{
	/* 设置定时器 */	
	mod_timer(&timer, jiffies + msecs_to_jiffies(TIME_NUM));
}

4、启动定时器

add_timer(&timer); 

通过以上的4个步骤,我们就可以正常使用Linux内核定时器了。

3.2.2阻塞操作

相信大家去吃一些美味小食时都排队的经历吧,这种漫长的等待经历是不是还历历在目?除了在队伍里傻站着和刷刷手机,啥也干不了。


但是有些商场就有一种很好的等待机制:
无线提示器。客户点餐后就可以在一定范围内自由活动,当你点的餐做好了,无线提示器会提示你去取餐。这种方式可以极大的减轻客户站立排队的低效和痛苦。
回到上面排队的例子,
第一种站立式排队模式,当你处在这种等待模式下时你必须一直保存这种排队等待的状态,直到你买到你需要的商品。这种等待方式属于非阻塞式等待。
第二种无线提示器模式,当你完成商品付款后,就可以在一定范围内自由活动,直到收到无线提示器会提示你。这种等待方式属于阻塞式等待。
非常明显在大多数情况下阻塞式等待会更加高效。

我们使用linux等待队列实现阻塞操作,当事件未准备好时任务进入挂起状态,当事件准备好时唤醒任务。
使用等待队列需要包含如下头文件:

#include <linux/wait.h> 		
#include <linux/sched.h>

linux等待队列操作分为以下4个步骤:
1、定义等待队列和等待标志

wait_queue_head_t button_wait; 
bool  key_ready = false;

2、初始化等待队列

init_waitqueue_head(&button_wait);

3、进入等待

wait_event(button_wait, key_ready);

4、唤醒任务
需要在另外一处可以执行到的代码出执行唤醒代码

key_ready = true;
wake_up(&button_wait);

通过以上的4个步骤,我们就可以正常使用Linux等待队列进行阻塞操作了。

3.3按键驱动实现

描本次按键驱动程序策略如下:
1、应用程序通过驱动程序的read函数读取按键值,当检测有按键被按键时read函数返回按键值,当无按键按下时read函数使用等待队列让应用程序挂起(不继续运行)。

2、驱动程序中设置一个周期为10ms内核定时器,在定时器中断回调函数中检测按键是否被按下,并做去抖操作。如果在定时器中断回调函数中确定有按键被按下,此时唤醒因为等待按键被挂起的应用程序。

策略框图如下:

3.4驱动代码

编写一个demo_driver.c的驱动程序,驱动程序源码如下(代码不再作解释说明,可以参数代码注释):

/**
*********************************************************************************************************
*                                        		driver_button
*                                      (c) Copyright 2021-2031
*                                         All Rights Reserved
*
* @File    : 
* @By      : liwei
* @Version : V0.01
* 
*********************************************************************************************************
**/

/**********************************************************************************************************
Includes 
**********************************************************************************************************/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/cdev.h>

#include <linux/io.h>   		/*  io remap */
#include <linux/ioport.h>

#include <linux/timer.h>  		/* time */
#include <linux/jiffies.h>

#include <linux/uaccess.h>		/* copy */  

#include <linux/wait.h> 		/* wait */
#include <linux/sched.h>
/**********************************************************************************************************
Define
**********************************************************************************************************/
#define    DRIVER_MAJOR     232
#define    DEVICE_NAME     "driver_button"

#define 	BASE_ADDRESS 0x01C22800

#define   IOC_MAGIC  'w'
#define   IOCTL_TEST_ON   _IO(IOC_MAGIC,0)
#define   IOCTL_TEST_OFF   _IO(IOC_MAGIC,1)


#define BUTTON_NULL 	(0X3F)
#define BUTTON_HOME 	(0X04)
#define BUTTON_NEXT 	(0X0B)
#define BUTTON_PREV 	(0X11)
#define BUTTON_ENTER 	(0X18)

#define TIME_NUM 	(10)
#define FILTER_NUM 	(5) 

/**********************************************************************************************************
Typedef
**********************************************************************************************************/


/**********************************************************************************************************
Variables
**********************************************************************************************************/
/* gpio寄存器 */
static volatile unsigned char __iomem		*base_res;    	
static volatile unsigned char __iomem		*adc_base;
static volatile unsigned char __iomem		*adc_data;
static volatile unsigned char __iomem		*adc_ctl;
static volatile unsigned char __iomem		*adc_irq;
		
struct timer_list timer; /* 定义定时器 */
wait_queue_head_t button_wait; /* 等待队列 */
bool  key_ready = false;

static unsigned char user_key =  BUTTON_NULL;
static unsigned char last_key = BUTTON_NULL;
static unsigned char handle_key = BUTTON_NULL;

/**********************************************************************************************************
Function 
**********************************************************************************************************/


/***********************************************************************************************************
* @描述	: 键值处理
***********************************************************************************************************/
void get_key_value(unsigned char value)
{
	switch(value)
	{
		case BUTTON_HOME:
			user_key = 1;
		break;
		case BUTTON_NEXT:
			user_key = 2;
		break;
		case BUTTON_PREV:
			user_key = 3;
		break;
		case BUTTON_ENTER:
			user_key = 4;
		break;
		default :
		break;		
	}		
}
/***********************************************************************************************************
* @描述	: 按键处理函数 
***********************************************************************************************************/
void button_handle(void)
{
	static unsigned char null_key_num = 0;
	static unsigned char valid_key_num = 0;
	/*判断按键是否为空*/
	if(*adc_data != BUTTON_NULL)
	{
		null_key_num = 0;
		last_key =  *adc_data;
		/*判断与上次有效键值是否一致*/
		if(handle_key != last_key)
		{	
			/*多次判断,去抖操作*/
			valid_key_num++;
			if(valid_key_num > FILTER_NUM)
			{
				valid_key_num = 0;
				handle_key = last_key;
				/*获取键值*/
				get_key_value(handle_key);
				/*唤醒等待任务*/
				key_ready = true;
				wake_up(&button_wait);				
			}			
		}
	}
	else
	{
		/*多次判断,去抖操作*/
		null_key_num++;
		valid_key_num = 0;
		if(null_key_num > FILTER_NUM)
		{
			/*按键为空*/
			null_key_num = 0;
			last_key = 	BUTTON_NULL;
			handle_key =  BUTTON_NULL;
			
		}		
	}		
}
/***********************************************************************************************************
* @描述	: 定时器回调函数 
***********************************************************************************************************/
void time_callback(unsigned long arg)
{
	/* 设置定时器 */	
	mod_timer(&timer, jiffies + msecs_to_jiffies(TIME_NUM));
	/* 根据ADC数值 获取按键值 */
	button_handle();
}
/***********************************************************************************************************
* @描述	:  初始化函数
***********************************************************************************************************/ 
void time_init(void)
{
	/* 初始化定时器 */
	init_timer(&timer); 
	/* 设置定时处理函数 */
	timer.function = time_callback; 
	/*  超时时间 2 秒 */
	timer.expires=jiffies + msecs_to_jiffies(TIME_NUM); 
	/*  启动定时器 */
	add_timer(&timer); 
}
/***********************************************************************************************************
* @描述	:  
***********************************************************************************************************/
static int ioremap_init(void)
{
    base_res = request_mem_region(BASE_ADDRESS , 0x10 , "GPIOE_MEM");
	
	if(base_res == NULL)
	{
		printk("request_mem_region BASE_ADDRESS,0x28 fail\n");		
	}
    else
	  printk(KERN_EMERG DEVICE_NAME " ======================request_mem_region  ok ======================\n");
	
    /* IO映射 */
	adc_base = (unsigned char*)ioremap(BASE_ADDRESS , 0x10);

	if(adc_base == NULL)
	{	
		printk("ioremap BASE_ADDRESS,0x28 fail\n");	
	}	
  	else
	  printk(KERN_EMERG DEVICE_NAME " ======================ioremap  ok ======================\n");

    /* 寄存器地址映射 */
	adc_ctl		=(unsigned int*)(adc_base + 0x00);
	adc_irq		=(unsigned int*)(adc_base + 0x04);
	adc_data 	=(unsigned int*)(adc_base + 0x0c);
	return 0;	
}
/***********************************************************************************************************
* @描述	:  
***********************************************************************************************************/
static int adc_init(void)
{
	*adc_ctl = 0x00C00041;
	*adc_irq = 0x00000013;	
	return 0;	
}
/***********************************************************************************************************
* @描述	:  
***********************************************************************************************************/
static int button_open(struct inode *inode, struct file *file)
{
	printk(KERN_EMERG "======================open======================\n");
	/* IO资源申请 */
	ioremap_init();
	/* 初始化adc  */
	adc_init();
	/* 初始化定时器  */
	time_init();
	/* 初始化等待队列  */	
	init_waitqueue_head(&button_wait);
	return 0;
}
/***********************************************************************************************************
* @描述	:  
***********************************************************************************************************/
static ssize_t button_write(struct file *file, const char __user * buf, size_t count, loff_t *ppos)
{
    printk(KERN_EMERG "======================write======================\n");
    return 0;
}
/***********************************************************************************************************
* @描述	:  
***********************************************************************************************************/
static ssize_t button_read(struct file *file,  char __user * buf, size_t count, loff_t *ppos)
{
	char buff[20];
	char num;
	/* 判断按键是否有效 */	
	if(user_key == BUTTON_NULL )	
	{
		/* 无有效按键时,任务进入等待 */
		printk(KERN_EMERG "====================== wait : null key ======================\n");
		wait_event(button_wait, key_ready);
	}
	key_ready = false;
	/* 有效按键唤醒任务 */
	printk(KERN_EMERG "====================== wake up :valid key ======================\n");
	num = sprintf(buff,"%s%d","key = ", user_key);	
	copy_to_user(buf , buff , num);
	/* 清空按键数值 */
	user_key = BUTTON_NULL;
	memset(buff,0,20);

    return 0;
}
/***********************************************************************************************************
* @描述	:  
***********************************************************************************************************/
static int button_close(struct inode *inode, struct file *file)
{
	/* 删除定时器 */
	del_timer(&timer); 	
    printk(KERN_EMERG "======================close ======================\n");
    return 0;
}
/***********************************************************************************************************
* @描述	:  
***********************************************************************************************************/
static int button_ioctl( struct file *file,  unsigned int  cmd, unsigned long arg)
{
    printk(KERN_EMERG "======================ioctl ======================\n");	
    return 0;
}
/***********************************************************************************************************
* @描述	:  
***********************************************************************************************************/
static struct file_operations driver_flops = 
{
	.owner  =   THIS_MODULE,
	.open   =   button_open,     
	.write  =   button_write,
	.read 	=  	button_read,
	.unlocked_ioctl = button_ioctl,
	.release =  button_close,
};
static struct cdev dev;
static     dev_t devno;
/***********************************************************************************************************
* @描述	:  
***********************************************************************************************************/
static int __init driver_button_init(void)
{
    int ret;
    /* 申请设备号 */
   	ret = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME);
	if(ret < 0) 
	{
		pr_err("alloc_chrdev_region failed!");
		return ret;
	}
	printk("MAJOR is %d\n", MAJOR(devno));
	printk("MINOR is %d\n", MINOR(devno)); 
	/* 注册设备 */
	cdev_init(&dev,  &driver_flops);
	ret = cdev_add(&dev ,  devno, 1);
	if (ret < 0) 
	{
		pr_err("cdev_add failed!");
		return ret;
	}
    printk(KERN_EMERG DEVICE_NAME " ======================driver_button_init======================\n");
	return 0;
}
/***********************************************************************************************************
* @描述	:  
***********************************************************************************************************/
static void __exit driver_button_exit(void)
{
    /* 注销设备 */
	cdev_del(&dev);
    /* 释放设备号 */
	unregister_chrdev_region(devno, 1);
    printk(KERN_EMERG DEVICE_NAME " ======================driver_button_exit======================\n");
}

/***********************************************************************************************************
* @描述	:  
***********************************************************************************************************/
module_init(driver_button_init);
module_exit(driver_button_exit);
MODULE_LICENSE("GPL");
/***********************************************END*******************************************************/

4.编写makefile

本次设计通过单编ko的方式得到驱动ko,单编ko的详细说明请参考我的另外一篇文章《如何编译linux驱动ko》。本次设计的Makefile代码如下(根据开发环境指定内核路径和编译工具路径):

.PHONY:    main  clean

KERNELDIR   :=   /home/liwei/v3_work/project/linux-3.4
PWD   :=   $(shell pwd)
CROSS_ARCH := /home/liwei/v3_work/tools/host/bin/arm-buildroot-linux-gnueabihf-gcc

obj-m   +=   driver_button.o
	
main: 
	$(MAKE) $(CROSS_ARCH) -C  $(KERNELDIR)   M=$(PWD)   modules 
	
clean: 
	rm   -rf   *.o   *~   core   .depend   .*.cmd   *.ko   *.mod.c   .tmp_versions *.symvers *.d *.markers *.order

将driver_button.c和上述的Makefile文件放在同一个目录下(路径为任意路径,不需要一定放在内核目录中),执行make指令,最终我们得到了驱动ko 。

5.编写应用程序

编写一个demo_app.c的应用程序,程序源码如下:

/**
*********************************************************************************************************
*                                        		demo
*                                      (c) Copyright 2021-2031
*                                         All Rights Reserved
*
* @File    : 
* @By      : liwei
* @Version : V0.01
* 
*********************************************************************************************************
**/

/**********************************************************************************************************
Includes 
**********************************************************************************************************/
#include <stdio.h> 
#include <string.h> 
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h>


/***********************************************************************************************************
* @描述	:  
***********************************************************************************************************/
int main(int arvc, char *argv[])
{
	int fd;
	int buff[20] ;
	int num;
	printf("==========button test==================\n");
	//打开驱动 
	fd = open("/dev/driver_button",O_RDWR);

	while(1)
	{
		//执行驱动读操作
		num= read(fd,buff,15);	
		printf("run:%s\r\n",buff);
		//延时等待串口发送数据 ,清发送BUFF
		sleep(1);
		memset(buff,0,20);
	}
	
	return 0;
}
/***********************************************END*******************************************************/

应用程序源码用于测试按键驱动功能,在测试程序在一直循环读取按键驱动数据,并将按键数据打印出来。

我们将应用程序源码放在虚拟机的任意一个目录中,并在该目录下执行如下gcc编译指令:

arm-buildroot-linux-gnueabihf-gcc app_button.c  -o app_button

于是我们得到一个可执行文件app_button

6.安装驱动及运行应用程序

目前我们得到了driver_button.ko和app_button两个文件,我们使用SecureCRTPortable工具将这两个文件传输到V3开发板中。


执行安装驱动指令:

insmod driver_button.ko 


执行创建文件节点指令:

mknod /dev/driver_button c 248 0


执行修改app_button文件权限指令:

chmod 777 app_button

执行运行demo_app指令:

./app_button


测试按键结果如下:

7.总结

本文本文描述了基于全志V3S开发板的按键驱动程序和测试应用程序的设计流程,本次设计细节如下:

1、硬件电路通过ADC识别按键。
2、使用内核定时器周期查询按键是否按下。
3、使用等待队列休眠等待按键的应用。

创作不易希望朋友们点赞,转发,评论,关注。
您的点赞,转发,评论,关注将是我持续更新的动力
作者:李巍
Github:liyinuoman2017
CSDN:liyinuo2017
今日头条:程序猿李巍

相关推荐

Linux在线安装JDK1.8

首先在服务器pingwww.baidu.com查看是否可以连网然后就可以在线下载一、下载安装JDK1.81、在下载安装的同时做好一些准备工作...

Linux安装JDK,超详细

1、了解RPMRPM是Red-HatPackageManager(RPM软件包管理器)的缩写,这一文件格式名称虽然打上了RedHat的标志,但是其原始设计理念是开放式的,现在包括OpenLinux...

Linux安装jdk1.8(超级详细)

前言最近刚购买了一台阿里云的服务器准备要搭建一个网站,正好将网站的一个完整搭建过程分享给大家!#一、下载jdk1.8首先我们需要去下载linux版本的jdk1.8安装包,我们有两种方式去下载安装...

Linux系统安装JDK教程

下载jdk-8u151-linux-x64.tar.gz下载地址:https://www.oracle.com/technetwork/java/javase/downloads/index.ht...

干货|JDK下载安装与环境变量配置图文教程「超详细」

1.JDK介绍1.1什么是JDK?SUN公司提供了一套Java开发环境,简称JDK(JavaDevelopmentKit),它是整个Java的核心,其中包括Java编译器、Java运行工具、Jav...

Linux下安装jdk1.8

一、安装环境操作系统:CentOSLinuxrelease7.6.1810(Core)JDK版本:1.8二、安装步骤1.下载安装包...

Linux上安装JDK

以CentOS为例。检查是否已安装过jdk。yumlist--installed|grepjdk或者...

Linux系统的一些常用目录以及介绍

根目录(/):“/”目录也称为根目录,位于Linux文件系统目录结构的顶层。在很多系统中,“/”目录是系统中的唯一分区。如果还有其他分区,必须挂载到“/”目录下某个位置。整个目录结构呈树形结构,因此也...

Linux系统目录结构

一、系统目录结构几乎所有的计算机操作系统都是使用目录结构组织文件。具体来说就是在一个目录中存放子目录和文件,而在子目录中又会进一步存放子目录和文件,以此类推形成一个树状的文件结构,由于其结构很像一棵树...

Linux文件查找

在Linux下通常find不很常用的,因为速度慢(find是直接查找硬盘),通常我们都是先使用whereis或者是locate来检查,如果真的找不到了,才以find来搜寻。为什么...

嵌入式linux基本操作之查找文件

对于很多初学者来说都习惯用windows操作系统,对于这个系统来说查找一个文件简直不在话下。而学习嵌入式开发行业之后,发现所用到的是嵌入式Linux操作系统,本想着跟windows类似,结果在操作的时...

linux系统查看软件安装目录的方法

linux系统下怎么查看软件安装的目录?方法1:whereis软件名以查询nginx为例子...

Linux下如何对目录中的文件进行统计

统计目录中的文件数量...

Linux常见文件目录管理命令

touch用于创建空白文件touch文件名称mkdir用于创建空白目录还可以通过参数-p创建递归的目录...

Linux常用查找文件方法总结

一、前言Linux系统提供了多种查找文件的命令,而且每种查找命令都具有其独特的优势,下面详细总结一下常用的几个Linux查找命令。二、which命令查找类型:二进制文件;...

取消回复欢迎 发表评论: