本次设计系统内核是基于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 = 0x00C00041LRADC_INTC = 0x000000133.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 __iomembase_res; static volatile unsigned char __iomemadc_base;static volatile unsigned char __iomemadc_data;static volatile unsigned char __iomemadc_ctl;static volatile unsigned char __iomemadc_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 cleanKERNELDIR := /home/liwei/v3_work/project/linux-3.4PWD := $(shell pwd)CROSS_ARCH := /home/liwei/v3_work/tools/host/bin/arm-buildroot-linux-gnueabihf-gccobj-m += driver_button.omain: $(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);//延时等待串口发送数据 ,清发送BUFFsleep(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:liyinuoman2017CSDN:liyinuo2017今日头条:程序猿李巍






