RK3568笔记三十九:多个LED驱动开发测试(设备树)

若该文为原创文章,转载请注明原文出处。

通过设备树配置一个节点下两个子节点控制两个IO口,一个板载LED,一个外接LED。

一、介绍

通过学习设备树控制GPIO,发现有多种方式

一、直接通过寄存器控制

二、通过设备树,但不通过pinctrl子系统

三、通pinctrl的GPIO子系统。

正点原子三个方法都有测试代码,自行测试。

学习控制多个LED主要是想模拟I2C或SPI,不使用硬件方式处理。

但很多手册,只给了单个GPIO的设备树配置方式。

此篇记录,方便后面I2C或SPI模拟测试使用。

测试使用的是正点原子的ATK-DLRK3568板子,根据操作可以测试成功,环境需要自行搭建。

二、原理图

两个LED,一个板载LED,一个

LED1: GPIO0_C0  高电平亮,低电平灭

LED2: GPIO3_C4 高电平灭,低电平亮(没有硬件,简易搭建)

三、创建节点

1、在设备树中创建设备节点

修改/home/alientek/rk3568_linux_sdk/kernel/arch/arm64/boot/dts/rockchip/目录下的rk3568-atk-evb1-ddr4-v10.dtsi文件,添加gpios节点

gpios {compatible = "gpio-led-test";pinctrl-names = "default";status = "okay";led1 {compatible = "led1-test";pinctrl-0 = <&pinctrl_led>;gpios-led = <&gpio0 RK_PC0 GPIO_ACTIVE_LOW>;status = "okay";};beep {compatible = "beep-test";pinctrl-0 = <&pinctrl_beep>;gpios-beep = <&gpio3 RK_PC4 GPIO_ACTIVE_HIGH>;status = "okay";};};

意思是在设备树下创建节点gpios,gpios节点下又生成两个子节点led1,和beep

2、创建设备的 pinctrl 节点

修改/home/alientek/rk3568_linux_sdk/kernel/arch/arm64/boot/dts/rockchip/目录下的rk3568-pinctrl.dtsi文件,在最后面增加节点

led-gpio {pinctrl_led: led-gpio-ctrl {rockchip,pins = <0 RK_PC0 RK_FUNC_GPIO &pcfg_pull_none>;};};beep-gpio {pinctrl_beep: beep-gpio-ctrl {rockchip,pins = <3 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>;};};

修改后,重新编译内核,生成boot.img文件,重新烧写即可。

重启后会在/proc/device-tree下找到gpios节点,gpios下又有beep和led1节点

四、驱动编写

1、led_gpios.c

