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

news2024/11/13 13:28:48

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.4

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o button_test button_test.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f ledtest

# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o


obj-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.coloradmin.cn/o/1944757.html

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

相关文章

算法-DFS搜索

题目一 解题思路 深度遍历剪枝优化 第r行&#xff0c;第i列能不能放棋子&#xff1a;用数组dg udg cor 分别表示&#xff1a;点对应的两个斜线以及列上是否有皇后。 边界问题&#xff1a; dg[i r] 表示 r行i列处&#xff0c;所在的对角线上有没有棋子 udg[n - i r]表示 r…

环信+亚马逊云科技服务:助力出海AI社交应用扬帆起航

随着大模型技术的飞速发展&#xff0c;AI智能体的社交体验得到了显著提升&#xff0c;AI社交类应用在全球范围内持续火热。尤其是年轻一代对新技术和新体验的热情&#xff0c;使得AI社交产品在海外市场迅速崛起。作为领先的即时通讯解决方案提供商&#xff0c;环信与亚马逊云科…

uni-app:踩坑路---scroll-view内使用fixed定位,无效的问题

前言&#xff1a; emmm&#xff0c;说起来这个问题整得还挺好笑的&#xff0c;本人在公司内&#xff0c;奋笔疾书写代码&#xff0c;愉快的提交测试的时候&#xff0c;测试跟我说&#xff0c;在苹果手机上你这个样式有bug&#xff0c;我倒是要看看&#xff0c;是什么bug。 安卓…

Golang | Leetcode Golang题解之第268题丢失的数字

题目&#xff1a; 题解&#xff1a; func missingNumber(nums []int) int {n : len(nums)total : n * (n 1) / 2arrSum : 0for _, num : range nums {arrSum num}return total - arrSum }

5.9 结构化开发方法

大纲 系统分析与设计概述&#xff08;选择题 1 分&#xff09; 结构化开发方法 系统分析阶段的主要工作 系统设计基本原理 内聚&#xff08;主要考点&#xff09;

【HarmonyOS学习】用户文件访问

概述 文件所有者为登录到该终端设备的用户&#xff0c;包括用户私有的图片、视频、音频、文档等。 应用对用户文件的创建、访问、删除等行为&#xff0c;需要提前获取用户授权&#xff0c;或由用户操作完成。 用户文件访问框架 是一套提供给开发者访问和管理用户文件的基础框…

深入Mysql-03-MySQL 表的约束与数据库设计

文章目录 数据库约束的概述约束种类主键约束唯一约束非空约束默认值外键约束 表与表之间的关系数据库设计 数据库约束的概述 对表中的数据进行限制&#xff0c;保证数据的正确性、有效性和完整性。一个表如果添加了约束&#xff0c;不正确的数据将无法插入到表中。 约束种类 …

java实现OCR图片识别,RapidOcr开源免费

先看一下识别效果&#xff08;自我感觉很牛逼&#xff09;&#xff0c;比Tess4J Tesseract省事&#xff0c;这个还需要训练&#xff0c;安装软件、下载语言包什么的 很费事&#xff0c;关键识别率不高 RapidOcr不管文字的横竖&#xff0c;还是斜的都能识别&#xff08;代码实现…

鸿蒙开发仓颉语言【在工程中使用Hyperion TCP框架】

3. 在工程中使用Hyperion TCP框架 3.1 导入Hyperion TCP框架的静态库 在工程的module.json中引入Hyperion TCP框架的静态库&#xff1a; "package_requires": {"package_option": {"hyperion_hyperion.buffer": "${path_to_hyperion_proj…

SpringBoot整合SSE技术详解

Hi &#x1f44b;, Im shy SpringBoot整合SSE技术详解 1. 引言 在现代Web应用中,实时通信变得越来越重要。Server-Sent Events (SSE)是一种允许服务器向客户端推送数据的技术,为实现实时更新提供了一种简单而有效的方法。本文将详细介绍如何在SpringBoot中整合SSE,并探讨S…

java之回合制游戏以及如何优化

public class Role {private String name;private int blood;//空参public Role() {}//包含全部参数的构造public Role(String name, int blood) {this.name name;this.blood blood;}public String getName() {return name;}public void setName(String name) {this.name na…

单细胞生物都能学会的树莓派4B实现路由器

本文参考自CSDN用户羟基氟化宇的畅玩树莓派4B&#xff08;二&#xff09;树莓派搭建无线路由器&#xff08;支持5GWIFI&#xff09; 本文补充其中的细节及遇到的问题。 本文提及的代码&#xff0c;均需在树莓派终端中运行。 〇、硬件准备 树莓派4B一个、网线一根。 &#xff…

【NoSQL数据库】Redis学习笔记

一、缓存穿透 缓存穿透是先查Redis&#xff0c;发现缓存中没有数据&#xff0c;再查数据库。然而&#xff0c;如果查询的数据在数据库中也不存在&#xff0c;那么每次查询都会绕过缓存&#xff0c;直接落到数据库上。 解决方案一、缓存空数据 查询Redis缓存&#xff1a;首先查…

前后端分离项目部署,vue--nagix发布部署,.net--API发布部署。

目录 Nginx免安装部署文件包准备一、vue前端部署1、修改http.js2、npm run build 编译项目3、解压Nginx免安装,修改nginx.conf二、.net后端发布部署1、编辑appsetting.json,配置跨域请求2、配置WebApi,点击发布3、配置文件发布到那个文件夹4、配置发布相关选项5、点击保存,…

中电金信:AI数据服务

01 方案简介 AI数据服务解决方案为泛娱乐、电子商务、交通出行等行业提供数据处理、数据分析、AI模型训练等服务&#xff0c;通过自主研发的IDSC自动化数据服务平台与客户业务流程无缝衔接&#xff0c;实现超低延时的实时数据处理支持。 02 应用场景 智能医疗&#xff1a; 通…

Ribbon负载均衡与内核原理

什么是Ribbon? 目前主流的负载方案分为两种&#xff1a; 集中式负载均衡&#xff0c;在消费者和服务提供方中间使用独立的代理方式进行负载&#xff0c;有硬件的&#xff08;比如F5&#xff09;&#xff0c;也有软件的&#xff08;Nginx&#xff09;客户端根据自己的请求做负…

transformers进行学习率调整lr_scheduler(warmup)

一、get_scheduler实现warmup 1、warmup基本思想 Warmup&#xff08;预热&#xff09;是深度学习训练中的一种技巧&#xff0c;旨在逐步增加学习率以稳定训练过程&#xff0c;特别是在训练的早期阶段。它主要用于防止在训练初期因学习率过大导致的模型参数剧烈波动或不稳定。…

力扣经典题目之->设计循环队列 的超详细讲解与实现

一&#xff1a;题目 二&#xff1a;思路讲解 前提&#xff1a; a&#xff1a;本文采取数组来实现队列去解决题目 b&#xff1a;开辟k1个空间&#xff0c;front指向队首&#xff0c;rear指向队尾的后一个&#xff0c;rear这样会更好的判空和判满 以下根据pop和push感受满和空…

python如何调用matlab python package库matlab转python安装包调用使用简单示例

说明(废话) 之前没有进行python调用过matlab&#xff0c;前面用matlab engine for python可以通过调用matlab的源码文件的形式可以调用工程&#xff0c;但是这又有一个问题&#xff0c;就是在运行的时候必须提供python和matlab的全部源码 该文章是通过matlab源码转python pack…

FPGA JTAG最小系统 EP2C5T144C8N

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