** 个人名片:**
🦁作者简介:学生
🐯个人主页:妄北y🐧个人QQ:2061314755
🐻个人邮箱:2061314755@qq.com
🦉个人WeChat:Vir2021GKBS
🐼本文由妄北y原创,首发CSDN🎊🎊🎊
🐨座右铭:大多数人想要改造这个世界,但却罕有人想改造自己。
专栏导航:
妄北y系列专栏导航:
C/C++的基础算法:C/C++是一种常用的编程语言,可以用于实现各种算法,这里我们对一些基础算法进行了详细的介绍与分享。🎇🎇🎇
QT基础入门学习:对QT的基础图形化页面设计进行了一个简单的学习与认识,利用QT的基础知识进行了翻金币小游戏的制作🤹🤹🤹
Linux基础编程:初步认识什么是Linux,为什么学Linux,安装环境,进行基础命令的学习,入门级的shell编程。🍻🍻🍻
Linux应用开发基础开发:分享Linux的基本概念、命令行操作、文件系统、用户和权限管理等,网络编程相关知识,TCP/IP 协议、套接字(Socket)编程等,可以实现网络通信功能。💐💐💐
Linux驱动开发:Linux驱动开发是Linux操作系统中非常关键的一部分,驱动程序是一种特殊的程序,它包含有关硬件设备的信息,并充当操作系统与硬件之间的桥梁。驱动开发的主要目标是让硬件设备能够在Linux系统上正常运行,并实现与操作系统之间的无缝连接。💪💪💪
Linux项目开发:Linux基础知识的实践,做项目是最锻炼能力的一个学习方法,这里我们会学习到一些简单基础的项目开发与应用,而且都是毕业设计级别的哦。🤸🤸🤸
非常期待和您一起在这个小小的互联网世界里共同探索、学习和成长。💝💝💝 ✨✨ 欢迎订阅本专栏 ✨✨
文章介绍:
🎉本篇文章对Linux驱动基础知识学习的相关知识进行分享!🥳🥳🥳
在Linux中,查询方式的按键驱动程序是一种用于检测和处理按键输入事件的软件组件。它通过定期轮询硬件接口来检测按键状态的变化,并将这些变化转化为相应的输入事件,然后上报给Linux的输入子系统。
如果您觉得文章不错,期待你的一键三连哦,你的鼓励是我创作动力的源泉,让我们一起加油,一起奔跑,让我们顶峰相见!!!💪💪💪
🎁感谢大家点赞👍收藏⭐评论✍️
**目录: **
一、LED 驱动回顾
对于 LED,APP 调用 open 函数导致驱动程序的 led_open 函数被调用。在 里面,把 GPIO 配置为输出引脚。安装驱动程序后并不意味着会使用对应的硬件, 而 APP 要使用对应的硬件,必须先调用 open 函数。所以建议在驱动程序的 open 函数中去设置引脚。
APP 继续调用 write 函数传入数值,在驱动程序的 led_write 函数根据该 数值去设置 GPIO 的数据寄存器,从而控制 GPIO 的输出电平。
怎么操作寄存器?从芯片手册得到对应寄存器的物理地址,在驱动程序中使 用 ioremap 函数映射得到虚拟地址。驱动程序中使用虚拟地址去访问寄存器。
1.1 回顾 LED 驱动
二、 按键驱动编写思路
GPIO 按键的原理图一般有如下 2 种:
图 2.1 按键原理图示意
按键没被按下时,上图中左边的 GPIO 电平为高,右边的 GPIO 电平为低。
按键被按下后,上图中左边的 GPIO 电平为低,右边的 GPIO 电平为高。
编写按键驱动程序最简单的方法如图 14.3 所示:
2.2 按键驱动编写
回顾一下编写驱动程序的套路:
2.3 驱动程序编写套路
对于使用查询方式的按键驱动程序,我们只需要实现 **button_open、 button_read**。
三、编程:先写框架
我们的目的写出一个容易扩展到各种芯片、各种板子的按键驱动程序,所以驱动程序分为上下两层:
(1)button_drv.c 分配/设置/注册 file_operations 结构体
起承上启下的作用,向上提供 button_open,button_read 供 APP 调用。
而这 2 个函数又会调用底层硬件提供的 p_button_opr 中的 init、read 函数操作硬件。
(2)board_xxx.c 分配/设置/注册 button_operations 结构体
这个结构体是我们自己抽象出来的,里面定义单板 xxx 的按键操作函数。
这样的结构易于扩展,对于不同的单板,只需要替换 board_xxx.c 提供自己的 button_operations 结构体即可。
3.1 **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"
static int major = 0;
static struct button_operations *p_button_opr;
static struct class *button_class;
static int button_open (struct inode *inode, struct file *file)
{
int minor = iminor(inode);
p_button_opr->init(minor);
return 0;
}
static ssize_t button_read (struct file *file, char __user *buf, size_t size, loff_t *off)
{
unsigned int minor = iminor(file_inode(file));
char level;
int err;
level = p_button_opr->read(minor);
err = copy_to_user(buf, &level, 1);
return 1;
}
static struct file_operations button_fops = {
.open = button_open,
.read = button_read,
};
void register_button_operations(struct button_operations *opr)
{
int i;
p_button_opr = opr;
for (i = 0; i < opr->count; i++)
{
device_create(button_class, NULL, MKDEV(major, i), NULL, "100ask_button%d", i);
}
}
void unregister_button_operations(void)
{
int i;
for (i = 0; i < p_button_opr->count; i++)
{
device_destroy(button_class, MKDEV(major, i));
}
}
EXPORT_SYMBOL(register_button_operations);
EXPORT_SYMBOL(unregister_button_operations);
int button_init(void)
{
major = register_chrdev(0, "100ask_button", &button_fops);
button_class = class_create(THIS_MODULE, "100ask_button");
if (IS_ERR(button_class))
return -1;
return 0;
}
void button_exit(void)
{
class_destroy(button_class);
unregister_chrdev(major, "100ask_button");
}
module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");
第26行:确定主设备号(让系统随机分配)
static int major = 0;
第29行:创建类,让设备自动创建设备号
static struct class *button_class;
定义一个file_operations结构体
static struct file_operations button_fops = { .open = button_open, .read = button_read, };
第55~64行:创建register_button_operations来注册按钮操作
void register_button_operations(struct button_operations *opr) { int i; p_button_opr = opr; for (i = 0; i < opr->count; i++) { device_create(button_class, NULL, MKDEV(major, i), NULL, "100ask_button%d", i); } }
*button_operations *opr**中定义了*count**就可以知道有几个按键
用一个for循环遍历每个按键调用device_create 在button_class下面虚拟文件中构造设备节点,父亲为NULL,主设备为major,次设备号为i,格式为100ask_button%d。
在这个类下面创造了设备节点,主设备号为major,次设备号为i,名字是100ask_buttoni
第66~74行:unregister_button_operations,销毁上面创建的按钮操作,注销按钮操作
void unregister_button_operations(void) { int i; for (i = 0; i < p_button_opr->count; i++) { device_destroy(button_class, MKDEV(major, i)); } }
销毁掉创建的类
device_destroy(button_class, MKDEV(major, i));
EXPORT_SYMBOL
是一个常用于 Linux 内核编程的宏,它用于将一个符号(例如一个函数或变量)标记为“导出的”,这意味着这个符号可以在其他模块中被引用或链接。在 Linux 内核中,模块是一种可以动态加载和卸载的代码段,而
EXPORT_SYMBOL
使得一个模块中的函数或变量可以被其他模块所使用。
EXPORT_SYMBOL(register_button_operations); EXPORT_SYMBOL(unregister_button_operations);
这俩个函数是给别人用的,我们需要在button.drv头文件里面声明一下
void register_button_operations(struct button_operations *opr); void unregister_button_operations(void);
第81~90行:定义入口函数init()
int button_init(void) { major = register_chrdev(0, "100ask_button", &button_fops); button_class = class_create(THIS_MODULE, "100ask_button"); if (IS_ERR(button_class)) return -1; return 0; }
在入口函数中把file_operations结构体 button_fops****告诉内核
major = register_chrdev(0, "100ask_button", &button_fops);
在入口函数创建类
button_class = class_create(THIS_MODULE, "100ask_button");
在这个类下创造dev,需要有真实的dev时候才能创建,由底层** button_operations *opr**提供
第92~96行:定义出口函数 exit()
void button_exit(void) { class_destroy(button_class); unregister_chrdev(major, "100ask_button"); }
卸载主设备号
unregister_chrdev(major, "100ask_button");
销毁掉入口函数中创建的类
class_destroy(button_class);
第98~100行:向内核声明函数
module_init(button_init); module_exit(button_exit); MODULE_LICENSE("GPL");
框架搭建完毕,现在我们实现file_operations结构体中的button_open和 button_read
主设备号用来寻找驱动程序,次设备号是提供给我们用的,想怎么用就怎么用,想用次设备号寻找哪个按键就用哪个次设备号
第83行** button_init**中注册主设备号
major = register_chrdev(0, "100ask_button", &button_fops); //主设备号
第31~36行 :编写button_open函数
static int button_open (struct inode *inode, struct file *file) { int minor = iminor(inode); p_button_opr->init(minor); return 0; }
调用底层提供的p_button_opr结构体中的**init()**函数来初始化引脚,配置为输入引脚,传入次设备号minor,从inode节点中获得次设备号
int minor = iminor(inode);
p_button_opr->init(minor)
第38~47行 :编写button_read函数
static ssize_t button_read (struct file *file, char __user *buf, size_t size, loff_t *off) { unsigned int minor = iminor(file_inode(file)); char level; int err; level = p_button_opr->read(minor); err = copy_to_user(buf, &level, 1); return 1; }
从file中获得次设备号
unsigned int minor = iminor(file_inode(file));
调用底层提供的p_button_opr结构体中的**read()**函数来把GPIO的电平读回来
level = p_button_opr->read(minor);
读回来后传回用户空间,从level中将数据拷贝到buf中,拷贝1字节,return 1表示读了1字节
err = copy_to_user(buf, &level, 1); return 1;
问题来了,底层提供的p_button_opr结构体哪里来的呢?
是底层硬件相关的代码提供的,现在让我们看一下底层代码
3.2 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 *opr);
void unregister_button_operations(void);
#endif
count:有多少个按键
设置button_operations结构体提供上层init()函数和read()函数
在 **button_drv.c **(上层)第28行中定义全局变量,就可以使用底层p_button_opr
static struct button_operations *p_button_opr;
在button_drv.c (上层)第55~64行中提供注册函数:
void register_button_operations(struct button_operations *opr) { int i; p_button_opr = opr; for (i = 0; i < opr->count; i++) { device_create(button_class, NULL, MKDEV(major, i), NULL, "100ask_button%d", i); } }
由底层向上一层提供**p_button_opr结构体 **
3.3 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, read gpio for button %d\n", __FILE__, __FUNCTION__, __LINE__, which);
return 1;
}
static struct button_operations my_buttons_ops ={
.count = 2,
.init = board_xxx_button_init_gpio,
.read = board_xxx_button_read_gpio,
};
int board_xxx_button_init(void)
{
register_button_operations(&my_buttons_ops);
return 0;
}
void board_xxx_button_exit(void)
{
unregister_button_operations();
}
module_init(board_xxx_button_init);
module_exit(board_xxx_button_exit);
MODULE_LICENSE("GPL");
第26~29行:初始化引脚
static void board_xxx_button_init_gpio (int which) { printk("%s %s %d, init gpio for button %d\n", __FILE__, __FUNCTION__, __LINE__, which); }
打印一句话
第31~35行:读取引脚
static int board_xxx_button_read_gpio (int which) { printk("%s %s %d, read gpio for button %d\n", __FILE__, __FUNCTION__, __LINE__, which); return 1; }
打印一句话并且返回引脚电平
第37~41行:定义一个** button_operations 结构体**
static struct button_operations my_buttons_ops ={ .count = 2, .init = board_xxx_button_init_gpio, .read = board_xxx_button_read_gpio, };
count表明有2个按键
init表明初始化引脚的函数
read表明读取引脚的函数
第43~47行:注册函数
int board_xxx_button_init(void) { register_button_operations(&my_buttons_ops); return 0; }
入口函数中注册了一个my_buttons_ops结构体
第49~52行:卸载函数
void board_xxx_button_exit(void) { unregister_button_operations(); }
出口函数中销毁一个my_buttons_ops结构体
3.4 button_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;
}
3.5 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_imx6ull-sdk/Linux-4.9.88
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.1编译
编译程序,把代码上传代服务器后执行 make 命令。
cp *.ko button_test ~/nfs_rootfs/
4.2 挂载到开发板
在开发板上挂载 NFS
4.3 测试
[root@100ask:~]# echo "7 4 1 7" > /proc/sys/kernel/printk // 打开内核的打印信息,有些
板子默认打开了
提示找不到符号,所以我们需要先装载button_drv.ko
4.4 测试结果:
大佬觉得有用的话点个赞 👍🏻 呗。
❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍
🔥🔥🔥任务在无形中完成,价值在无形中升华,让我们一起加油吧!🌙🌙🌙
版权归原作者 妄北y 所有, 如有侵权,请联系我们删除。