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");  // 控制 GPIO
    led2_id = mygpio_init(leds_node, "led2_gpio");  // 控制 GPIO
    led3_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);  // 释放 GPIO
    gpio_free(led2_id);  // 释放 GPIO
    gpio_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);  // 打开 LED1
        ioctl(fd, LED_ON, ret+1);  // 打开 LED2
        ioctl(fd, LED_ON, ret+2);  // 打开 LED3
        sleep(1);  // 等待 1 秒
        ioctl(fd, LED_OFF, ret);    // 关闭 LED1
        ioctl(fd, LED_OFF, ret+1);  // 关闭 LED2
        ioctl(fd, LED_OFF, ret+2);  // 关闭 LED3
        sleep(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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/974937.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

瑞芯微RV1126部署YOLOv8全流程:环境搭建、pt-onnx-rknn模型转换、C++推理代码、错误解决、优化、交叉编译第三方库

目录 1 环境搭建 2 交叉编译opencv 3 模型训练 4 模型转换 4.1 pt模型转onnx模型 4.2 onnx模型转rknn模型 4.2.1 安装rknn-toolkit 4.2.2 onn转成rknn模型 5 升级npu驱动 6 C++推理源码demo 6.1 原版demo 6.2 增加opencv读取图片的代码 7 交叉编译x264 ffmepg和op…

如何为自己的 PDF 文件添加密码?在线加密 PDF 文件其实更简单

随着信息泄露和数据安全问题的日益突出&#xff0c;保护敏感信息变得尤为重要。加密 PDF 文件是一种有效的手段&#xff0c;可以确保只有授权用户才能访问或修改文档内容。本文将详细介绍如何使用 CleverPDF 在线工具为你的 PDF 文件添加密码保护&#xff0c;确保其安全性。 为…

蓝桥杯核心内容

核心内容 数学 质数与筛质数&#xff0c;分解质因数 分解质因数 所有的数都可以写成有限个数相乘质数&#xff1a;可以写成1✖本身&#xff08;如131✖13&#xff09;合数&#xff1a;ab1✖...✖bn-》把乘数里面是合数的再分&#xff08;如b3是合数-》b3c1✖c2&#xff09;进…

七星棋牌源码高阶技术指南:6端互通、200+子游戏玩法深度剖析与企业级搭建实战(完全开源)

在棋牌游戏行业高速发展的今天&#xff0c;如何构建一个具备高并发、强稳定性与多功能支持的棋牌游戏系统成为众多开发者和运营团队关注的焦点。七星棋牌全开源修复版源码 凭借其 六端互通、200子游戏玩法、多省区本地化支持&#xff0c;以及 乐豆系统、防沉迷、比赛场、AI智能…

【学习笔记】【SpringCloud】MybatisPlus 基础使用

目录 一、使用 MybatisPlus 基本步骤 1. 引入 MybatisPlus 依赖 2. 定义Mapper接口并继承BaseMapper 二、MybatisPlus 常用配置 三、自定义SQL 四、IService 接口 1. 批量新增的效率问题 2. 配置方式 五、插件功能 1. 分页插件 一、使用 MybatisPlus 基本步骤 1. 引…

QT 引入Quazip和Zlib源码工程到项目中,无需编译成库,跨平台,压缩进度

前言 最近在做项目时遇到一个需求&#xff0c;需要将升级的文件压缩成zip&#xff0c;再进行传输&#xff1b; 通过网络调研&#xff0c;有许多方式可以实现&#xff0c;例如QT私有模块的ZipReader、QZipWriter&#xff1b;或者第三方库zlib或者libzip或者quazip等&#xff1…

在高流量下保持WordPress网站的稳定和高效运行

随着流量的不断增加&#xff0c;网站的稳定和高效运行变得越来越重要&#xff0c;特别是使用WordPress搭建的网站。流量过高时&#xff0c;网站加载可能会变慢&#xff0c;甚至崩溃&#xff0c;直接影响用户体验和网站正常运营。因此&#xff0c;我们需要采取一些有效的措施&am…

linux 安装启动zookeeper全过程及遇到的坑

1、下载安装zookeeper 参考文章&#xff1a;https://blog.csdn.net/weixin_48887095/article/details/132397448 2、启动失败 1、启动失败JAVA_HOME is not set and java could not be found in PATH 已安装 JAVA 配置了JAVA_HOME,还是报错解决方法&#xff1a;参考&#xf…

投资组合风险管理

投资组合风险管理 市场风险 信用风险流动性风险风险指标收益率波动率最大回撤 α \alpha α&#xff08;詹森指数&#xff09;&#xff0c; β \beta β卡玛比率月胜率上/下行捕获比夏普比率索提诺比率经风险调整的收益率&#xff08;&#x1d440;2&#xff09;特雷诺比率信息…

MySQL八股学习笔记

文章目录 一、MySQL结构1.宏观结构1.1.Server层1.2.存储引擎层 2.建立链接-连接器3.查询缓存4.解析SQL-解析器&#xff08;1&#xff09;词法分析&#xff08;2&#xff09;语法分析 5.执行SQL5.1.预处理器 prepare5.2.优化器 optimize5.3.执行器 execute&#xff08;1&#xf…

在windows下安装windows+Ubuntu16.04双系统(下)

这篇文章的内容主要来源于这篇文章&#xff0c;为正式安装windowsUbuntu16.04双系统部分。在正式安装前&#xff0c;若还没有进行前期准备工作&#xff08;1.分区2.制作启动u盘&#xff09;&#xff0c;见《在windows下安装windowsUbuntu16.04双系统(上)》 二、正式安装Ubuntu …

自然语言处理NLP 04案例——苏宁易购优质评论与差评分析

上一篇文章&#xff0c;我们爬取了苏宁易购平台某产品的优质评价和差评&#xff0c;今天我们对优质评价与差评进行分析 selenium爬取苏宁易购平台某产品的评论-CSDN博客 目录 1. 数据加载 2. 中文分词 3. 停用词处理 4. 数据标注与合并 5. 数据集划分 6. 文本特征提取 …

最新版本Exoplayer扩展FFmpeg音频软解码保姆级教程

ExoPlayer 是一个开源的 Android 媒体播放库&#xff0c;由 Google 开发和维护&#xff0c;用于替代 Android 系统自带的 MediaPlayer。它提供了更强大的功能、更好的性能和更高的灵活性&#xff0c;适用于各种复杂的媒体播放场景。所以被广泛用于各种播放器场景。 最近项目中…

华为昇腾910b服务器部署DeepSeek翻车现场

最近到祸一台HUAWEI Kunpeng 920 5250&#xff0c;先看看配置。之前是部署的讯飞大模型&#xff0c;发现资源利用率太低了。把5台减少到3台&#xff0c;就出了他 硬件配置信息 基本硬件信息 按照惯例先来看看配置。一共3块盘&#xff0c;500G的系统盘&#xff0c; 2块3T固态…

【操作系统】操作系统概述

操作系统概述 1.1 操作系统的概念1.1.1 操作系统定义——什么是OS&#xff1f;1.1.2 操作系统作用——OS有什么用&#xff1f;1.1.3 操作系统地位——计算机系统中&#xff0c;OS处于什么地位&#xff1f;1.1.4 为什么学操作系统&#xff1f; 1.2 操作系统的历史1.2.1 操作系统…

使用Dify将AI机器人嵌入到你的前端页面中及chrome的扩展应用

目录 1 博主有话说2 前提环境3 Dify创建个聊天助手应用4 将AI聊天机器人嵌入到html中5 将AI聊天机器人设置为chrome的扩展应用6 博主增语 1 博主有话说 那博主话不多说&#xff0c;先展示一下成果&#xff01; 这个界面是使用dify配置的一个“聊天助手”的应用&#xff0c;助…

Django REST Framework (DRF) 中用于构建 API 视图类解析

Django REST Framework (DRF) 提供了丰富的视图类&#xff0c;用于构建 API 视图。这些视图类可以分为以下几类&#xff1a; 1. 基础视图类 这些是 DRF 中最基础的视图类&#xff0c;通常用于实现自定义逻辑。 常用类 APIView&#xff1a; 最基本的视图类&#xff0c;所有其…

数据结构-图-找出星型图的中心节点

力扣题目&#xff1a;1791. 找出星型图的中心节点 - 力扣&#xff08;LeetCode&#xff09; 有一个无向的 星型 图&#xff0c;由 n 个编号从 1 到 n 的节点组成。星型图有一个 中心 节点&#xff0c;并且恰有 n - 1 条边将中心节点与其他每个节点连接起来。 给你一个二维整数…

计算机网络之物理层——基于《计算机网络》谢希仁第八版

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;数据结构&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;网络编程等领域UP&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff0…

VScode C语言学习开发环境;运行提示“#Include错误,无法打开源文件stdio.h”

C/C环境配置 参考&#xff1a; VS Code 配置 C/C 编程运行环境&#xff08;保姆级教程&#xff09;_vscode配置c环境-CSDN博客 基本步骤 - 安装MinGW-W64&#xff0c;其包含 GCC 编译器&#xff1a;bin目录添加到环境变量&#xff1b;CMD 中输入gcc --version或where gcc验证…