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

news/2025/2/27 4:52:30/

需求整理

在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


http://www.ppmy.cn/news/1575176.html

相关文章

lua-游戏红点提示系统抽象设计

文章目录 前言一、定义红点节点类型二、节点注册与管理三、状态更新与冒泡机制 四、示例配置与使用五、结构示意图六、关键机制说明总结 前言 在游戏开发中&#xff0c;红点提示系统可以通过树形结构和策略模式进行抽象&#xff0c;实现高扩展性。以下是基于Lua的实现方案&…

R 语言科研绘图第 27 期 --- 密度图-分组

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…

【论文精读】VLM-AD:通过视觉-语言模型监督实现端到端自动驾驶

论文地址&#xff1a; VLM-AD: End-to-End Autonomous Driving through Vision-Language Model Supervision 摘要 人类驾驶员依赖常识推理来应对复杂多变的真实世界驾驶场景。现有的端到端&#xff08;E2E&#xff09;自动驾驶&#xff08;AD&#xff09;模型通常被优化以模仿…

Nmap网络安全审计

&#x1f345; 点击文末小卡片 &#xff0c;免费获取网络安全全套资料&#xff0c;资料在手&#xff0c;涨薪更快 Nmap网络安全审计 什么是Nmap Nmap是由Gordon Lyon设计并实现的&#xff0c;于1997开始发布。最初设计Nmap的目的只是希望打造一款强大的端口扫描工具。但是随着…

【Http和Https区别】

概念&#xff1a; 一、Http协议 HTTP&#xff08;超文本传输协议&#xff09;是一种用于传输超媒体文档&#xff08;如HTML&#xff09;的应用层协议&#xff0c;主要用于Web浏览器和服务器之间的通信。http也是客户端和服务器之间请求与响应的标准协议&#xff0c;客户端通常…

Git版本控制系统---本地操作(万字详解!)

目录 git基本配置 认识工作区、暂存区、版本库 添加文件--情况一&#xff1a; 添加文件-情况二: 修改文件: 版本回退&#xff1a; git基本配置 1.初始化本地仓库&#xff0c;注意&#xff1a;一定要在一个目录下进行&#xff0c;一般都是新建一个文件夹&#xff0c;在文件…

斐波那契数列模型:在动态规划的丝绸之路上追寻斐波那契的足迹(上)

文章目录 引言递归与动态规划的对比递归解法的初探动态规划的优雅与高效自顶向下的记忆化搜索自底向上的迭代法 性能分析与比较小结 引言 斐波那契数列&#xff0c;这一数列如同一条无形的丝线&#xff0c;穿越千年时光&#xff0c;悄然延续其魅力。其定义简单而优美&#xff…

DeepSeek 全面分析报告

引言 DeepSeek 是一款由中国人工智能初创公司 DeepSeek 开发的大型语言模型 (LLM)&#xff0c;于 2025 年 1 月发布&#xff0c;迅速成为全球人工智能领域的一匹黑马。DeepSeek 不仅在性能上可与 OpenAI、Google 等巨头的模型相媲美&#xff0c;而且其训练成本和运行效率都显著…