RK3568笔记三十五:LED驱动开发测试
若该文为原创文章,转载请注明原文出处。
字符设备驱动程序的基本框架,主要是如何申请及释放设备号、添加以及注销设备,初始化、添加与删除 cdev 结构体,并通过 cdev_init 函数建立 cdev 和 file_operations 之间的关联,cdev 结构体和 file_operations 结构体。
使用的开发板是正点原子的ATK-DLRK3568.
一、查看原理图
通过查看原理图,得知LED控制引脚接到了 GPIO0_C0上,通过三极管控制LED,GPIO0_C0输出高电平,LED亮,输出低电平,LED灭。
二、GPIO介绍
GPIO(General Purpose Input/Output Port):通用输入输出端口。
除作为一般的输入/输出功能外,还可以配置为中断和模拟UART、CAN、PWM、I2C、SDMMC、CLK等功能。
比如 GPIO0_C0 这个 IO 就可以用 作:GPIO,PWM1_M0,GPU_AVS 和 UART0_RX 这四个功能,所以我们首先要设置好当前引 脚用作什么功能。
rk356x 系列对应的文档为:
• Rockchip_RK3568_TRM_Part1_xxx.pdf
• Rockchip_RK3566_Datasheet_xxx.pdf
• Rockchip_RK3568_Datasheet_xxx.pdf
1、GPIO分组
RK3568共160个GPIO引脚,复用型引脚分为 5 组 (GPIO0~4),每组里面都有 32 个复 用型引脚,而且又分为 4 个小组 (A、B、C、D),每个小组 8 个引脚 (0~7)。例如:GPIO0_C7 是 GPIO0 大组,第 3 个小组,第 8 个引脚。
要查找GPIO对应的配置寄存器地址,必须知道他属于哪个分组。
2、GPIO引脚号计算方法
pins = 32*bank_num + 8*group + x
bank_num : 0 ~ 4,对应GPIO 0~4
group : 0 ~ 3,对应GPIO A~D
GPIO0_C2 = 32*0(bank_num) + 8*2(group) + 0 = 16
根据计算GPIO0_C0序号为16。在后面驱动代码时会用到。
2、寄存器配置
这里以GPIO0_C0为例,查看Rockchip_RK3568_TRM_Part1手册可知,GPIO0 组复用功能是在 PMU_GRF 寄存器,实验中需要对 GPIO 进行配置,一般情况下需要对 GPIO 的复用寄存器,方向寄存器,数据寄存器进行配置, 和复用相关的总共 8 个寄存器。
1. 查找复用寄存器
2. 查找方向寄存器

通过寄存器描述,该寄存器有高 16bit 和低 16bit,高 16bit 控制低 16bit 的 写使能,低 16bit 控制 GPIO 的输出方向, 0:输入,1:输出。
GPIO0_C0属于 GPIO0 中 A-D 组总计 64 个引脚中的高 32 引脚范围,所以需要将 GPIO_SWPORT_DDR_H 寄存器的第 0bit 位和 16bit 位置 1,允许写 bit16。
Operational Base + offset = 0xFDD60000 + 0x000C = 0xFDD6000C
3. GPIO 引脚高低电平设置

