当前位置: 首页 > article >正文

STM32MP157A单片机移植Linux驱动深入版

需求整理

在Linux设备树中新增leds节点,其有3个gpio属性,分别表示PE10对应led1,PF10对应led2,PE8对应led3,设备树键值对如下:

    leds {
        led1-gpio = <&gpioe 10 0>;
        led2-gpio = <&gpiof 10 0>;
        led3-gpio = <&gpioe 8 0>;
    };

内核驱动实现对灯控模块的初始化函数、模块退出函数、灯控模块各回调函数(open/release/unlocked_ioctl/read/write)。

应用程序实现对灯控模块的控制,通过ioctl函数控制led亮灭。

驱动开发逻辑分析

1.驱动初始化

注册字符设备 --> register_chrdev

申请一个struct class结构体,保存当前设备类的信息 --> class_create

申请一个struct device结构体,保存当前设备节点的信息 --> device_create

通过名称查找设备节点 --> of_find_node_by_name

2.初始化GPIO

获取GPIO编号 --> of_get_named_gpio

请求GPIO --> gpio_request

设置GPIO方向为输出 --> gpio_direction_output

3.ioctl回调函数

获取应用程序发送的值 --> copy_from_user

处理应用程序发送的功能码 --> 回调函数中的第二个参数的值(一般有应用程序通过ioctl命令发送)

4.驱动退出

释放GPIO --> gpio_free

注销字符设备文件 --> device_destroy

注销字符设备类 --> class_destroy

注销字符设备 --> unregister_chrdev

5.指定模块

指定模块初始化函数 --> module_init

指定模块注销函数 --> module_exit

应用程序开发逻辑分析

1.打开字符设备文件 --> open

2.发送功能码和值给驱动 --> ioctl

3.功能码 --> _IO() / _IOW / _IOR / _IOWR ...

详细代码

