11.2 设备树下的 LED 驱动

一、修改设备树文件

  首先进入该目录下 /linux/atk-mpl/linux/my_linux/linux-5.4.31/arch/arm/boot/dts 打开  stm32mp157d-atk.dts 文件,在根节点 "/" 最后输入以下内容:

stm32mp1_led {compatible = "atkstm32mp1-led";    // 设置 stm32mp1_led 节点兼容为“atkstm32mp1-led”status = "okay";    // 状态为 okay/* reg属性设置了驱动里面所要使用的寄存器物理地址 */reg = <0X50000A28 0X04 // 表示 STM32MP1 的 RCC_MP_AHB4ENSETR 寄存器,其中地址为0X50000A28,长度为0X040X5000A000 0X04 /* GPIOI_MODER */0X5000A004 0X04 /* GPIOI_OTYPER */0X5000A008 0X04 /* GPIOI_OSPEEDR */0X5000A00C 0X04 /* GPIOI_PUPDR */0X5000A018 0X04 >; /* GPIOI_BSRR */
};

  之后编译 stm32mp157d-atk.dts,命令如下:

cd /linux/atk-mpl/linux/my_linux/linux-5.4.31
make dtbs
# 之后会产生 stm32mp157d-atk.dtb 文件

  编译完成这个文件后,我是使用 tftpboot 来去读系统文件,先将 stm32mp157d-atk.dtb 放在 /linux/tftpboot 中,不要忘记把这个文件给权限(chmod 777 xxx),之后在 uboot 输入命令:

setenv bootcmd 'tftp c2000000 uImage;tftp c4000000 stm32mp157d-atk.dtb;bootm c2000000 - c4000000'
saveenv     # 时刻记着不要忘记保存,之前我就说为什么一直只能改一次,发现是这个原因
boot

  最后可以进入 stm32mp1_led 目录中,查看属性文件。

二、LED 驱动编写

  在 /linux/atk-mpl/Drivers 目录创建新的子目录 4_dtsled,并且创建好 Vscode工作区 dtsled 和 新的 dtsled.c 文件,在 dtsled.c 输入以下内容:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define DTSLED_CNT			    1		  	/* 设备号个数 */