#include <linux/init.h>
#include <linux/of.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/io.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/uaccess.h>#define GPIOLED_CNT 2 /* 设备号个数 */
#define GPIOLED_NAME "ledtest" /* 名字 */#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 *//* gpioled 设备结构体 */
struct gpioled_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device[2]; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 */struct device_node *nd[GPIOLED_CNT]; /* 设备节点 */int gpios[GPIOLED_CNT]; /* led 所使用的 GPIO 编号 */
};struct gpioled_dev gpioled; /* led 设备 *//** @description : 打开设备* @param – inode : 传递给驱动的 inode* @param – filp : 设备文件,file 结构体有个叫做 private_data 的成员变量* 一般在 open 的时候将 private_data 指向设备结构体。* @return : 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{int minor = MINOR(inode->i_rdev);filp->private_data = &gpioled; /* 设置私有数据 */printk("open minor is %d\r\n",minor);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;struct gpioled_dev *dev = filp->private_data;int minor = MINOR(file_inode(filp)->i_rdev);/*获取设备的子节点号,在4.x内核必须用file_inode(filp)代替filp->f_dentry->inode,不然编译会出错*/printk("write inode minor is %d\r\n",minor);retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0){printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0]; /* 获取状态值 */printk("ledstat = %d\n", ledstat);switch(minor){case 0:if(ledstat == LEDON) {gpio_set_value(dev->gpios[0], 0); /* 打开 LED 灯 */printk("open led\n");} else if(ledstat == LEDOFF) {gpio_set_value(dev->gpios[0], 1); /* 关闭 LED 灯 */printk("close led\n");}break;case 1:if(ledstat == LEDON) {gpio_set_value(dev->gpios[1], 1); /* 打开 LED 灯 */printk("open beep\n");} else if(ledstat == LEDOFF) {gpio_set_value(dev->gpios[1], 0); /* 关闭 LED 灯 */printk("close beep\n");}break;default:break;}return 0;
}/** @description : 关闭/释放设备* @param – filp : 要关闭的设备文件(文件描述符)* @return : 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{return 0;
}/* 设备操作函数 */
static struct file_operations gpioled_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)
{int ret = 0;struct property *proper;/* 设置 LED 所使用的 GPIO *//* 1、获取设备节点:gpioled */gpioled.nd[0] = of_find_node_by_path("/gpios/led1");if(gpioled.nd[0] == NULL) {printk("gpioled node cant not found!\r\n");return -EINVAL;} else {printk("gpioled node has been found!\r\n");}/* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */gpioled.gpios[0] = of_get_named_gpio(gpioled.nd[0], "gpios-led", 0);if(gpioled.gpios[0] < 0) {printk("can't get led-gpio!\r\n");return -EINVAL;}printk("led-gpio num = %d\r\n", gpioled.gpios[0]);/* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */ret = gpio_direction_output(gpioled.gpios[0], 0);if(ret < 0) {printk("can't set le-gpio!\r\n");}/*获取字节点的compatible属性*/proper = of_find_property(gpioled.nd[0], "compatible", NULL);if(proper == NULL) {printk("compatible property find failed\r\n");} else {printk("led compatible = %s\r\n", (char*)proper->value);}/*  设置 BEEP 所使用的 GPIO *//* 1、获取设备节点:gpioled */gpioled.nd[1] = of_find_node_by_path("/gpios/beep");if(gpioled.nd[1] == NULL) {printk("gpiobeep node cant not found!\r\n");return -EINVAL;} else {printk("gpiobeep node has been found!\r\n");}/* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */gpioled.gpios[1] = of_get_named_gpio(gpioled.nd[1], "gpios-beep", 0);if(gpioled.gpios[1] < 0) {printk("can't get beep-gpio!\r\n");return -EINVAL;}printk("beep-gpio num = %d\r\n", gpioled.gpios[1]);/* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */ret = gpio_direction_output(gpioled.gpios[1], 1);if(ret < 0) {printk("can't set beep-gpio!\r\n");}/*获取字节点的compatible属性*/proper = of_find_property(gpioled.nd[0], "compatible", NULL);if(proper == NULL) {printk("beep compatible property find failed\r\n");} else {printk("beep compatible = %s\r\n", (char*)proper->value);}/*符设备驱动 *//* 1、创建设备号 */if (gpioled.major) { /* 定义了设备号 */gpioled.devid = MKDEV(gpioled.major, 0);register_chrdev_region(gpioled.devid, GPIOLED_CNT,GPIOLED_NAME);} else { /* 没有定义设备号 */alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT,GPIOLED_NAME); /* 申请设备号 */gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */}printk("gpioled major=%d,minor=%d\r\n",gpioled.major,gpioled.minor);/* 2、初始化 cdev */gpioled.cdev.owner = THIS_MODULE;cdev_init(&gpioled.cdev, &gpioled_fops);/* 3、添加一个 cdev */ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);if(ret){printk("cdev_add erro!\r\n");goto cdevadd_erro;}printk("cdev_add ok!\r\n");/* 4、创建类 */gpioled.class = class_create(THIS_MODULE, "my-led");if (IS_ERR(gpioled.class)) {printk("class_create erro!\r\n");ret=PTR_ERR(gpioled.class);goto class_create_erro;}/* 5、创建设备 */gpioled.device[0] = device_create(gpioled.class, NULL,MKDEV(gpioled.major,0), NULL, "led1");if (IS_ERR(gpioled.device[0])) {ret =  PTR_ERR(gpioled.device[1]);goto device_create1_erro;}/* 5、创建设备 */gpioled.device[1] = device_create(gpioled.class, NULL,MKDEV(gpioled.major,1), NULL, "beep");if (IS_ERR(gpioled.device[1])) {ret = PTR_ERR(gpioled.device[1]);}printk("led_init ok\n");return 0;
evice_create1_erro:class_destroy(gpioled.class);
lass_create_erro:cdev_del(&gpioled.cdev); /* 删除 cdev */
devadd_erro:unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); return ret;}/*189 * @description : 驱动出口函数* @param : 无* @return : 无*/static void __exit led_exit(void){/* 注销字符设备驱动 */cdev_del(&gpioled.cdev); /* 删除 cdev */unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销 */device_destroy(gpioled.class, MKDEV(gpioled.major,0));device_destroy(gpioled.class, MKDEV(gpioled.major,1));class_destroy(gpioled.class);}module_init(led_init);module_exit(led_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("yifeng");

代码有个注意事项,

1、获取设备节点,节点不要填错,为设备树的节点:

gpioled.nd[0] = of_find_node_by_path("/gpios/led1");gpioled.nd[1] = of_find_node_by_path("/gpios/beep");

2、创建设备,会在/dev/下生成节点

/* 5、创建设备 */gpioled.device[0] = device_create(gpioled.class, NULL, MKDEV(gpioled.major,0), NULL, "led1");/* 5、创建设备 */gpioled.device[1] = device_create(gpioled.class, NULL, MKDEV(gpioled.major,1), NULL, "beep");

2、makefile

KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel
ARCH=arm64
CROSS_COMPILE=/opt/atk-dlrk356x-toolchain/usr/bin/aarch64-buildroot-linux-gnu-export  ARCH  CROSS_COMPILECURRENT_PATH := $(shell pwd)
obj-m := led_gpios.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译生成ko文件,拷贝到开发板。

五、App编写

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>int main(int argc, char **argv)
{int fd;char status;if (argc != 3){printf("Usage: %s <dev> <on | off>\\n", argv[0]);return -1;}fd = open(argv[1], O_RDWR);if (fd == -1){printf("can not open file %s\\n", argv[1]);return -1;}if (0 == strcmp(argv[2], "on")){status = 1;write(fd, &status, 1);}else{status = 0;write(fd, &status, 1);}close(fd);return 0;
}

编译,生成的可执行文件拷贝到开发板测试。

/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc ledApp.c -o ledApp

六、测试

1、关闭 LED 的心跳灯

echo none > /sys/class/leds/work/trigger

2、加载和卸载驱动模块

insmod led_gpios.ko

加载成功

3、测试

./ledApp /dev/led1 on
./ledApp /dev/led1 off

./ledApp /dev/beep on
 ./ledApp /dev/beep off

实现状态是相反的,需要根据实际的修改,但控制是正常的。

如有侵权,或需要完整代码,请及时联系博主。

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

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

相关文章

基于STC89C52RC单片机的大棚温控系统(含文档、源码与proteus仿真,以及系统详细介绍)

本篇文章论述的是基于STC89C52RC单片机的大棚温控系统的详情介绍&#xff0c;如果对您有帮助的话&#xff0c;还请关注一下哦&#xff0c;如果有资源方面的需要可以联系我。 目录 摘要 原理图 仿真图 系统总体设计图 代码 系统论文 参考文献 资源下载 摘要 本文介绍的…

CSA笔记3-文件管理命令(补充)+vim+打包解包压缩解压缩命令

grep(-i -n -v -w) [rootxxx ~]# grep root anaconda-ks.cfg #匹配关键字所在的行 [rootxxx ~]# grep -i root anaconda-ks.cfg #-i 忽略大小写 [rootxxx ~]# grep -n root anaconda-ks.cfg #显示匹配到的行号 [rootxxx ~]# grep -v root anaconda-ks.cfg #-v 不匹配有…

甄选范文“论软件维护方法及其应用”软考高级论文,系统架构设计师论文

论文真题 软件维护是指在软件交付使用后,直至软件被淘汰的整个时间范围内,为了改正错误或满足 新的需求而修改软件的活动。在软件系统运行过程中,软件需要维护的原因是多种多样的, 根据维护的原因不同,可以将软件维护分为改正性维护、适应性维护、完善性维护和预防性 维护…

Linux 上 TTY 的起源

注&#xff1a;机翻&#xff0c;未校对。 What is a TTY on Linux? (and How to Use the tty Command) What does the tty command do? It prints the name of the terminal you’re using. TTY stands for “teletypewriter.” What’s the story behind the name of the co…

debian 实现离线批量安装软件包

前言 实现在线缓冲需要的软件和对应依赖的包&#xff0c;离线进行安装 &#xff0c;用于软件封装。 测试下载一个gcc和依赖环境&#xff0c;关闭默认在线源&#xff0c;测试离线安装gcc和依赖环境 兼容 debian ubuntu/test 测试下载安装包到目录 vim /repo_download.sh #!…

【数据结构】算法复杂度

算法复杂度 数据结构算法复杂度 大o渐进表示法空间复杂度 数据结构 数据结构&#xff1a;是计算机存储和组织数据的方式。 比如打开一个网页&#xff0c;我们看到的文字就是数据&#xff0c;这些数据需要用一个结构来把他管理起来&#xff0c;我们称之为&#xff1a;数据结构 …

基于springboot3实现单点登录(一): 单点登录及其相关概念介绍

引言 应网友要求&#xff0c;从本文开始我们将实现一套基于springboot3springsecurity的单点登录认证系统。 单点登录的实现方式有多种&#xff0c;接下来我们会以oauth2为例来介绍和实现。 单点登录介绍 单点登录&#xff08;Single Sign-On&#xff0c;简称SSO&#xff0…

nftables(7)集合(SETS)

简介 在nftables中&#xff0c;集合&#xff08;sets&#xff09;是一个非常有用的特性&#xff0c;它允许你以集合的形式管理IP地址、端口号等网络元素&#xff0c;从而简化规则的配置和管理。 nftables提供了两种类型的集合&#xff1a;匿名集合和命名集合。 匿名集合&…

使用base64通用文件上传

编写一个上传文件的组件 tuku,点击图片上传后使用FileReader异步读取文件的内容&#xff0c;读取完成后获得文件名和base64码&#xff0c;调用后端uploadApi,传入姓名和base64文件信息&#xff0c;后端存入nginx中&#xff0c;用于访问 tuku.ts组件代码&#xff1a; <templa…

系统测试-白盒测试学习

目录 1、语句覆盖法&#xff1a; 2、判定覆盖法&#xff1a; 3、条件覆盖法&#xff1a; 4、判定条件覆盖&#xff1a; 5、条件组合的覆盖&#xff1a; 6、路径覆盖&#xff1a; 黑盒&#xff1a;需求 白盒&#xff1a;主要用于单元测试 1、语句覆盖法&#xff1a; 程序…

OSU!题解(概率dp)

题目&#xff1a;OSU! - 洛谷 思路&#xff1a; 设E()表示截止到i所获得的分数&#xff1b; 对于到i点的每一个l&#xff0c;如果第i1点为1&#xff0c;那么会新增分数3*l^23*l1; 就有递推公式方程&#xff1a; E()E()p[i1]p*(3*l^23*l1);(p代表截止到i获得长度l的概率)&a…

怎样在 PostgreSQL 中优化对多表关联的连接条件选择?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 怎样在 PostgreSQL 中优化对多表关联的连接条件选择一、理解多表关联的基本概念二、选择合适的连接条件…

【C++】拷贝构造函数及析构函数

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

dbeaver连接mysql8异常

部署了mysql8&#xff0c;尝试用dbeaver 24.1.2连接它。结果配置完成后测试连接时报错&#xff1a;Public Key Retrieval is not allowed. 按照提示修改驱动属性&#xff1a; allowPublicKeyRetrievaltrue

【BUG】已解决:ValueError: Expected 2D array, got 1D array instead

已解决&#xff1a;ValueError: Expected 2D array, got 1D array instead 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武汉…

Python | Leetcode Python题解之第238题除自身以外数组的乘积

题目&#xff1a; 题解&#xff1a; class Solution:def productExceptSelf(self, nums: List[int]) -> List[int]:length len(nums)# L 和 R 分别表示左右两侧的乘积列表L, R, answer [0]*length, [0]*length, [0]*length# L[i] 为索引 i 左侧所有元素的乘积# 对于索引为…

人工智能 (AI) 应用:一个异常肺呼吸声辅助诊断系统

关键词&#xff1a;深度学习、肺癌、多标签、轻量级模型设计、异常肺音、音频分类 近年来&#xff0c;流感对人类的危害不断增加&#xff0c;COVID-19疾病的迅速传播加剧了这一问题&#xff0c;导致大多数患者因呼吸系统异常而死亡。在这次流行病爆发之前&#xff0c;呼吸系统…

SCI一区级 | Matlab实现GJO-CNN-LSTM-Multihead-Attention多变量时间序列预测

SCI一区级 | Matlab实现GJO-CNN-LSTM-Mutilhead-Attention多变量时间序列预测 目录 SCI一区级 | Matlab实现GJO-CNN-LSTM-Mutilhead-Attention多变量时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现GJO-CNN-LSTM-Mutilhead-Attention金豺优化算…

MongoDB自学笔记(三)

一、前文回顾 上一篇文章中我们学习了更新操作&#xff0c;以及讲解了部分的更新操作符&#xff0c;今天我们继续学习剩余的更新操作符。 二、更新操作符 1、$rename 语法&#xff1a;{ $rename: { < field1 >: < newName1 >, < field2 >: < newName2…

力扣刷题之978.最长湍流子数组

题干要求&#xff1a; 给定一个整数数组 arr &#xff0c;返回 arr 的 最大湍流子数组的长度 。 如果比较符号在子数组中的每个相邻元素对之间翻转&#xff0c;则该子数组是 湍流子数组 。 更正式地来说&#xff0c;当 arr 的子数组 A[i], A[i1], ..., A[j] 满足仅满足下列条…