驱动程序 --> leds.c
#include <linux/init.h>       // 包含内核初始化相关的头文件
#include <linux/module.h>     // 包含内核模块相关的头文件
#include <linux/of.h>         // 包含设备树操作相关的头文件
#include <linux/gpio.h>       // 包含 GPIO 操作相关的头文件
#include <linux/of_gpio.h>    // 包含设备树 GPIO 相关的头文件
#include <linux/fs.h>         // 包含文件操作相关的头文件
#include <linux/uaccess.h>    // 包含用户空间访问内核空间相关的头文件
#include <linux/device.h>     // 包含设备相关的头文件
#include "leds.h"            // 包含自定义头文件/* 设备树节点定义leds {led1-gpio = <&gpioe 10 0>;led2-gpio = <&gpiof 10 0>;led3-gpio = <&gpioe 8 0>;};
*/
static struct class *led_class;
static struct device *led_device;
static struct device_node *leds_node;  // 定义设备节点指针
static char kernel_buf[100];  // 定义缓冲区
int led1_id,led2_id,led3_id;                     // 定义 GPIO 编号
int led_major;  // 定义主设备号static int led_open(struct inode *inode, struct file *file);
static int led_close(struct inode *inode, struct file *file);
static int led_read(struct file *file, char __user *buf, size_t count, loff_t *ppos);
static int led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos);
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg);struct file_operations fops = {.open = led_open,.release = led_close,.unlocked_ioctl = led_ioctl,.read = led_read,.write = led_write,
};static int led_open(struct inode *inode, struct file *file)
{printk("led_open\n");return 0;
}static int led_close(struct inode *inode, struct file *file)
{printk("led_close\n");return 0;
}static int led_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{uint32_t n = copy_to_user(buf, kernel_buf, count);if(n){printk("copy_to_user failed\n");return -EFAULT;}printk("led_read\n");return 0;
}static int led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{uint32_t n = copy_from_user(kernel_buf, buf, count);if(n){printk("copy_from_user failed\n");return -EFAULT;}printk("led_write\n");return 0;
}static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{//获取arg的值unsigned int value;if(copy_from_user(&value, (unsigned int *)arg, sizeof(unsigned int))){printk("copy_from_user failed\n");return -EFAULT;}switch(cmd){case LED_ON:switch(value){case 1:gpio_set_value(led1_id, 1);break;case 2:gpio_set_value(led2_id, 1);break;case 3:gpio_set_value(led3_id, 1);break;default:    printk("cmd error\n");return -EINVAL;}break;case LED_OFF:switch(value){case 1:gpio_set_value(led1_id, 0);break;case 2:gpio_set_value(led2_id, 0);break;case 3:gpio_set_value(led3_id, 0);break;default:    printk("cmd error\n");return -EINVAL;}break;default:printk("cmd error\n");return -EINVAL;}printk("led_ioctl\n");return 0;
}//通过设备树的属性名查找gpio,并初始化gpio
static int mygpio_init(struct device_node *np ,const char *name)
{int id;printk("name=%s\n", name);  // 打印属性名id = of_get_named_gpio(np, name, 0);  // 获取 GPIO 编号if (id < 0)  // 如果获取 GPIO 编号失败{printk("get gpio number failed\n");  // 打印获取 GPIO 编号失败的消息return -ENODEV;  // 返回错误码}printk("get gpio number success\n");  // 打印获取 GPIO 编号成功的消息if(gpio_request(id, NULL))  // 请求 GPIO{printk("request gpio failed\n");  // 打印请求 GPIO 失败的消息return -ENODEV;  // 返回错误码}   printk("request gpio success\n");  // 打印请求 GPIO 成功的消息if(gpio_direction_output(id, 0))  // 设置 GPIO 方向为输出{printk("set gpio direction failed\n");  // 打印设置 GPIO 方向失败的消息return -ENODEV;  // 返回错误码}printk("set gpio direction success\n");  // 打印设置 GPIO 方向成功的消息return id;  // 返回 GPIO 编号
}static int __init leds_init(void)  // 模块初始化函数
{//字符设备注册led_major = register_chrdev(0, "leds_control", &fops);if(led_major < 0){printk("register_chrdev failed\n");return -ENODEV;}printk("register_chrdev success:led_major = %d\n", led_major);//申请一个struct class结构体,保存当前设备类的信息led_class = class_create(THIS_MODULE, "leds_control");if(IS_ERR(led_class)){printk("class_create failed\n");unregister_chrdev(led_major, "leds_control");   // 注销字符设备return -ENODEV;}printk("class_create success\n");//申请一个struct device结构体,保存当前设备节点的信息led_device = device_create(led_class, NULL, MKDEV(led_major, 0), NULL, "leds_control");if(IS_ERR(led_device)){printk("device_create failed\n");class_destroy(led_class);  // 注销类unregister_chrdev(led_major, "leds_control");   // 注销字符设备return -ENODEV;}printk("device_create success\n");leds_node = of_find_node_by_name(NULL, "leds");  // 通过名称查找设备节点//leds_node = of_find_compatible_node(NULL, NULL, "sjh,mynode");  // 通过兼容字符串查找设备节点if (!leds_node)  // 如果未找到设备节点{printk("mynode node not found\n");  // 打印未找到节点的消息device_destroy(led_class, MKDEV(led_major, 0));  // 注销设备class_destroy(led_class);  // 注销类unregister_chrdev(led_major, "leds_control");   // 注销字符设备return -ENODEV;  // 返回错误码}printk("mynode node found\n");  // 打印找到节点的消息led1_id = mygpio_init(leds_node, "led1_gpio");  // 控制 GPIOled2_id = mygpio_init(leds_node, "led2_gpio");  // 控制 GPIOled3_id = mygpio_init(leds_node, "led3_gpio");  // 控制 GPIO/*    printk("name=%s,value=%s\n", leds_node->properties->name, (char *)leds_node->properties->value);  // 打印第一个属性的名称和值printk("name=%s,value=%s\n", leds_node->properties->next->name, (char *)leds_node->properties->next->value);  // 打印第二个属性的名称和值printk("name=%s,value=%x %x\n", leds_node->properties->next->next->name, __be32_to_cpup((uint32_t *)leds_node->properties->next->next->value), __be32_to_cpup((uint32_t *)leds_node->properties->next->next->value + 1));  // 打印第三个属性的名称和值(无符号整数)// 解析设备树的属性binarry = of_find_property(leds_node, "binarry", &len);  // 查找名为 "binarry" 的属性if (!binarry)  // 如果未找到属性{printk("binarry property not found\n");  // 打印未找到属性的消息return -ENODEV;  // 返回错误码}for (i = 0; i < len; i++)  // 遍历属性值{printk("%02x ", *((unsigned char *)binarry->value + i));  // 打印属性值的每个字节} */return 0;  // 返回成功
}static void __exit leds_exit(void)  // 模块退出函数
{// 退出时执行的清理操作(当前为空)gpio_free(led1_id);  // 释放 GPIOgpio_free(led2_id);  // 释放 GPIOgpio_free(led3_id);  // 释放 GPIO//字符设备注销device_destroy(led_class, MKDEV(led_major, 0));  // 注销设备class_destroy(led_class);  // 注销类unregister_chrdev(led_major, "leds_control");   // 注销字符设备printk("exit\n");  // 打印退出消息
}module_init(leds_init);  // 指定模块初始化函数
module_exit(leds_exit);  // 指定模块退出函数
MODULE_LICENSE("GPL");  // 指定模块许可证为 GPL
MODULE_AUTHOR("Johnson");  // 指定模块作者
MODULE_DESCRIPTION("leds driver");  // 指定模块描述
MODULE_VERSION("V1.0");  // 指定模块版本
应用程序 --> test_app.c

