【玄铁杯第三届RISC-V应用创新大赛】lpi4a 5. 按键驱动程序
[复制链接]
licheepi 4a开发板,提供了4个按键,REC,SHUT,PWR,WAKE,我试了,PWR关机键,是用来开关机的,不能编写程序,那么另外3个对应的GPIO为
SHUT GPIO1_0
WAKE AOGPIO_2
REC GPIO1_9
使用命令sudo cat /sys/kernel/debug/gpio,查看GPIO1_0对应的GPIO号码为448,驱动SHUT按键的代码如下:
驱动程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <linux/irq.h>
// GPIO号,GPIO1_0
#define SHUT_GPIO 448
/*------------------ 字符设备内容 ----------------------*/
#define SHUTKEY_NAME "shutkey"
#define SHUTKEY_CNT (1)
/*------------------ 设备数据结构体 ----------------------*/
struct key_dev_t
{
dev_t devid; // 设备号
struct cdev cdev; // cdev
struct class *class; // 类
struct device *device; // 设备
struct device_node *nd; // 设备节点
int irq_num; // 中断号
int gpio; // 数据接收引脚
atomic_t value; // 按键值
atomic_t iskey; // 有键按下或松开
wait_queue_head_t r_wait; // 定义等待队列头
};
struct key_dev_t key_dev; // 设备数据结构体
static void timer_func(struct timer_list *t); // 声明定时器消抖函数
DEFINE_TIMER(timer, timer_func); // 定义一个定时器, 内部有static struct timer_list timer_test;
static void timer_func(struct timer_list *t)
{
//long long now = ktime_to_us(ktime_get());
if (gpio_get_value(SHUT_GPIO)) // 松开
{
//printk("key up %lld\n", now);
atomic_set(&key_dev.value, 0);
}else{ // 按下
//printk("key down %lld\n", now);
atomic_set(&key_dev.value, 1);
}
atomic_set(&key_dev.iskey, 1);//有键产生
wake_up(&key_dev.r_wait); // 呼醒进程
}
// 中断回调函数
static irqreturn_t key_interrupt(int irq, void *dev_id)
{
mod_timer(&timer, jiffies + msecs_to_jiffies(50)); // 50毫秒定时器
return IRQ_HANDLED;
}
// 按键初始化
static int shut_init(void)
{
int res;
/* 申请 GPIO 资源 */
key_dev.gpio = SHUT_GPIO;
res = gpio_request(key_dev.gpio, SHUTKEY_NAME);
if (res)
{
pr_err("key dev: Failed to request gpio\n");
return res;
}
/* 将 GPIO 设置为输入模式 */
gpio_direction_input(key_dev.gpio);
/* 申请中断 */
key_dev.irq_num = gpio_to_irq(key_dev.gpio);
res = request_irq(key_dev.irq_num, key_interrupt, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, SHUTKEY_NAME, NULL); // 双边沿触发
if (res)
{
gpio_free(key_dev.gpio);
return res;
}else{
printk("irq_num: %d\n", key_dev.irq_num);
}
return 0;
}
// 打开设备
static int key_open(struct inode *inode, struct file *filp)
{
/* 将设备数据设置为私有数据 */
filp->private_data = &key_dev;
printk("key_open\n");
return 0;
}
// 从设备读取数据
static ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *offt)
{
int res = 0;
struct key_dev_t *dev = filp->private_data;
char value;
wait_event_interruptible(dev->r_wait, atomic_read(&key_dev.iskey)); // 没有按键,进入阻塞
atomic_set(&key_dev.iskey, 0);//按键读取后,清0
value = atomic_read(&key_dev.value);//重新读取按键值
res = copy_to_user(buf, &value, sizeof(value));
if (res != 0)
{
return -1;
}
return 0;
}
// 关闭/释放设备
static int key_release(struct inode *inode, struct file *filp)
{
int res = 0;
printk("key_release\n");
return res;
}
/* 设备操作函数结构体 */
static struct file_operations key_ops = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.release = key_release,
};
// 注册字符设备驱动
static int key_register(void)
{
int ret = -1; // 保存错误状态码
/* GPIO 中断初始化 */
ret = shut_init();
/* 1、创建设备号 */
/* 采用动态分配的方式,获取设备编号,次设备号为0 */
/* 设备名称为 SHUTKEY_NAME,可通过命令 cat /proc/devices 查看 */
/* SHUTKEY_CNT 为1,只申请一个设备编号 */
ret = alloc_chrdev_region(&key_dev.devid, 0, SHUTKEY_CNT, SHUTKEY_NAME);
if (ret < 0)
{
pr_err("%s Couldn't alloc_chrdev_region, ret = %d \r\n", SHUTKEY_NAME, ret);
goto fail_region;
}
/* 2、初始化 cdev */
/* 关联字符设备结构体 cdev 与文件操作结构体 file_operations */
key_dev.cdev.owner = THIS_MODULE;
cdev_init(&key_dev.cdev, &key_ops);
/* 3、添加一个 cdev */
/* 添加设备至cdev_map散列表中 */
ret = cdev_add(&key_dev.cdev, key_dev.devid, SHUTKEY_CNT);
if (ret < 0)
{
pr_err("fail to add cdev \r\n");
goto del_unregister;
}
/* 4、创建类 */
key_dev.class = class_create(THIS_MODULE, SHUTKEY_NAME);
if (IS_ERR(key_dev.class))
{
pr_err("Failed to create device class \r\n");
goto del_cdev;
}
/* 5、创建设备,设备名是 SHUTKEY_NAME */
/*创建设备 SHUTKEY_NAME 指定设备名,*/
key_dev.device = device_create(key_dev.class, NULL, key_dev.devid, NULL, SHUTKEY_NAME);
if (IS_ERR(key_dev.device))
{
goto destroy_class;
}
atomic_set(&key_dev.value, 0); // 初始化元子变量
atomic_set(&key_dev.iskey, 0);
init_waitqueue_head(&key_dev.r_wait); // 初始化等待队列
return 0;
destroy_class:
device_destroy(key_dev.class, key_dev.devid);
del_cdev:
cdev_del(&key_dev.cdev);
del_unregister:
unregister_chrdev_region(key_dev.devid, SHUTKEY_CNT);
fail_region:
/* 释放个人初始化申请的资源,如del_init(); */
free_irq(key_dev.irq_num, NULL);
gpio_free(key_dev.gpio);
return -EIO;
}
// 注销字符设备驱动
static void key_unregister(void)
{
/* 1、删除 cdev */
cdev_del(&key_dev.cdev);
/* 2、注销设备号 */
unregister_chrdev_region(key_dev.devid, SHUTKEY_CNT);
/* 3、注销设备 */
device_destroy(key_dev.class, key_dev.devid);
/* 4、注销类 */
class_destroy(key_dev.class);
/* 释放中断 */
free_irq(key_dev.irq_num, NULL);
/* 释放 IO */
gpio_free(key_dev.gpio);
// 删除定时器
del_timer(&timer);
}
// 驱动入口函数
static int __init key_driver_init(void)
{
pr_info("key_driver_init\n");
return key_register();
}
// 驱动出口函数
static void __exit key_driver_exit(void)
{
pr_info("key_driver_exit\n");
key_unregister();
}
/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(key_driver_init);
module_exit(key_driver_exit);
/* LICENSE 和作者信息 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("乘简");
应用程序
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char* argv[]){
int fd,ret;
char value;
fd=open("/dev/shutkey", O_RDWR);
if(fd<0){
printf("can't open file %s",argv[1]);
return -1;
}
while(1){
ret=read(fd,&value,sizeof(value));
if(ret<0){
printf("read error\n");
return -1;
}else{
if(value){
printf("shut key down!\n");
}else{
printf("shut key up!\n");
}
}
}
ret=close(fd);
return 0;
}
按键后执行结果:
|