#define DTSLED_NAME			"dtsled"	    /* 名字 */
#define LEDOFF 					0			/* 关灯 */
#define LEDON 					1			/* 开灯 *//* 映射后的寄存器虚拟地址指针 */
static void __iomem *MPU_AHB4_PERIPH_RCC_PI;
static void __iomem *GPIOI_MODER_PI;
static void __iomem *GPIOI_OTYPER_PI;
static void __iomem *GPIOI_OSPEEDR_PI;
static void __iomem *GPIOI_PUPDR_PI;
static void __iomem *GPIOI_BSRR_PI;/* dtsled设备结构体 */
struct dtsled_dev{dev_t devid;			 /* 设备号 */struct cdev cdev;		 /* cdev */struct class *class;	 /* 类 */struct device *device;	 /* 设备 */int major;				 /* 主设备号	*/int minor;				 /* 次设备号 */struct device_node	*nd;  // 设备结构体 dtsled_dev 中添加了成员变量 nd, nd 是 device_node 结构体类型指针,表示设备节点/* 如果我们要读取设备树某个节点的属性值,首先要先得到这个节点,一般在设备结构体中添加 device_node 指针变量来存放这个节点 */
};struct dtsled_dev dtsled;	/* led设备 *//** @description		: LED打开/关闭* @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED* @return 			: 无*/
void led_switch(u8 sta)
{u32 val = 0;if(sta == LEDON) {val = readl(GPIOI_BSRR_PI);val |= (1 << 16);	writel(val, GPIOI_BSRR_PI);}else if(sta == LEDOFF) {val = readl(GPIOI_BSRR_PI);val|= (1 << 0);	writel(val, GPIOI_BSRR_PI);}	
}/** @description		: 取消映射* @return 			: 无*/
void led_unmap(void)
{/* 取消映射 */iounmap(MPU_AHB4_PERIPH_RCC_PI);iounmap(GPIOI_MODER_PI);iounmap(GPIOI_OTYPER_PI);iounmap(GPIOI_OSPEEDR_PI);iounmap(GPIOI_PUPDR_PI);iounmap(GPIOI_BSRR_PI);
}/** @description		: 打开设备* @param - inode 	: 传递给驱动的inode* @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量* 					  一般在open的时候将private_data指向设备结构体。* @return 			: 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{filp->private_data = &dtsled; /* 设置私有数据 */return 0;
}/** @description		: 从设备读取数据 * @param - filp 	: 要打开的设备文件(文件描述符)* @param - buf 	: 返回给用户空间的数据缓冲区* @param - cnt 	: 要读取的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 读取的字节数,如果为负值,表示读取失败*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** @description		: 向设备写数据 * @param - filp 	: 设备文件,表示打开的文件描述符* @param - buf 	: 要写给设备写入的数据* @param - cnt 	: 要写入的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 写入的字节数,如果为负值,表示写入失败*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0];		/* 获取状态值 */if(ledstat == LEDON) {	led_switch(LEDON);		/* 打开LED灯 */} else if(ledstat == LEDOFF) {led_switch(LEDOFF);	/* 关闭LED灯 */}return 0;
}/** @description		: 关闭/释放设备* @param - filp 	: 要关闭的设备文件(文件描述符)* @return 			: 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{return 0;
}/* 设备操作函数 */
static struct file_operations dtsled_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = 	led_release,
};/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static int __init led_init(void)
{u32 val = 0;int ret;u32 regdata[12];const char *str;struct property *proper;/* 获取设备树中的属性数据 *//* 1、获取设备节点:stm32mp1_led */dtsled.nd = of_find_node_by_path("/stm32mp1_led");  // 通过路径去寻找stm32mp1_led节点if(dtsled.nd == NULL) {printk("stm32mp1_led node nost find!\r\n");return -EINVAL;} else {printk("stm32mp1_lcd node find!\r\n");}/* 2、获取compatible属性内容 */proper = of_find_property(dtsled.nd, "compatible", NULL);   // 获取 stm32mp1_led 节点的 compatible 属性,返回值为 property 结构体类型指针变量, property 的成员变量 value 表示属性值if(proper == NULL) {printk("compatible property find failed\r\n");} else {printk("compatible = %s\r\n", (char*)proper->value);}/* 3、获取status属性内容 */ret = of_property_read_string(dtsled.nd, "status", &str);   // 获取 stm32mp1_led 节点的 status 属性值if(ret < 0){printk("status read failed!\r\n");} else {printk("status = %s\r\n",str);}/* 4、获取reg属性内容 */ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 12);  // stm32mp1_led 节点的 reg 属性所有值,并且将获取到的值都存放到 regdata 数组中if(ret < 0) {printk("reg property read failed!\r\n");} else {u8 i = 0;printk("reg data:\r\n");for(i = 0; i < 12; i++)printk("%#X ", regdata[i]);printk("\r\n");}/* 初始化LED *//* 1、寄存器地址映射 */MPU_AHB4_PERIPH_RCC_PI = of_iomap(dtsled.nd, 0);        // 这里之前都是用 ioremap,现在这里用的是 of_iomap,最大的区别就是前者使用不使用设备树,后者使用设备树GPIOI_MODER_PI = of_iomap(dtsled.nd, 1);GPIOI_OTYPER_PI = of_iomap(dtsled.nd, 2);GPIOI_OSPEEDR_PI = of_iomap(dtsled.nd, 3);GPIOI_PUPDR_PI = of_iomap(dtsled.nd, 4);GPIOI_BSRR_PI = of_iomap(dtsled.nd, 5);/* 2、使能PI时钟 */val = readl(MPU_AHB4_PERIPH_RCC_PI);val &= ~(0X1 << 8); /* 清除以前的设置 */val |= (0X1 << 8);  /* 设置新值 */writel(val, MPU_AHB4_PERIPH_RCC_PI);/* 3、设置PI0通用的输出模式。*/val = readl(GPIOI_MODER_PI);val &= ~(0X3 << 0); /* bit0:1清零 */val |= (0X1 << 0);  /* bit0:1设置01 */writel(val, GPIOI_MODER_PI);/* 3、设置PI0为推挽模式。*/val = readl(GPIOI_OTYPER_PI);val &= ~(0X1 << 0); /* bit0清零,设置为上拉*/writel(val, GPIOI_OTYPER_PI);/* 4、设置PI0为高速。*/val = readl(GPIOI_OSPEEDR_PI);val &= ~(0X3 << 0); /* bit0:1 清零 */val |= (0x2 << 0); /* bit0:1 设置为10*/writel(val, GPIOI_OSPEEDR_PI);/* 5、设置PI0为上拉。*/val = readl(GPIOI_PUPDR_PI);val &= ~(0X3 << 0); /* bit0:1 清零*/val |= (0x1 << 0); /*bit0:1 设置为01*/writel(val,GPIOI_PUPDR_PI);/* 6、默认关闭LED */val = readl(GPIOI_BSRR_PI);val |= (0x1 << 0);writel(val, GPIOI_BSRR_PI);/* 注册字符设备驱动 *//* 1、创建设备号 */if (dtsled.major) {		/*  定义了设备号 */dtsled.devid = MKDEV(dtsled.major, 0);ret = register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);if(ret < 0) {pr_err("cannot register %s char driver [ret=%d]\n",DTSLED_NAME, DTSLED_CNT);goto fail_map;}} else {						/* 没有定义设备号 */ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);	/* 申请设备号 */if(ret < 0) {pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", DTSLED_NAME, ret);goto fail_map;}dtsled.major = MAJOR(dtsled.devid);	/* 获取分配号的主设备号 */dtsled.minor = MINOR(dtsled.devid);	/* 获取分配号的次设备号 */}printk("dtsled major=%d,minor=%d\r\n",dtsled.major, dtsled.minor);	/* 2、初始化cdev */dtsled.cdev.owner = THIS_MODULE;cdev_init(&dtsled.cdev, &dtsled_fops);/* 3、添加一个cdev */ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);if(ret < 0)goto del_unregister;/* 4、创建类 */dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);if (IS_ERR(dtsled.class)) {goto del_cdev;}/* 5、创建设备 */dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);if (IS_ERR(dtsled.device)) {goto destroy_class;}return 0;destroy_class:class_destroy(dtsled.class);
del_cdev:cdev_del(&dtsled.cdev);
del_unregister:unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
fail_map:led_unmap();return -EIO;
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit led_exit(void)
{/* 取消映射 */led_unmap();/* 注销字符设备驱动 */cdev_del(&dtsled.cdev);/*  删除cdev */unregister_chrdev_region(dtsled.devid, DTSLED_CNT); /* 注销设备号 */device_destroy(dtsled.class, dtsled.devid);class_destroy(dtsled.class);
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

  照样做了一个流程图,跟上一节比多出来了设备树内容和寄存器映射是用设备树中的语法 of_iomap:

三、编译驱动程序和测试APP

  测试APP都是用的最开始的测试APP。

  创建 Makefile 文件,输入以下内容:

KERNELDIR := /home/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31	# Linux内核源码路径
CURRENT_PATH := $(shell pwd)		# 获取当前所处路径
obj-m := dtsled.o		build: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean# 跟上一节的Makefile差不多,就是 obj-m := dtsled.o 有区别

  创建完成后,输入以下命令输出驱动模块文件:

make -j32    # 使用 32 个并行任务进行编译,让编译快一些

  输入以下命令测试 ledApp.c的测试文件:

arm-none-linux-gnueabihf-gcc ledApp.c -o ledApp

四、运行测试

  将 ledApp 和 dtsled.ko 拷贝到 rootfs/lib/modules/5.4.31 目录中,并加载 dtsled.ko 驱动模块:

depmod     // 第一次加载驱动的时候需要此命令    它是生成和更新模块依赖关系的命令、
modprobe dtsled     // 加载驱动

  stm32mp1_led 这个节点找到了,并且 compatible 属性值为“atkstm32mp1-led”, status 属性值为“okay”, reg 属性的值为“0X50000A28 0X4 0X5000A0000X4 0X5000A004 0X4 0X5000A008 0X4 0X5000A00C 0X4 0X5000A018 0X4”,这些都和我们设置的设备树一致。 

  用 ledApp 测试驱动是否正常工作。

./ledApp /dev/dtsled 1     // 打开红色LED 
./ledApp /dev/dtsled 0     // 关闭红色LED // 卸载驱动
rommod dtsled.ko

  从这章我开始明白,正点原子是从最老的开始教学,一步一步,其中一个一个知识点插入进来讲,最终到后面的大成。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java操作Word修订功能:启用、接受、拒绝、获取修订

Word的修订功能是一种在文档中进行编辑和审阅的功能。它允许多个用户对同一文档进行修改并跟踪这些修改&#xff0c;以便进行审查和接受或拒绝修改。修订功能通常用于团队合作、专业编辑和文件审查等场景。 本文将从以下几个方面介绍如何使用免费工具Free Spire.Doc for Java在…

机器视觉系统选型-高图像精度

图像精度 X方向系统精度&#xff08;X方向象素值&#xff09;&#xff1d; 视野范围&#xff08;X方向&#xff09; CCD芯片象素数量&#xff08;X方向&#xff09; Y方向系统精度&#xff08;Y方向象素值&#xff09;&#xff1d; 视野范围&#xff08;Y方向 CCD芯片象素数量&…

【lesson17】minishell(shell的模拟实现)

文章目录 模拟实现shell的思路具体实现一直循环&#xff08;一&#xff09;显示提示行符&#xff08;二&#xff09;获取用户输入的字符串&#xff08;三&#xff09;对字符串进行解析&#xff08;四&#xff09;创建子进程执行指令&#xff08;5&#xff09; 细节问题解决问题…

Redis第2讲——Java三种客户端(Jedis、Lettuce和Redisson)

上篇文章介绍了Redis的9种数据类型和常命令、7种数据结构和9种编码方式。但是如果想要把它应用到项目中&#xff0c;我们还需要一个redis的客户端。redis的Java客户端种类还是很多的&#xff0c;其中使用最广泛的有三种——Jedis、lettuce和redisson&#xff0c;下面我们一起来…

web前端游戏项目-雷霆战机飞机大战游戏【附源码】

文章目录 一&#xff1a;雷霆战机HTML源码&#xff1a;JS文件&#xff1a;&#xff08;1&#xff09;function.js&#xff08;2&#xff09;impact.js&#xff08;3&#xff09;move.1.1.js&#xff08;4&#xff09;script.js 二&#xff1a;飞机大战HTML源码&#xff1a;CSS源…

性能压力测试--确保企业数字化业务稳健运行

随着企业的数字化转型和依赖云计算的普及&#xff0c;软件系统的性能已经成为企业成功运营的关键因素之一。性能压力测试作为确保系统在各种条件下都能高效运行的关键步骤&#xff0c;对企业的重要性不可忽视。以下是性能压力测试对企业的几个重要方面的影响和作用&#xff1a;…

最新AI创作系统ChatGPT系统源码+DALL-E3文生图+AI绘画+GPT语音对话功能

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作Ch…

ubuntu 20.04安装一系列软件

1&#xff09;安装下载的包的指令&#xff1a; sudo dpkg -i xxx.deb 2&#xff09;通用指令&#xff1a; sudo apt-get install xxxx 3&#xff09;更新和升级软件包&#xff08;遇到问题先尝试这个指令&#xff09;&#xff1a; sudo apt-get update sudo apt-get install…

Java_集合进阶(Collection和List系列)

一、集合概述和分类 1.1 集合的分类 已经学习过了ArrayList集合&#xff0c;但是除了ArrayList集合&#xff0c;Java还提供了很多种其他的集合&#xff0c;如下图所示&#xff1a; 我想你的第一感觉是这些集合好多呀&#xff01;但是&#xff0c;我们学习时会对这些集合进行…

基于alibaba druid的血缘解析工具

基于alibaba druid的血缘解析 1、前言 仅仅对mysql数据库的select查询语句进行了血缘解析&#xff0c;该血缘解析包含了原始表字段、临时表字段和目标表字段的关联关系。 2、涉及到技术 主要使用了druid的如下接口对语法树进行解析&#xff1a; &#xff08;1&#xff09;…

JavaWeb笔记之前端开发CSS

一 、引言 1.1 CSS概念 层叠样式表(英文全称&#xff1a;Cascading Style Sheets)是一种用来表现HTML&#xff08;标准通用标记语言的一个应用&#xff09;或XML&#xff08;标准通用标记语言的一个子集&#xff09;等文件样式的计算机语言。CSS不仅可以静态地修饰网页&…

美颜技术详解:深入了解视频美颜SDK的工作机制

本文将深入探讨视频美颜SDK的工作机制&#xff0c;揭示其背后的科技奥秘和算法原理。 1.引言 视频美颜SDK作为一种集成到应用程序中的技术工具&#xff0c;通过先进的算法和图像处理技术&#xff0c;为用户提供令人印象深刻的实时美颜效果。 2.视频美颜SDK的基本工作原理 首…

SVN小白常见操作流程

SVN小白常见操作流程 一、什么是Subversion&#xff1f;二、TortoiseSVN客户端安装教程三、SVN 操作3.1 SVN Ckeckout(检出)3.2 Add(新增文件)3.3 SVN Commit(提交)3.4 SVN Update(更新操作)3.5SVN Delete(删除操作)3.6 SVN Revert to a revision(版本回溯)3.7 不同版本内容之间…

Jenkins 执行远程脚本的插件—SSH2 Easy

SSH2 Easy 是什么&#xff1f; SSH2 Easy 是一个 Jenkins 插件&#xff0c;它用于在 Jenkins 构建过程中通过 SSH2 协议与远程服务器进行交互。通过该插件&#xff0c;用户可以在 Jenkins 的构建过程中执行远程命令、上传或下载文件、管理远程服务器等操作。 以下是 SSH2 Eas…

vue3使用mock模拟后端接口

安装mock axios yarn add mock yarn add axios 新建在src/mockdata/automenu.js 模拟后端的json数据格式 import Mock from mockjs Mock.mock(/menu,get,{status: 200,menuList: [{id : 1,iconCls: "fa fa-window",name: 系统管理,url: /},{id: 2,icon: icon-j…

YOLOv8改进 | 主干篇 | 利用MobileNetV1替换Backbone(轻量化网络结构)

一、本文介绍 本文给大家带来的改进机制是MobileNetV1&#xff0c;其是专为移动和嵌入式视觉应用设计的轻量化网络结构。这些模型基于简化的架构&#xff0c;并利用深度可分离卷积构建轻量级深度神经网络&#xff0c;其引入了两个简单的全局超参数&#xff0c;用于在延迟和准确…

【C语言】指针详解(一)

目录 1.内存和地址 1.1内存 1.2如何理解编址 2.指针变量和地址 2.1取地址操作符&#xff08;&&#xff09; 2.2指针变量和解引用操作符&#xff08;*&#xff09; 2.2.1指针变量 2.2.2拆解指针类型 2.2.3解引用操作符 2.3指针变量大小 1.内存和地址 1.1内存 在讲内…

《数据分析-JiMuReport》积木报表详细入门教程

积木报表详细入门教程 一、JimuReport部署入门介绍 积木报表可以通过源码部署、SpringBoot集成、Docker部署以及各种成熟框架部署&#xff0c;具体可查看积木官方文档 当前采用源码部署&#xff0c;首先下载Jimureport-example-1.5.6 1 jimureport-example目录查看 使用ID…

自动气象监测站助力生活生产

随着科技的发展&#xff0c;我们的生活和生产方式正在发生着日新月异的变化。其中&#xff0c;WX-CQ12 自动气象监测站作为一项气象监测设备&#xff0c;正在发挥着越来越重要的作用。它不仅为我们提供了更加准确、实时的天气信息&#xff0c;还为农业、交通、旅游等领域提供了…

python flask+vue实现前后端图片上传

python flaskvue实现前后端图片上传 vue代码如下&#xff1a; <template><div><input type"file" change"handleFileChange"/><button click"uploadFile">上传</button><br><img :src"imageUrl&…