实现按1s间隔控制三个led灯亮灭

#include<stdlib.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
#include<string.h>
#include<sys/ioctl.h>
#include "leds.h"            // 包含自定义头文件int main(int argc,const char * argv[])
{int fd;int ret[3] = {1,2,3};    // 定义返回值int value;  // 定义变量fd = open("/dev/leds_control", O_RDWR);  // 打开设备文件if(fd < 0)  // 如果打开设备文件失败{perror("open");  // 打印错误信息return -1;  // 返回错误码}printf("open success\n");  // 打印打开设备文件成功的消息while(1){ioctl(fd, LED_ON, ret);  // 打开 LED1ioctl(fd, LED_ON, ret+1);  // 打开 LED2ioctl(fd, LED_ON, ret+2);  // 打开 LED3sleep(1);  // 等待 1 秒ioctl(fd, LED_OFF, ret);    // 关闭 LED1ioctl(fd, LED_OFF, ret+1);  // 关闭 LED2ioctl(fd, LED_OFF, ret+2);  // 关闭 LED3sleep(1);  // 等待 1 秒}return 0;
}
头文件 --> leds.h
#ifndef __LEDS_H__
#define __LEDS_H__#define LED_ON _IOW('l', 1, int)
#define LED_OFF _IOW('l', 0, int)#endif
结果
加载内核模块 --> insmod leds.ko

 查看自动生成的字符设备文件 --> ls /dev/

 执行应用程序 --> ./a.out

相关文章:

STM32MP157A单片机移植Linux驱动深入版

需求整理 在Linux设备树中新增leds节点&#xff0c;其有3个gpio属性&#xff0c;分别表示PE10对应led1&#xff0c;PF10对应led2&#xff0c;PE8对应led3&#xff0c;设备树键值对如下&#xff1a; leds { led1-gpio <&gpioe 10 0>; led2-gpio &l…...

NLP在市场情报分析中的应用:解析数据驱动的营销新时代

