Skip to main content
德胜云
  万速智能9 > 编程学习

「阿里服务器」linux按键驱动设计(V3S开发板)怎么可以错过

2023-01-06 18:09:02 浏览:

「阿里服务器」linux按键驱动设计(V3S开发板)怎么可以错过

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 cleanKERNELDIR := /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.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); //延时等待串口发送数据 ,清发送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按键驱动设计(V3S开发板)怎么可以错过

  • 墙裂推荐「易合网:有几种类型的网站,你了解吗?」易合网软件定
  • 学到了吗「美国CN2云服务器哪家比较好?」美国vps云主机租
  • 深度揭秘「如何给自己的公司建网站,制作网站的平台」怎么建设公
  • 一篇读懂「网站建设的基本流程,做网站的流程与步骤」网站建设的
  • 这都可以「10个无需备案性价比超高的国外域名注册商」国外域名