GPIO_SWPORT_DR_L 和 GPIO_SWPORT_DR_H 寄存器有高 16bit 和低 16bit,高 16bit 控制低 16bit 的写 使能,低 16bit 控制 GPIO 的高低电平。
GPIO0_C0属于 GPIO0 中 A-D 组总计 64 个引脚 中高的 32 引脚范围,所以需要将 GPIO_SWPORT_DR_H 寄存器的第0bit 位和 16bit 位置 1。
4. 总结
GPIO 的基地址为 0xFDD60000,偏移地址为 0x000C,
GPIO 的基地址为 0xFDD60000,偏移地址为 0x0004,
三、驱动程序编写
1、编写led_cdev.c驱动文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>#define DEV_NAME "led_chrdev"
#define DEV_CNT (1)#define GPIO0_BASE (0xfdd60000)//每组GPIO,有2个寄存器,对应32个引脚,每个寄存器负责16个引脚;
//一个寄存器32位,其中高16位都是使能位,低16位对应16个引脚,每个引脚占用1比特位
#define GPIO0_DR_L (GPIO0_BASE + 0x0000)
#define GPIO0_DR_H (GPIO0_BASE + 0x0004)#define GPIO0_DDR_L (GPIO0_BASE + 0x0008)
#define GPIO0_DDR_H (GPIO0_BASE + 0x000C)static dev_t devno;
struct class *led_chrdev_class;struct led_chrdev {struct cdev dev;unsigned int __iomem *va_dr; // 数据寄存器,设置输出的电压unsigned int __iomem *va_ddr; // 数据方向寄存器,设置输入或者输出unsigned int led_pin; // 偏移
};/* 打开设备函数 */
static int led_chrdev_open(struct inode *inode, struct file *filp)
{ unsigned int val = 0;struct led_chrdev *led_cdev = (struct led_chrdev *)container_of(inode->i_cdev, struct led_chrdev,dev);filp->private_data = container_of(inode->i_cdev, struct led_chrdev, dev);printk("open\n");//设置输出模式val = ioread32(led_cdev->va_ddr);val |= ((unsigned int)0x1 << (led_cdev->led_pin+16));val |= ((unsigned int)0X1 << (led_cdev->led_pin));iowrite32(val,led_cdev->va_ddr);//输出高电平val = ioread32(led_cdev->va_dr);val |= ((unsigned int)0x1 << (led_cdev->led_pin+16));val |= ((unsigned int)0x1 << (led_cdev->led_pin));iowrite32(val, led_cdev->va_dr);return 0;
}static int led_chrdev_release(struct inode *inode, struct file *filp)
{return 0;
}/* 从设备读取数据 */
static ssize_t led_chrdev_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{ printk("This is led_chrdev_read\r\n");return 0;
}/* 向设备写入数据函数 */
static ssize_t led_chrdev_write(struct file *filp, const char __user * buf,size_t count, loff_t * ppos)
{unsigned long val = 0;char ret = 0;struct led_chrdev *led_cdev = (struct led_chrdev *)filp->private_data;printk("write \n");get_user(ret, buf);val = ioread32(led_cdev->va_dr);printk("val = %lx\n", val);if (ret == '0' || ret == 0){val |= ((unsigned int)0x1 << (led_cdev->led_pin+16));val &= ~((unsigned int)0x01 << (led_cdev->led_pin)); /*设置GPIO引脚输出低电平*/}else{val |= ((unsigned int)0x1 << (led_cdev->led_pin+16));val |= ((unsigned int)0x01 << (led_cdev->led_pin)); /*设置GPIO引脚输出高电平*/}iowrite32(val, led_cdev->va_dr);printk("val = %lx\n", val);return count;
}/* 设备操作函数 */
static struct file_operations led_chrdev_fops = {.owner = THIS_MODULE, // 将 owner 字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块.open = led_chrdev_open, // 将 open 字段指向 chrdev_open(...)函数.read = led_chrdev_read, // 将 open 字段指向 chrdev_read(...)函数.write = led_chrdev_write, // 将 open 字段指向 chrdev_write(...)函数.release = led_chrdev_release, // 将 open 字段指向 chrdev_release(...)函数
};static struct led_chrdev led_cdev[DEV_CNT] = {{.led_pin = 0}, //偏移,高16引脚,GPIO0_C0
};/* 驱动入口函数 */
static __init int led_chrdev_init(void)
{int i = 0;dev_t cur_dev;printk("led_chrdev init (lubancat2 GPIO0_C7)\n");/*0 将物理地址转化为虚拟地址 */led_cdev[0].va_dr = ioremap(GPIO0_DR_H, 4); //led_cdev[0].va_ddr = ioremap(GPIO0_DDR_H, 4); // /*1 创建设备号 */alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME);/*2 创建类 */led_chrdev_class = class_create(THIS_MODULE, "led_chrdev");for (; i < DEV_CNT; i++) {/*3 初始化 cdev */cdev_init(&led_cdev[i].dev, &led_chrdev_fops);led_cdev[i].dev.owner = THIS_MODULE;/*4 获取主设备号和次设备号 */cur_dev = MKDEV(MAJOR(devno), MINOR(devno) + i);/*5 添加一个 cdev,完成字符设备注册到内核 */cdev_add(&led_cdev[i].dev, cur_dev, 1);/*6 创建设备*/device_create(led_chrdev_class, NULL, cur_dev, NULL, DEV_NAME "%d", i);}return 0;
}/* 驱动出口函数 */
static __exit void led_chrdev_exit(void)
{int i;dev_t cur_dev;printk("led chrdev exit (lubancat2 GPIO0_C7)\n");/*注销字符设备*/for (i = 0; i < DEV_CNT; i++) {iounmap(led_cdev[i].va_dr); // 释放模式寄存器虚拟地址iounmap(led_cdev[i].va_ddr); // 释放输出类型寄存器虚拟地址}for (i = 0; i < DEV_CNT; i++) {cur_dev = MKDEV(MAJOR(devno), MINOR(devno) + i);//删除设备device_destroy(led_chrdev_class, cur_dev);// 删除 cdevcdev_del(&led_cdev[i].dev);}// 注销设备号unregister_chrdev_region(devno, DEV_CNT);// 删除类class_destroy(led_chrdev_class);}module_init(led_chrdev_init);
module_exit(led_chrdev_exit);MODULE_AUTHOR("yifeng");
MODULE_LICENSE("GPL");
总结:
模块加载
1、初始化 LED 灯结构体成员,将物理寄存器的地址映射到虚拟地址空间
2、向动态申请一个设备号
3、创建设备类
4、绑定 led_cdev 与 led_chrdev_fops
5、注册设备
6、创建设备
模块卸载
1、删除设备
2、注销设备
3、释放被占用的设备号
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_cdev.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
编译
3、编写APP应用
ledApp.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"#define LEDOFF 0
#define LEDON 1/** @description : main主程序* @param - argc : argv数组元素个数* @param - argv : 具体参数* @return : 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd, retvalue;char *filename;unsigned char databuf[1];if(argc != 3){printf("Error Usage!\r\n");return -1;}filename = argv[1];/* 打开led驱动 */fd = open(filename, O_RDWR);if(fd < 0){printf("file %s open failed!\r\n", argv[1]);return -1;}databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 *//* 向/dev/led文件写入数据 */retvalue = write(fd, databuf, sizeof(databuf));if(retvalue < 0){printf("LED Control Failed!\r\n");close(fd);return -1;}retvalue = close(fd); /* 关闭文件 */if(retvalue < 0){printf("file %s close failed!\r\n", argv[1]);return -1;}return 0;
}
编译
/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc ledApp.c -o ledApp
4、测试
测试有两个,一是直接测试,二是使用APP应用程序测试。
在测试前需要关闭心跳灯
echo none > /sys/class/leds/work/trigger
加载LED驱动:
insmod led_cdev.ko
测试方法一:
sh -c 'echo 0 >/dev/led_chrdev0'
sh -c 'echo 1 >/dev/led_chrdev0'
正点原子的LED是反的,所以1是亮,0是灭。
测试方法二:
./ledApp /dev/led 1 //打开 LED 灯
./ledApp /dev/led 0 //关闭 LED 灯
经实验,LED驱动工作正常。
卸载驱动:
rmmod led_cdev
如有侵权,或需要完整代码,请及时联系博主。
相关文章:

RK3568笔记三十五:LED驱动开发测试
若该文为原创文章,转载请注明原文出处。 字符设备驱动程序的基本框架,主要是如何申请及释放设备号、添加以及注销设备,初始化、添加与删除 cdev 结构体,并通过 cdev_init 函数建立 cdev 和 file_operations 之间的关联,…...

pnpm 如何安装指定版本
要安装特定版本的pnpm,可以使用npm命令来全局安装特定版本的pnpm,例如: npm install -g pnpm2.0.0在上面的命令中,使用了2.0.0来指定安装2.0.0版本的pnpm。您可以将2.0.0替换为您需要安装的版本号。 如果您使用的是yarn…...

LeetCode 240 搜索二维矩阵||
1.题目要求: 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:每行的元素从左到右升序排列。 每列的元素从上到下升序排列。实列: 2.各位大佬们,大家好,此题我用的方法是一行一行的找&…...

万界星空科技MES系统:食品加工安全的实时监控与智能管理
万界星空科技MES系统通过集成多种技术和功能,能够实时监控食品加工过程中各环节的安全风险。以下是对该系统如何实现实时监控的详细分析: 一、集成传感器和数据分析技术 万界星空科技MES系统利用集成的传感器和数据分析技术,实时监控生产过程…...

【学习笔记】4、组合逻辑电路(下)
接前文《【学习笔记】4、组合逻辑电路(上)》 4.4.5 算术运算电路 1. 半加器和全加器 半加器和全加器是算术运算电路中的基本单元。半加器和全加器是1位相加的组合逻辑电路。 (1)半加器 半加器:只考虑两个加数本身,不考虑低位进…...

使机器人在执行任务倒快递
这段代码是用来控制机器人在不同模式下的行为,具体是处理 residenceright 和 residenceleft 两种模式下的过渡过程。代码中使用了一个 mythread 结构体,该结构体包含了机器人的当前模式 (mode) 和过渡过程的阶段 (residenceTransientProcess)。以下是对这…...

谈谈软件交互设计
谈谈软件交互设计 交互设计的由来 交互设计(Interaction Design)这一概念,最初是由IDEO创始人之一Bill.Moggridge(莫格里奇)1984年在一次会议上提出。他设计了世界上第一台笔记本电脑Compass,并写作出版了在交互设计领域影响深远的《Designing Interactions》一书,被称…...

npm install报错:淘宝镜像证书过期
npm install报错:淘宝镜像证书过期 近期使用npm淘宝镜像新建项目或依赖时出现报错: npm ERR! request to https://registry.npm.taobao.org/xxx failed, reason: certificate has expired 错误原因: 早在 2021 年,淘宝就发文称…...

各种Attention|即插即用|适用于YoloV5、V7、V8、V9、V10(一)
摘要 本文总结了各种注意力,即插即用,方便大家将注意力加到自己的论文中。 SE import torch from torch import nn class SEAttention(nn.Module): """ SENet(Squeeze-and-Excitation Networks)中的注意力…...

语言模型演进:从NLP到LLM的跨越之旅
在人工智能的浩瀚宇宙中,自然语言处理(NLP)一直是一个充满挑战和机遇的领域。随着技术的发展,我们见证了从传统规则到统计机器学习,再到深度学习和预训练模型的演进。如今,我们站在了大型语言模型ÿ…...

自动驾驶中的人机互相接管问题讨论
一、背景 人机接管(human takeover)是指在自动驾驶过程中,当系统遇到超出其处理能力或预设安全阈值的情况时,将控制权交还给驾驶员的过程。这一环节的设计直接关系到自动驾驶技术的实用性与安全性,是目前研究和实践中…...

语音识别HResults统计工具以及字根据关键词进行合并
#主要想说一下关键词合并 1.HResults统计工具可以参考其他博主的:https://blog.csdn.net/weixin_30348519/article/details/98426654?ops_request_misc%257B%2522request%255Fid%2522%253A%2522172088587416800215066191%2522%252C%2522scm%2522%253A%25222014071…...

lvs集群、NAT模式和DR模式、keepalive
目录 lvs集群概念 集群的类型:三种类型 系统可靠性指标 lvs集群中的术语 lvs的工作方式 NAT模式 lvs的工具 算法 实验 数据流向 步骤 一 、调度器配置(test1 192.168.233.10) 二、RS配置(nginx1和nginx2)…...

zookeeper在哪里能用到
zookeeper是什么 ZooKeeper 顾名思义 动物园管理员,他是拿来管大象(Hadoop) 、 蜜蜂(Hive) 、 小猪(Pig) 的管理员, Apache Hbase和 Apache Solr 以及LinkedIn sensei 等项目中都采用到了 Zookeeper。 ZooKeeper是一个分布式的,开放源码的分…...

coco_eval 使用
参考 coco eval 解析 COCO目标检测比赛中的模型评价指标介绍! coco 的评估函数对应的是 pycocotools 中的 cocoeval.py 文件。 从整体上来看,整个 COCOeval 类的框架如图: 基础的用法为 # The usage for CocoEval is as follows: cocoGt…...

国产精品ORM框架-SqlSugar详解 进阶功能 集成整合 脚手架应用 专题二
国产精品ORM框架-SqlSugar详解 SqlSugar初识 专题一-CSDN博客 sqlsugar 官网-CSDN博客 4、进阶功能 5、集成整合 6、脚手架应用 4、进阶功能 4.1、生命周期 Queryable 什么时候操作库 Queryable是一个引用类型 Queryable拷贝机制 4.2、执行Sql 方法列表 方法名 描述 返…...

el-table 动态添加删除 -- 鼠标移入移出显隐删除图标
<el-table class"list-box" :data"replaceDataList" border><el-table-column label"原始值" prop"original" align"center" ><template slot-scope"scope"><div mouseenter"showClick…...

Kafka接收消息
文章目录 Acknowledgment读消息指定分区批量消费消息拦截 // 采用监听得方式接收 Payload标记消息体内容. KafkaListener(topics {"test"},groupId "hello") public void onEvent(Payload String event,Header(value KafkaHeaders.RECEIVED_TOPIC) Stri…...

C语言 | Leetcode C语言题解之第233题数字1的个数
题目: 题解: int countDigitOne(int n) {// mulk 表示 10^k// 在下面的代码中,可以发现 k 并没有被直接使用到(都是使用 10^k)// 但为了让代码看起来更加直观,这里保留了 klong long mulk 1;int ans 0;f…...

简谈设计模式之原型模式
原型模式是一种创建型设计模式, 用于创建对象, 而不必指定它们所属的具体类. 它通过复制现有对象 (即原型) 来创建新对象. 原型模式适用于当创建新对象的过程代价较高或复杂时, 通过克隆现有对象来提高性能 原型模式结构 原型接口. 声明一个克隆自身的接口具体原型. 实现克隆…...

CentOS7.X系统部署Zabbix6.0版本(可跟做)
文章目录 一、部署环境说明二、基本环境部署步骤1、环境初始化操作2、部署并配置Nginx3、部署并配置PHP4、测试NginxPHP环境5、部署并配置MariaDB 三、Zabbix-Server部署步骤1、编译安装Zabbix-Server2、导入Zabbix初始化库3、配置Zabbix前端UI4、启动Zabbix-Server5、WEB页面配…...

QT文件生成可执行的exe程序
将qt项目生成可执行的exe程序可按照以下步骤进行: 1、在qt中构建运行生成.exe文件; 2、从自定义的路径中取出exe文件放在一个单独的空文件夹中(exe文件在该文件夹中的release文件夹中); 3、从开始程序中搜索qt…...

【ZooKeeper学习笔记】
1. ZooKeeper基本概念 Zookeeper官网:https://zookeeper.apache.org/index.html Zookeeper是Apache Hadoop项目中的一个子项目,是一个树形目录服务Zookeeper翻译过来就是动物园管理员,用来管理Hadoop(大象)、Hive&…...

220V降5V芯片输出电压电流封装选型WT
220V降5V芯片输出电压电流封装选型WT 220V降5V恒压推荐:非隔离芯片选型及其应用方案 在考虑220V转低压应用方案时,以下非隔离芯片型号及其封装形式提供了不同的电压电流输出能力: 1. WT5101A(SOT23-3封装)适用于将2…...

AWS S3 基本概念
AWS S3 基本概念 引言什么是 AWS S3S3 应用S3 的核心概念 引言 最近工作中有接触到 S3,往 S3 写入数据,从 S3 访问数据,所以花点时间整理一下有关 S3 的基本概念。 什么是 AWS S3 AWS S3 (Amazon Simple Storage Service) 是一个由 Amazon…...

[XCUITest] 处理iOS权限点击授权 有哪些权限?
位置权限 (Location Permission) app.addUIInterruptionMonitor(withDescription: "Location Permission Dialog") { (alert) -> Bool in if alert.buttons["Allow While Using App"].exists { alert.buttons["Allow While Using App"].tap(…...

宪法学学习笔记(个人向) Part.5
宪法学学习笔记(个人向) Part.5 4. 公民基本权利和义务 4.1 公民🌸 概念 是指具有某个国家国籍的自然人; 【拓展】国籍:在宪法上是指一个人隶属于某个国家的法律上的身份🌸 ; 取得方式 出生国籍 因出生而获得的国籍&a…...

C语言的指针与数组
函数定义 参考书籍章节9.7 无论函数定义的参数是数组还是指针,在编译的时候,编译器都将在栈上开辟一个空间存放入参的地址,换句话说,也就是在函数内部都当做指针处理。 #include <stdio.h> #include <stdlib.h>char g…...

计算机图形学入门28:相机、透镜和光场
1.前言 相机(Cameras)、透镜(Lenses)和光场(Light Fields)都是图形学中重要的组成部分。在之前的学习中,都是默认它们的存在,所以现在也需要单独拿出来学习下。 2.成像方法 计算机图形学有两种成像方法,即合成(Synthesis)和捕捉(Capture)。前…...

Swift 基于Codable协议使用
Codable协议 继承自 Decodable & Encodable // // Test1.swift // TestDemo // // Created by admin on 2024/7/9. // import Foundationstruct Player{var name:Stringvar highScore:Int 0var history:[Int] []var address:Address?var birthday:Date?init(name: St…...