NLP在市场情报分析中的应用:解析数据驱动的营销新时代 在当今信息爆炸的时代,市场情报分析已成为企业决策和市场策略的重要工具。自然语言处理(Natural Language Processing, NLP)作为人工智能领域的一个重要分支,为市场情报分析带来了全新的可能。作为人工智能和Python领…...

[大模型笔记]扣子-知识库搭建,并用Java-SDK调用的笔记

记录一下学习coze官方提供的java-sdk的过程 官方参考文档 一、搭建知识库 1、登录coze后&#xff0c;点击工作空间-资源库&#xff0c;点击右上角的资源&#xff0c;点击知识库 2、输入知识库名词以及知识库的描述 3、选择要上传的文档类型&#xff0c;点击创建并导入&…...

Unity学习笔记-Unity了解,安装,简单配置(一)

Unity 是什么&#xff1f; Unity 是一款广受欢迎的跨平台游戏开发引擎&#xff0c;由 Unity Technologies 公司开发并推出。它以强大的功能和易用性&#xff0c;在游戏开发领域占据着举足轻重的地位&#xff0c;甚至可以说&#xff0c;它改变了游戏开发的格局。凭借其出色的跨…...

LLaMA-Factory|微调大语言模型初探索(3),qlora微调deepseek记录

前言 上篇文章记录了使用lora微调llama-1b,微调成功,但是微调llama-8b显存爆炸,这次尝试使用qlora来尝试微调参数体量更大的大语言模型,看看64G显存的极限在哪里。 1.Why QLora? QLoRA 在模型加载阶段通过 4-bit 量化大幅减少了模型权重的显存占用。QLoRA 通过 反量化到 …...

手动配置 Yum 仓库

在我使用虚拟机&#xff0c;系统在尝试访问CentOS的镜像列表时遇到了网络问题&#xff0c;具体表现为无法解析mirrorlist.centos.org 于是手动配置yum仓库 备份现有的 repo 文件 sudo mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 创建新…...

JEEWMS cgFormBuildController.do 方法mobileForm存在SQL注入

一:登录扫描 JeeWMS是一款免费开源的仓库管理系统,支持3PL和厂内物流,涵盖订单管理,仓储管理,计费管理,现场作业,RFID,AGV等功能。本文介绍了系统的简介,功能,安装,截图和链接,适合仓储企业和开发者参考。厦门市灵鹿谷科技有限公司JEEWMS jeecgFormDemoController…...

【二分搜索 C/C++】洛谷 P1873 EKO / 砍树

2025 - 02 - 19 - 第 55 篇 Author: 郑龙浩 / 仟濹(CSND) 【二分搜索】 文章目录 洛谷 P1873 EKO / 砍树题目描述输入格式输出格式输入输出样例 #1输入 #1输出 #1 输入输出样例 #2输入 #2输出 #2 说明/提示题目中的部分变量思路代码 洛谷 P1873 EKO / 砍树 题目描述 伐木工人…...

python面试题整理

Python 如何处理异常&#xff1f; Python中&#xff0c;使用try 和 except 关键字来捕获和处理异常 try 块中放置可能会引发异常的代码&#xff0c;然后在except块中处理这些异常。 能补充一下finally的作用吗&#xff1f; finally 块中的代码无论是否发生异常都会执行&#xf…...

深度学习之图像回归(二)

前言 这篇文章主要是在图像回归&#xff08;一&#xff09;的基础上对该项目进行的优化。&#xff08;一&#xff09;主要是帮助迅速入门 理清一个深度学习项目的逻辑 这篇文章则主要注重在此基础上对于数据预处理和模型训练进行优化前者会通过涉及PCA主成分分析 特征选择 后…...

中文Build a Large Language Model (From Scratch) 免费获取全文

中文pdf下载地址&#xff1a;https://pan.baidu.com/s/1aq2aBcWt9vYagT2-HuxdWA?pwdlshj 提取码&#xff1a;lshj 原文、代码、视频项目地址&#xff1a;https://github.com/rasbt/LLMs-from-scratch 翻译工具&#xff1a;沉浸式翻译&#xff08;https://app.immersivetrans…...

