韦东山嵌入式linux系列-驱动设计的思想(面向对象/分层/分离)
1 面向对象
字符设备驱动程序抽象出一个 file_operations 结构体;
我们写的程序针对硬件部分抽象出 led_operations 结构体。
2 分层
上下分层,比如我们前面写的 LED 驱动程序就分为 2 层:
① 上层实现硬件无关的操作,比如注册字符设备驱动: led_drv.c
② 下层实现硬件相关的操作,比如 board_A.c 实现单板 A 的 LED 操作
3 分离
还能不能改进? 分离
在 board_A.c 中,实现了一个 led_operations,为 LED 引脚实现了初始化函数、控制函数:
static struct led_operations board_demo_led_opr = {.num = 1,.init = board_demo_led_init,.ctl = board_demo_led_ctl,
};
如果硬件上更换一个引脚来控制 LED 怎么办?你要去修改上面结构体中的init、 ctl 函数。
实际情况是,每一款芯片它的 GPIO 操作都是类似的。比如: GPIO1_3、GPIO5_4 这 2 个引脚接到 LED:
① GPIO1_3 属于第 1 组,即 GPIO1。
a) 有方向寄存器 DIR、数据寄存器DR等,基础地址是addr_base_addr_gpio1。
b) 设置为output引脚:修改GPIO1的DIR寄存器的bit3。
c) 设置输出电平:修改GPIO1的DR寄存器的 bit3。
② GPIO5_4 属于第 5 组,即 GPIO5。
a) 有方向寄存器 DIR、数据寄存器DR等,基础地址是addr_base_addr_gpio5。
b) 设置为 output 引脚:修改GPIO5的DIR寄存器的bit4
c) 设置输出电平:修改GPIO5 的DR寄存器的 bit4。
既然引脚操作那么有规律,并且这是跟主芯片相关的,那可以针对该芯片写出比较通用的硬件操作代码。
比如 board_A.c 使用芯片 chipY,那就可以写出: chipY_gpio.c,它实现芯片 Y 的 GPIO 操作,适用于芯片 Y 的所有 GPIO 引脚。
使用时,我们只需要在 board_A_led.c 中指定使用哪一个引脚即可。程序结构如下:
以面向对象的思想,在 board_A_led.c 中实现 led_resouce 结构体,它定义“资源”──要用哪一个引脚。
在 chipY_gpio.c 中仍是实现 led_operations 结构体,它要写得更完善,支持所有 GPIO。
上下分层,左右分离
4 实例代码
board_A_led.c
#include "led_resource.h"// gpio3_1
// 3(11)左移16位 -- 11 0000 0000 0000 0000
static struct led_resource board_A_led = {.pin = GROUP_PIN(3, 1),
};// 返回指针
struct led_resource* get_led_resource(void)
{return &board_A_led;
}
chip_demo_gpio.c
#include <linux/gfp.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include "led_resource.h"
#include "led_operations.h"static struct led_resource* p_led_reource;// init函数
static int board_demo_led_init(int which)
{printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);if (!p_led_reource){p_led_reource = get_led_resource();}printk("init gpio: group: %d, pin: %d\n", GROUP(p_led_reource->pin), PIN(p_led_reource->pin));switch(GROUP(p_led_reource->pin)){case 0:{printk("init pin of group 0...\n");break;}case 1:{printk("init pin of group 1...\n");break;}case 2:{printk("init pin of group 2...\n");break;}case 3:{printk("init pin of group 3...\n");break;}}return 0;
}// ctl函数
static int board_demo_led_ctl(int which, char status)
{printk("set led: %s: group: %d, pin: %d\n", status ? "on" : "off",GROUP(p_led_reource->pin), PIN(p_led_reource->pin));switch(GROUP(p_led_reource->pin)){case 0:{printk("init pin of group 0...\n");break;}case 1:{printk("init pin of group 1...\n");break;}case 2:{printk("init pin of group 2...\n");break;}case 3:{printk("init pin of group 3...\n");break;}}return 0;
}static struct led_operations board_demo_led_operations = {.init = board_demo_led_init,.ctl = board_demo_led_ctl,
};// 返回结构体
struct led_operations* get_board_led_operations(void)
{return &board_demo_led_operations;
}
led_resource.h
#ifndef LED_RESOURCE_H
#define LED_RESOURCE_H// gpio3_0
// bit[31:16] = group
// bit[15:0] = which pin
#define GROUP(x) ((x) >> 16)
#define PIN(x) ((x) & 0xFFFF)
#define GROUP_PIN(g, p) (((g) << 16) | (p))struct led_resource {int pin;
};// 声明函数
struct led_resource* get_led_resource(void);#endif
led_operations.h
#ifndef LED_OPERATIONS_H
#define LED_OPERATIONS_Hstruct led_operations {int (*init) (int which); // 初始化LED,which是哪一个LEDint (*ctl) (int which, char status); // 控制LED,which-哪一个LED,status-1亮,0灭
};// 返回结构体指针
struct led_operations* get_board_led_operations(void);#endif
led_drv.c
/*************************************************************************> File Name: led.drv.c> Author: Winter> Created Time: Sun 07 Jul 2024 12:35:19 AM EDT************************************************************************/#include <linux/module.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include "led_operations.h"#define LED_NUM 2// 1确定主设备号,也可以让内核分配
static int major = 0; // 让内核分配
static struct class *led_class;
struct led_operations* p_led_operations;#define MIN(a, b) (a < b ? a : b)// 3 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}// write(fd, &val, 1);
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;char status;struct inode* node;int minor;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 把用户区的数据buf拷贝到内核区status,即向写到内核status中写数据err = copy_from_user(&status, buf, 1);// 根据次设备号和status控制LEDnode = file_inode(file);minor = iminor(node);p_led_operations->ctl(minor, status);return 1;
}static int led_drv_open (struct inode *node, struct file *file)
{int minor;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 得到次设备号minor = iminor(node);// 根据次设备号初始化LEDp_led_operations->init(minor);return 0;
}static int led_drv_close (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}// 2定义自己的 file_operations 结构体
static struct file_operations led_drv = {.owner = THIS_MODULE,.open = led_drv_open,.read = led_drv_read,.write = led_drv_write,.release = led_drv_close,
};// 4把 file_operations 结构体告诉内核: register_chrdev
// 5谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
static int __init led_init(void)
{int err, i; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 注册led_drv,返回主设备号major = register_chrdev(0, "winter_led", &led_drv); /* /dev/led */// 创建classled_class = class_create(THIS_MODULE, "led_class");err = PTR_ERR(led_class);if (IS_ERR(led_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "led_class");return -1;}// 创建device// 根据次设备号访问多个LED
// device_create(led_class, NULL, MKDEV(major, 0), NULL, "winter_led0"); /* /dev/winter_led0 */
// device_create(led_class, NULL, MKDEV(major, 1), NULL, "winter_led1"); /* /dev/winter_led1 */for (i = 0; i < LED_NUM; i++){device_create(led_class, NULL, MKDEV(major, i), NULL, "winter_led%d", i);}// 入口函数获得结构体指针p_led_operations = get_board_led_operations();return 0;
}// 6有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
static void __exit led_exit(void)
{int i;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);for (i = 0; i < LED_NUM; i++){device_destroy(led_class, MKDEV(major, i));}device_destroy(led_class, MKDEV(major, 0));class_destroy(led_class);// 卸载unregister_chrdev(major, "winter_led");
}// 7其他完善:提供设备信息,自动创建设备节点: class_create,device_create
module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");
led_drv_test.c
/*************************************************************************> File Name: hello_test.c> Author: Winter> Created Time: Sun 07 Jul 2024 01:39:39 AM EDT************************************************************************/#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>/** ./led_drv /dev/winter_led0 on* ./led_drv /dev/winter_led0 off*/
int main(int argc, char **argv)
{int fd;char status;/* 1. 判断参数 */if (argc < 2) {printf("Usage: %s <dev> <on | off>\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. 写文件 */if (0 == strcmp(argv[2], "on")){status = 1;}else{status = 0;}write(fd, &status, 1);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 led_drv_test led_drv_test.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f led_drv_test# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o# leddrv.c board_demo.c 编译成 100ask.ko
winter_led-y := led_drv.o chip_demo_gpio.o board_A_led.o
obj-m += winter_led.o
编译
5 测试
在开发板挂载 Ubuntu 的NFS目录
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs/ /mnt
将ko文件和测试代码拷贝到挂载目录,安装驱动
insmod winter_led.ko
执行测试程序
./led_drv_test /dev/winter_led0 on
./led_drv_test /dev/winter_led0 off
相关文章:

韦东山嵌入式linux系列-驱动设计的思想(面向对象/分层/分离)
1 面向对象 字符设备驱动程序抽象出一个 file_operations 结构体; 我们写的程序针对硬件部分抽象出 led_operations 结构体。 2 分层 上下分层,比如我们前面写的 LED 驱动程序就分为 2 层: ① 上层实现硬件无关的操作,比如注册…...

0/1背包
0/1背包 背包问题是DP最经典的类型之一,而0/1背包是最经典最基础的背包问题。 背包体积为 V V V, n n n种物品,每种物品只有1个,第 i i i种物品对应体积为 c i c_i ci,价值为 w i w_i wi,怎样装填能使…...
Linux的进程和权限的基本命令
目录 基本命令 man find date cal du ln exit grep 基本命令-帮助查询: wc cat more less head tail echo alias unalias 基本命令-进程管理: ps kill top 操作系统负载查看 用户分类: 程序用户 普通用户&#x…...

鼠标录制工具怎么挑选?9款电脑鼠标录制工具分享(2024)
你知道鼠标录制工具吗?鼠标录制工具通过记录和回放用户的操作,帮助自动化重复性任务,提高工作效率和精确性。它可以帮助用户简化很多繁琐的操作步骤,非常适合运用在电脑自动化任务、游戏自动化中,给大家整理了2024年9款…...

C1W4.LAB.Vector manipulation+Hash functions and multiplanes
理论课:C1W4.Machine Translation and Document Search 文章目录 Python 中的矢量操作Transforming vectorsExample 1Example 2 Frobenius Norm Hash functions and multiplanesBasic Hash tablesPlanesHash Function with multiple planesRandom PlanesDocument v…...

YOLOv8改进 | 检测头 | 融合渐进特征金字塔的检测头【AFPN4】
秋招面试专栏推荐 :深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡 专栏目录 :《YOLOv8改进有效…...

数据采集监控平台:挖掘数据价值 高效高速生产!
在当今数字化的时代,数据已成为企业非常宝贵的资产之一。然而,要充分发挥数据的潜力,离不开一个强大的数据采集监控平台,尤其是生产制造行业。它不仅是数据的收集者,更是洞察生产的智慧之眼,高效高速处理产…...

【算法笔记自学】第 9 章 提高篇(3)——数据结构专题(2)
9.1树与二叉树 #include <cstdio>int main() {int n, m;scanf("%d%d", &n, &m);printf(n m 1 ? "Yes" : "No");return 0; } 9.2二叉树的遍历 #include <cstdio> #include <vector> using namespace std;const int…...
Objective-C 中字符串的保存位置
在 Objective-C 中,字符串常量和动态创建的字符串(例如通过 stringWithFormat:、initWithString: 等方法创建的字符串)在内存中保存的位置一样么 ? 在 Objective-C 中,字符串常量和动态创建的字符串在内存中的保存位置…...
git 想要创建一个新的本地分支并检出远程分支的内容
如果你想要创建一个新的本地分支并检出远程分支的内容: git checkout -b feature-branch origin/feature-branch feature-branch 是你在本地创建的新分支名,origin/feature-branch 是远程分支的引用。 根据你检出的远程分支的名字而定 不知道名称的时…...

C语言学习笔记[24]:循环语句while②
getchar()的使用场景 int main() {char password[20] {0};printf("请输入密码:");//输入 123456 后回车scanf("%s", passwoed);//数组名本身就是数组地址printf("请确认密码:Y/N");int ch getchar();if(Y ch)printf(&…...
安全运营概述
安全运营概述 概述安全运营的工作对内安全运营工作对外安全运营工作品牌建设 概述 安全是一个过程,安全是靠运营出来的,公司会不断的有新业务的变更,新产品的发布,新版本的升级,技术架构的升级,底层系统的…...
spring-cloud和spring-cloud-alibaba的关系
首先Spring Cloud 是什么? Spring Cloud是一系列框架的有序集合,它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发。Spring Cloud提供了微服务架构开发所需的多种组件和工具,如服务发现注册、配置中心、消息总线、负载均…...

持续集成06--Jenkins构建触发器
前言 在持续集成(CI)的实践中,构建触发器是自动化流程中不可或缺的一环。它决定了何时启动构建过程,从而确保代码变更能够及时地得到验证和反馈。Jenkins,作为业界领先的CI/CD工具,提供了多种构建触发器选项…...

根据视图矩阵, 恢复相机的世界空间的位置
根据视图矩阵, 恢复相机的世界空间的位置 一、方法1 glsl 实现: // 从本地局部坐标系(相机空间) 到 世界空间的旋转变换 mat3 getLocal2WorldRotation() {mat3 world2localRotation mat3(viewMatrix[0].xyz,viewMatrix[1].xyz,viewMatrix[2].xyz);return inverse(world2loca…...
使用pytest-playwright截图和录制视频并添加到Allure报告
一、依赖环境 python, version==3.9.5 pytest-playwright, version==0.5.1 allure-pytest, version==2.13.5 pytest, version==6.2.5 allure, version==2.22.0pytest-playwright支持如下命令行参数: Playwright:--browser={chromium,firefox,webkit}Browser engine which …...
pytorch GPU cuda 使用 报错 整理
GPU 使用、报错整理 1. 使用指定GPU(单卡)1.1 方法1:os.environ[CUDA_VISIBLE_DEVICES]1.2 方法2:torch.device(cuda:2)1.3 报错1:RuntimeError: CUDA error: invalid device ordinal CUDA kernel errors might be asy…...
python + Pytest + requests 的接口自动化步骤
pythonpytestrequestallureyaml接口自动化测试项目实战 开发环境准备 1. jdk 下载 Java官网下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 安装: https://blog.csdn.net/VA_AV/article/details/138…...

基于若依的ruoyi-nbcio流程管理系统修正自定义业务表单的回写bug
更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio 演示地址:RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码: h…...

GD32 MCU上电跌落导致启动异常如何解决
大家是否碰到过MCU上电过程中存在电源波动或者电压跌落导致MCU启动异常的问题?本视频将会为大家讲解可能的原因以及解决方法: GD32 MCU上下电复位波形如下图所示,上电过程中如果存在吃电的模块,比如wifi模块/4G模块/开启某块电路…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...

Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
WEB3全栈开发——面试专业技能点P4数据库
一、mysql2 原生驱动及其连接机制 概念介绍 mysql2 是 Node.js 环境中广泛使用的 MySQL 客户端库,基于 mysql 库改进而来,具有更好的性能、Promise 支持、流式查询、二进制数据处理能力等。 主要特点: 支持 Promise / async-await…...

内窥镜检查中基于提示的息肉分割|文献速递-深度学习医疗AI最新文献
Title 题目 Prompt-based polyp segmentation during endoscopy 内窥镜检查中基于提示的息肉分割 01 文献速递介绍 以下是对这段英文内容的中文翻译: ### 胃肠道癌症的发病率呈上升趋势,且有年轻化倾向(Bray等人,2018&#x…...