韦东山嵌入式linux系列-查询方式的按键驱动程序_编写框架

1 LED 驱动回顾

对于 LED, APP 调用 open 函数导致驱动程序的 led_open 函数被调用。在里面,把 GPIO 配置为输出引脚。安装驱动程序后并不意味着会使用对应的硬件,而 APP 要使用对应的硬件,必须先调用 open 函数。所以建议在驱动程序的open 函数中去设置引脚。

APP 继续调用 write 函数传入数值,在驱动程序的 led_write 函数根据该数值去设置 GPIO 的数据寄存器,从而控制 GPIO 的输出电平。怎么操作寄存器?从芯片手册得到对应寄存器的物理地址,在驱动程序中使用 ioremap 函数映射得到虚拟地址。驱动程序中使用虚拟地址去访问寄存器。

2 按键驱动编写思路

GPIO 按键的原理图一般有如下 2 种:

按键没被按下时,上图中左边的 GPIO 电平为高,右边的 GPIO 电平为低。

按键被按下后,上图中左边的 GPIO 电平为低,右边的 GPIO 电平为高。

回顾一下编写驱动程序的套路

对于使用查询方式的按键驱动程序,我们只需要实现 button_open、button_read

3 编程:先写框架

我们的目的写出一个容易扩展到各种芯片、各种板子的按键驱动程序,所以驱动程序分为上下两层:

① button_drv.c 分配/设置/注册 file_operations 结构体

起承上启下的作用,向上提供 button_open,button_read 供 APP 调用。
而这 2 个函数又会调用底层硬件提供的 p_button_opr 中的 init、 read函数操作硬件。

② board_xxx.c 分配/设置/注册 button_operations 结构体

这个结构体是我们自己抽象出来的,里面定义单板 xxx 的按键操作函数。这样的结构易于扩展,对于不同的单板,只需要替换 board_xxx.c 提供自己的 button_operations 结构体即可。

3.1 把按键的操作抽象出一个 button_operations 结构体

首先看看 button_drv.h,它定义了一个 button_operations 结构体,把按键的操作抽象为这个结构体

#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* operaions);
void unregister_button_operations(void);#endif

再看看 board_xxx.c,它实现了一个 button_operations 结构体,代码如下。

调用 register_button_operations 函数,把这个结构体注册到上层驱动中

// ...// 定义button_operations结构体
static struct button_operations my_button_operations = {.count = 2,.init = board_xxx_button_init_gpio,.read = board_xxx_button_read_gpio,
};// 入口函数
int board_xxx_init(void)
{// 注册设备结点register_button_operations(&my_button_operations);return 0;
}// 出口函数
void board_xxx_exit(void)
{// 注销设备结点unregister_button_operations();
}// ...

3.2 驱动程序的上层: file_operations 结构体

上层是 button_drv.c,它的核心是 file_operations 结构体,首先看看入口函数,代码如下

int button_init(void)
{// 注册file_operations结构体major = register_chrdev(0, "winter_button", &button_operations);// 注册结点button_class = class_create(THIS_MODULE, "winter_button");// 注册失败if (IS_ERR(button_class)){return -1;}return 0;
}

向内核注册一个 file_operations 结构体。
同时创建一个 class,但是该 class 下还没有 device,在后面获得底层硬件的信息时再在 class 下创建 device:这只是用来创建设备节点,它不是驱动程序的核心。

再来看看 button_drv.c 中 file_operations 结构体的成员函数,代码如下

static int major = 0;
static struct class* button_class;
static struct button_operations* p_button_operation;// 4实现open/read函数
// open函数主要完成初始化操作
int button_open (struct inode* node, struct file* file)
{int minor;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 用次设备号控制某个按键minor = iminor(node);p_button_operation->init(minor);return 0;
}// read读取按键信息
ssize_t button_read (struct file* file, char __user* buff, size_t size, loff_t* offset)
{unsigned int minor;int level;int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 通过file获取次设备号minor = iminor(file_inode(file));// 电平高低// 调用read函数level = p_button_operation->read(minor);// 将kernel_buf区的数据拷贝到用户区数据buf中,即从内核kernel_buf中读数据err = copy_to_user(buff, &level, 1);return 1;
}// 2定义自己的file_operations结构体
static struct file_operations button_operations = {.open = button_open,.read = button_read,
};

button_operations 指针,来自于底层硬件相关的代码。