【鸿蒙开发】第四十四章 Map Kit(地图服务)

目录​​​​​​​ 1 Map Kit简介 1.1 场景介绍 2 开发准备 开通地图服务 3 创建地图 3.1 显示地图 3.1.1 接口说明 3.1.2 开发步骤 1、地图显示 2、设置地图属性 3、开启3D建筑图层 4、地图前后台切换 5、深色模式 3.2 切换地图类型 3.2.1 场景介绍 3.2.2 接…...

EasyExcel 自定义头信息导出

需求&#xff1a;需要在导出 excel时&#xff0c;合并单元格自定义头信息(动态生成)&#xff0c;然后才是字段列表头即导出数据。 EasyExcel - 使用table去写入&#xff1a;https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write#%E4%BD%BF%E7%94%A8table%E…...

Angular 中获取 DOM 节点的几种方法

文章目录 1. 使用ViewChild获取单个 DOM 节点2. 使用ViewChildren获取多个 DOM 节点3. 使用ElementRef直接访问 DOM4. 使用Renderer2操作 DOM5. 总结 在 Angular 开发中&#xff0c;虽然框架鼓励我们通过组件和模板来操作 DOM&#xff0c;但在某些情况下&#xff0c;直接访问和…...

索引的优缺点与常见类型详解

索引是数据库优化的核心工具&#xff0c;但盲目使用可能适得其反。本文将系统梳理索引的缺点、常见类型及适用场景&#xff0c;助你避开常见陷阱。 一、索引的缺点 虽然索引能加速查询&#xff0c;但并非“免费午餐”&#xff0c;需警惕以下代价&#xff1a; 1. 存储空间开销…...

DeepSeek 提示词:定义、作用、分类与设计原则

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…...

ubuntu环境编译ffmepg支持nvidia显卡加速

文章目录 1. 安装NVIDIA驱动2. 安装CUDA&NV-CODEC2.1 安装CUDA2.2 安装NV-CODEC 3. 编译ffmpeg3.1 安装依赖3.2 下载源码安装依赖3.3 验证 4. 使用 1. 安装NVIDIA驱动 安装依赖包 sudo apt install -y ubuntu-drivers-common编辑 /etc/modprobe.d/blacklist-nouveau.conf 文…...

淘宝商品评论API调用教程:轻松获取用户评价数据(含测试Key)

在电商开发中&#xff0c;用户评价数据是优化产品和提升用户体验的重要依据。淘宝提供了商品评论API&#xff0c;方便开发者获取商品的用户评价信息。本文将详细介绍如何调用淘宝商品评论API&#xff0c;并附上测试Key供调试使用。 一、准备工作 注册淘宝开放平台账号 前往注册…...

边缘安全加速(Edge Security Acceleration)

边缘安全加速&#xff08;Edge Security Acceleration&#xff0c;简称ESA&#xff09;是一种通过将安全功能与网络边缘紧密结合来提升安全性和加速网络流量的技术。ESA的目标是将安全措施部署到接近用户或设备的地方&#xff0c;通常是在网络的边缘&#xff0c;而不是将所有流…...

SpringCould+vue3项目的后台用户管理的CURD【Taurus教育平台】

文章目录 一.SpringCouldvue3项目的后台用户管理的CURD【Taurus教育平台】 1.1 背景 二.用户列表&#xff08;分页查询&#xff09; 2.1 前端Vue3 &#xff08;Vue3-Element-Admin&#xff09;2.2 后端SpringCould 处理 三. 用户信息删除 3.1 前端Vue3 &#xff08;Vue3-Eleme…...

ROS-相机话题-获取图像-颜色目标识别与定位-目标跟随-人脸检测

文章目录 相机话题获取图像颜色目标识别与定位目标跟随人脸检测 相机话题 启动仿真 roslaunch wpr_simulation wpb_stage_robocup.launch rostopic hz /kinect2/qhd/image_color_rect/camera/image_raw&#xff1a;原始的、未经处理的图像数据。 /camera/image_rect&#xff…...

破解Docker镜像拉取难题:为Docker配置代理加速镜像拉取

为Docker配置代理加速镜像拉取 概述守护进程配置&#xff08;推荐长期使用&#xff09;Systemd环境变量配置&#xff08;适合临时调整&#xff09;其他 概述 为什么需要配置代理与镜像加速? 跨国网络限制&#xff1a;境外镜像仓库拉取速度慢或无法访问企业安全策略&#xff…...

细分数字货币钱包的不同种类

文章目录 一、中心化钱包1.1 中心化钱包架构1.2 中心化钱包业务细节流程 二、去中心化钱包(HD 钱包)2.1 去中心化钱包架构2.2 去中心化钱包细节业务流程 三、硬件钱包3.1 硬件钱包架构3.2 硬件钱包细节业务流程 四、MPC 托管钱包五、多签钱包 中心化钱包 &#xff1a;钱包私钥一…...

推理模型时代:大语言模型如何从对话走向深度思考?

一、对话模型和推理模型的区别概述 对话模型是专门用于问答交互的语言模型,符合人类的聊天方式,返回的内容可能仅仅只是一个简短的答案,一般模型名称后面会带有「chat」字样。 推理模型是比较新的产物,没有明确的定义,一般是指输出过程中带有<think>和</think&…...

调用click.getchar()时Windows PyCharm无法模拟键盘输入

文章目录 问题描述解决方案参考文献 问题描述 调用 click.getchar() 时&#xff0c;Windows PyCharm 无法模拟键盘输入 解决方案 Run → Edit Configurations… → Modify options → Emulate terminal in output console 参考文献 Terminal emulator | PyCharm Documentati…...

关于ES中text类型时间字段范围查询的结构化解决方案

前言 有关es中text类型的时间字段范围查询的问题&#xff0c;比如&#xff1a; {"query": {"range": {"insertTime": {"gte": "2025-02-01T00:00:00","lte": "2025-11-30T23:59:59","format&quo…...

易基因: ChIP-seq+DRIP-seq揭示AMPK通过调控H3K4me3沉积和R-loop形成以维持基因组稳定性和生殖细胞完整性|NAR

原文&#xff1a;ChIP-seqDRIP-seq揭示AMPK通过调控H3K4me3沉积和R-loop形成以维持基因组稳定性和生殖细胞完整性&#xff5c;NAR 大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 在饥饿等能量胁迫条件下&#xff0c;生物体会通过调整…...

Web 自动化测试提速利器:Aqua 的 Web Inspector (检查器)使用详解

Web 自动化测试提速利器:Aqua 的 Web Inspector (检查器)使用详解 前言简介一、安装二、Web Inspector 的使用2.1 获取元素定位器(Locators)2.2 将定位器添加到代码2.3 验证定位器2.4 处理 Frames (框架)总结前言 JetBrains 的 Aqua IDE 提供强大的 Web Inspector 工具,帮…...

数据中心储能蓄电池状态监测管理系统 组成架构介绍

安科瑞刘鸿鹏 摘要 随着数据中心对供电可靠性要求的提高&#xff0c;蓄电池储能系统成为关键的后备电源。本文探讨了蓄电池监测系统在数据中心储能系统中的重要性&#xff0c;分析了ABAT系列蓄电池在线监测系统的功能、技术特点及其应用优势。通过蓄电池监测系统的实施&#…...

01数据准备 抓取图片 通过爬虫方式获取bing的关键词搜索图片

为了获取训练所需的图片,我们最常用的手段就是自己去写一个爬虫去获取相关图片。本文将重点围绕如何采用爬虫的方式获取训练所需的图片素材进行讲解,为了大家能够够直观的掌握相关技术,参考本文的相关过程和代码获取自己的数据图片素材,笔者将详细介绍实现过程。 1、确定图…...