底层代码调用 register_button_operations 函数,向上提供这个结构体指针。

register_button_operations 函 数代码如下,它还根据底层提供button_operations 调用 device_create,这是创建设备节点

// 注册设备结点
void register_button_operations(struct button_operations* p_operaions)
{int i;// 赋值p_button_operation = p_operaions;for (i = 0; i < p_operaions->count; i++){device_create(button_class, NULL, MKDEV(major, i), NULL, "winter_button@%d", i);}
}// 注销设备结点
void unregister_button_operations(void)
{int i;for (i = 0; i < p_button_operation->count; i++){device_destroy(button_class, MKDEV(major, i));}
}// 导出
EXPORT_SYMBOL(register_button_operations);
EXPORT_SYMBOL(unregister_button_operations);

完整代码

button_drv.c
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/signal.h>
#include <linux/mutex.h>
#include <linux/mm.h>
#include <linux/timer.h>
#include <linux/wait.h>
#include <linux/skbuff.h>
#include <linux/proc_fs.h>
#include <linux/poll.h>
#include <linux/capi.h>
#include <linux/kernelcapi.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/moduleparam.h>#include "button_drv.h"// 1主设备号
static int major = 0;
static struct class* button_class;
static struct button_operations* p_button_operation;// 注册设备结点
void register_button_operations(struct button_operations* p_operaions)
{int i;// 赋值p_button_operation = p_operaions;for (i = 0; i < p_operaions->count; i++){device_create(button_class, NULL, MKDEV(major, i), NULL, "winter_button@%d", i);}
}// 注销设备结点
void unregister_button_operations(void)
{int i;for (i = 0; i < p_button_operation->count; i++){device_destroy(button_class, MKDEV(major, i));}
}// 导出
EXPORT_SYMBOL(register_button_operations);
EXPORT_SYMBOL(unregister_button_operations);// 4实现open/read函数
// open函数主要完成初始化操作
int button_open (struct inode* node, struct file* file)
{int minor;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 用次设备号控制某个按键minor = iminor(node);p_button_operation->init(minor);return 0;
}// read读取按键信息
ssize_t button_read (struct file* file, char __user* buff, size_t size, loff_t* offset)
{unsigned int minor;int level;int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 通过file获取次设备号minor = iminor(file_inode(file));// 电平高低// 调用read函数level = p_button_operation->read(minor);// 将kernel_buf区的数据拷贝到用户区数据buf中,即从内核kernel_buf中读数据err = copy_to_user(buff, &level, 1);return 1;
}// 2定义自己的file_operations结构体
static struct file_operations button_operations = {.open = button_open,.read = button_read,
};// 3在入口函数中注册
int button_init(void)
{// 注册file_operations结构体major = register_chrdev(0, "winter_button", &button_operations);// 注册结点button_class = class_create(THIS_MODULE, "winter_button");// 注册失败if (IS_ERR(button_class)){return -1;}return 0;
}// 出口函数
void button_exit(void)
{// 注销结点class_destroy(button_class);unregister_chrdev(major, "winter_button");
}module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");
button_drv.h
#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* operaions);
void unregister_button_operations(void);#endif
board_xxx.c
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/signal.h>
#include <linux/mutex.h>
#include <linux/mm.h>
#include <linux/timer.h>
#include <linux/wait.h>
#include <linux/skbuff.h>
#include <linux/proc_fs.h>
#include <linux/poll.h>
#include <linux/capi.h>
#include <linux/kernelcapi.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/moduleparam.h>#include "button_drv.h"// 实现具体的函数
static void board_xxx_button_init_gpio(int which)
{printk("%s %s %d, init gpio for button %d\n", __FILE__, __FUNCTION__, __LINE__, which);
}static int board_xxx_button_read_gpio(int which)
{printk("%s %s %d, init gpio for button %d\n", __FILE__, __FUNCTION__, __LINE__, which);// 返回高电平return 1;
}// 定义button_operations结构体
static struct button_operations my_button_operations = {.count = 2,.init = board_xxx_button_init_gpio,.read = board_xxx_button_read_gpio,
};// 入口函数
int board_xxx_init(void)
{// 注册设备结点register_button_operations(&my_button_operations);return 0;
}// 出口函数
void board_xxx_exit(void)
{// 注销设备结点unregister_button_operations();
}module_init(board_xxx_init);
module_exit(board_xxx_exit);
MODULE_LICENSE("GPL");
board_test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>/** ./button_test /dev/100ask_button0**/
int main(int argc, char **argv)
{int fd;char val;/* 1. 判断参数 */if (argc != 2) {printf("Usage: %s <dev>\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;
}

Makefile


# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册KERN_DIR = /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4all:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o button_test button_test.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f ledtest# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.oobj-m	+= button_drv.o
obj-m	+= board_xxx.o

编译

4 测试

这只是一个示例程序,还没有真正操作硬件。测试程序操作驱动程序时,只会导致驱动程序中打印信息。首先设置交叉工具链,修改驱动 Makefile 中内核的源码路径,编译驱动和测试程序。启动开发板后,通过 NFS 访问编译好驱动程序、测试程序,就可以在开发板上如下操作了:

在开发板挂载 Ubuntu 的NFS目录

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs/ /mnt

将ko文件和测试代码拷贝到挂载目录,安装驱动

insmod button_drv.ko
insmod board_xxx.ko

执行测试程序

./button_test /dev/winter_button@0
./button_test /dev/winter_button@1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/49545.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Adobe Character Animator (CH) 安装包软件下载

目录 一、软件简介 二、下载与安装 1. 下载 2. 安装 三、注意事项 1. 硬件要求 2. 兼容性 四、功能介绍 1. 实时面部捕捉 2. 实时语音同步 3. 动作捕捉 五、快捷键操作 CH 提供了一系列快捷键以方便用户快速操作。以下是一些常用的快捷键&#xff1a; 一、软件简介…

django电商用户消费数据分析系统-计算机毕业设计源码20891

摘 要 随着电子商务的快速发展&#xff0c;电商平台积累了大量的用户消费数据。为了更好地理解用户行为、优化商品结构和提升用户体验&#xff0c;本文设计并实现了一个基于Django框架的电商用户消费数据分析系统。 该系统包含后台首页、系统用户&#xff08;管理员&#xf…

Hive分布式SQL计算平台

Hive分布式SQL计算平台 一、Hive 概述二、Hive架构三、Hive客户端 1、Hive有哪些客户端可以使用2、Hive第三方客户端 四、Hive使用语法 1、数据库操作2、内部表&#xff0c;外部表3、数据的导入与导出4、分区表5、分桶表6、复杂类型操作7、数据抽样8、Virtual Columns 虚拟列9…

Samtec技术科普小课堂 | 一文入门射频连接器~

【摘要/前言】 在本文中&#xff0c;我们将回到基础知识&#xff0c;了解一下什么是射频连接器。如果您是信号完整性专家&#xff0c;请点击阅读原文访问我们的网站视频&#xff0c;通过我们的网络研讨会视频了解教科书上可能找不到的知识。 如果您是电气工程领域的新手&#…

pycharm git 新建备忘

git 提交时出现如下错误&#xff1a; Committer identity unknown *** Please tell me who you are. Run git config --global user.email "youexample.com" git config --global user.name "Your Name" to set your accounts default identity. Omit…

【Linux】条件变量及生产者消费者模型

为什么要将这两者放在一起进行呢&#xff1f; 主要是因为生产消费与条件变量关系密切&#xff0c;正好相辅相成。 目录 条件变量&#xff1a;条件变量的引出&#xff1a;条件变量的解释与接口&#xff1a;测试代码&#xff1a; 生产者消费者模型&#xff1a;概念&#xff1a;代…

【LeetCode】86.分割链表

1. 题目 2. 分析 这题没有太大难度&#xff0c;主要是熟悉代码。 3. 代码 # Definition for singly-linked list. # class ListNode: # def __init__(self, val0, nextNone): # self.val val # self.next next class Solution:def partition(self, he…

MySQL补充性文件

数据库专属单词 authentication #身份验证 delimiter #分隔符 character #字符集 collate #整理。 指定字符集的排序规则 unicode #统一码 flush #刷新 privileges #特权 string #串 set #设置 use #使用 zerofill #修饰符。0可以填补输出的值 unsigned #修饰符。无符…

德国云手机:企业移动办公解决方案

在现代商业环境中&#xff0c;移动办公已经成为一种趋势。德国云手机作为一种高效的解决方案&#xff0c;为企业提供了强大的支持。本文将探讨德国云手机如何优化企业的移动办公环境。 一、德国云手机的主要优势 高灵活性 德国云手机具有高度的灵活性&#xff0c;能够根据用户需…

【学习笔记】无人机系统(UAS)的连接、识别和跟踪(三)-架构模型和概念

引言 3GPP TS 23.256 技术规范&#xff0c;主要定义了3GPP系统对无人机&#xff08;UAV&#xff09;的连接性、身份识别、跟踪及A2X&#xff08;Aircraft-to-Everything&#xff09;服务的支持。 3GPP TS 23.256 技术规范&#xff1a; 【免费】3GPPTS23.256技术报告-无人机系…

基于 Electron+Vite+Vue3+Sass 框架搭建

技术参考 技术描述Electron一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。嵌入 Chromium 和 Node.jsElectron Forge用于打包和分发 Electron 应用程序的一体化工具。英文地址在此Vite前端构建工具Vue3用于构建用户界面的 JavaScript 框架vitejs/plugin-vueVite 插…

PlantUML 语法、图标和示例

基本语法 关键字 声明参与者的几个关键字 actor、boundary、control、entity、database、collections、participant 箭头样式 我们可以通过&#xff0c;修改箭头样式&#xff0c;来表达不一样的意思&#xff1a; 表示一条丢失的消息&#xff1a;末尾加 x让箭头只有上半部…

Kafka - 生产者

生产者消息对象 public class ProducerRecord<K, V> {private final String topic; // 主题private final Integer partition; //分区号private final Headers headers; //消息头部private final K key; //键private final V value; //值private final Long timestamp; …

opencv 按键开启连续截图,并加载提示图片

背景图小图 键盘监听使用的是pynput 库 保存图片时使用了年月日时分秒命名 原图&#xff1a; from pynput import keyboard import cv2 import time# 键盘监听 def on_press(key):global jieglobal guanif key.char a:jie Trueelif key.char d:jie Falseelif key.char…

FPGA JTAG最小系统 EP2C5T144C8N

FPGA的文档没有相应的基础还真不容易看懂&#xff0c;下面是B站上对FPGA文档的解读(本文非对文档解读&#xff0c;只是为个人记录第三期&#xff1a;CycloneIV E最小系统板设计&#xff08;一&#xff09;从Datasheet上获取FPGA的基本参数_哔哩哔哩_bilibili 电源部份 核心电…

AI学习记录 -使用react开发一个网页,对接chatgpt接口,附带一些英语的学习prompt

实现了如下功能&#xff08;使用react实现&#xff0c;原创&#xff09; 实现功能&#xff1a; 1、对接gpt35模型问答&#xff0c;并实现了流式传输&#xff08;在java端&#xff09; 2、在实际使用中&#xff0c;我们的问答历史会经常分享给他人&#xff0c;所以下图的 copy …

Python酷库之旅-第三方库Pandas(042)

目录 一、用法精讲 141、pandas.Series.agg(regate)方法 141-1、语法 141-2、参数 141-3、功能 141-4、返回值 141-5、说明 141-6、用法 141-6-1、数据准备 141-6-2、代码示例 141-6-3、结果输出 142、pandas.Series.transform方法 142-1、语法 142-2、参数 142…

1196. 拐角I

问题描述 输入整数 &#x1d441;N &#xff0c;输出相应方阵。 输入一个整数 &#x1d441;N 。&#xff08; 0<&#x1d441;≤100) 输出一个方阵&#xff0c;每个数字的场宽为 3 附代码&#xff1a; #include<iostream> using namespace std; int main() { …

大屏数据看板一般是用什么技术实现的?

我们看到过很多企业都会使用数据看板&#xff0c;那么大屏看板的真正意义是什么呢&#xff1f;难道只是为了好看&#xff1f;答案当然不仅仅是。 大屏看板不仅可以提升公司形象&#xff0c;还可以提升企业的管理层次。对于客户&#xff0c;体现公司实力和品牌形象&#xff0c;…

域名解析到ipv6,并用CF隐藏端口

要求&#xff1a;域名解析到 IPv6 地址并隐藏端口 ‍ 效果&#xff1a;用域名 https://myhalo.soulio.top​ 访问http://[2409:8a62:867:4f12:56c7:5508:f7x6:8]:8080​。唯一缺点是延迟有点高。 ​​ ‍ 难度&#xff1a;需要有一定域名解析、cloudflare使用基础 ‍ 实…