【嵌入式linux驱动------点亮led】基于鲁班猫4 rk3588s
见野火给的官方资料手册:提取码:hslu
我是参考野火官方进行编程的,里面用的是rk3568,这边就是迁移记录一下,下面的图片是鲁班猫给出的修改代码,可以自行去看。 我这边走一遍3588的流程,自己去找一下手册 然后迁移一下。
这边要注意的是,寄存器的移位一定要熟练~ 同时在新手期也是比较容易出错的点~
文章目录
- [【嵌入式linux驱动------点亮led】基于鲁班猫4 rk3588s](#【嵌入式linux驱动——点亮led】基于鲁班猫4 rk3588s)
0 看看鲁班猫是哪个版本

如果不知道自己哪个版本,直接在命令终端输入,先确定一下版本号,不同的版本硬件规格还是不太一样的

bash
cat /proc/device-tree/model

我这个版本是鲁班猫4-V1
1.具体步骤
这个步骤,如果学过单片机的uu应该很熟悉。点亮一盏灯
- 使能 clk
- 引脚模式------设置gpio模式
- 设置 输入 输出模式
- 设置输出高低电平 获取输入引脚的电平
所以我们的步骤------>
- 首先去找个引脚 使能一下这个gpio
- 然后找到gpio对应的地址(方便对其虚拟地址映射 和读写操作)
- 编写对应的驱动程序和应用程序
由于这边前两步骤------>配置gpio时钟和 配置gpio模式,板卡上已经帮我们做好了,所以我们一般只需要配置第三步和第四步
2.硬件------ 确定gpio配置

由野火鲁班猫4开发硬件规格书可以看到:

电源指示灯在上电后为红色常亮,表示电源在稳定运行。系统状态指示灯为绿色 LED,当系统正常开机后,状态指示灯会进入心跳模式,即一个周期闪烁两次,同时该指示灯为可编程控制指示灯,用户也可以自行控制 GPIO4_B5 引脚,实现对该指示灯进行控制

所以我们这次就采用gpio4_b5作为本次操作的led
引脚复用
进入rk3588s的手册可以看到

在 RK3588 的 TRM(技术参考手册)中, GPIO4 组的复用功能寄存器位于 BUS_IOC (Bus I/O Controller) 模块中。不像 RK3568 那样简单地统称为 SYS_GRF。RK3588 引入了 IOC (I/O Controller) 的概念来专门管理引脚复用(IOMUX)和电气特性(上下拉、驱动能力等)。
- RK3568:
PMU_GRF(GPIO0) +SYS_GRF(GPIO1~4) - RK3588:
PMU1_IOC(GPIO0) +BUS_IOC(GPIO1~4)
在RK3588 的 TRM-PART1文档中搜索关键字BUS_IOC_GPIO4
可以看到

查询 Rockchip_RK3588_TRM_Part1 手册,BUS_IOC_GPIO4B_IOMUX_H寄存器,如下图所示:

BUS_IOC_GPIO4B_IOMUX_H寄存器总共32位,高16位都是使能位,控制低16位的写使能,低16位对应4个引脚,每个引脚占用4bits,不同的值引脚复用为不同功能, 如GPIO4_B5引脚,控制该引脚复位功能的[7:4]位,都为0时,复用为GPIO功能,而系统复位默认为0,本实验中可以不进行配置。 如果需要复用为PWM功能就要将[7:4]位设置为1011,也就是将第7bit位 第5bit位 第4bit位设置为1,因为高16位控制对应低16位写使能,所以需要将第7+16bit 第5+16bit 第4+16bit设置为1,即第23bit 21bit 和20bit设置为1 从而才能对其进行写操作。
引脚电平
通过设置GPIO寄存器设置输入输出、高低电平、中断、抖动等 一些引脚的驱动能力,电气属性等,主要通过设置General Register Files (GRF)
手册上有这么一段

翻译一下就是:
在软件控制下,信号的数据与方向控制分别来自端口数据寄存器 (GPIO SWPORT DR L/ GPIO SWPORT DR H)和方向控制寄存器(GPIO SWPORT DDR L/ GPIO SWPORT DDR H)
外部I/O接口的输出方向由端口数据方向寄存器的数值决定。写入这些内存映射寄存器的数据会被映射到 GPIO 外设的输出信号(GPIO端口DDR)上,该信号控制着外部I/O接口的输出方向。默认数据方向为输入。写入端口数据寄存器的数据会驱动I/O接口的输出缓冲器(GPIO端口DR)

(下图)以GPIO_SWPORT_DR_L寄存器说明,该寄存器有高16bit和低16bit,高16bit控制低16bit的写使能,低16bit控制GPIO的高低电平,GPIO_SWPORT_DR_H同理。

GPIO_SWPORT_DR怎么配置
RK3588S 的 GPIO 控制器(如 GPIO0, GPIO4)内部结构如下:
- 分组 (Bank): 每个 GPIO 控制器(大组)有 32 个引脚。
- 细分 (Sub-bank): 这 32 个引脚分为 4 个小组:
- A 组:引脚 0~7 (对应 bit 0-7)
- B 组:引脚 0~7 (对应 bit 8-15)
- C 组:引脚 0~7 (对应 bit 16-23)
- D 组:引脚 0~7 (对应 bit 24-31)
寄存器分工 (DR_L vs DR_H): 32 个引脚的数据寄存器被拆分成了两个 32 位的寄存器:
GPIO_SWPORT_DR_L(Low): 管理 A 组 和 B 组 (引脚 0~15)。GPIO_SWPORT_DR_H(High): 管理 C 组 和 D 组 (引脚 16~31)。
怎么控制GPIO4_B5 高低电平?
我们要控制 GPIO4_B5 的高低电平,需要经过以下三步推理:
第一步:确定寄存器 (用 L 还是 H?)
- GPIO4_B5 位于 GPIO4 大组。
- 它属于 B 组。
- 根据上面的规则,A 组和 B 组由
_L(Low) 寄存器管理。 - 结论: 我们要操作的是
GPIO_SWPORT_DR_L寄存器。 (注:如果是 C 或 D 组,才用_H寄存器)
第二步:计算数据位 (Data Bit)
可以看看这个博主的

我们需要算出 B5 在 _L 寄存器 中的具体位置:
- A 组占用了前 8 位 (Bit 0 ~ Bit 7)。
- B 组紧随其后 (Bit 8 开始)。
- B5 是 B 组的第 5 号引脚 (从 0 开始计数)。
- 计算公式:
Bit_Index = (Group_Index * 8) + Pin_Index- B 组的 Group_Index 为 1 (A=0, B=1)。
1 * 8 + 5 = 13。
- 结论: GPIO4_B5 对应寄存器的 第 13 位 (Bit 13)。
第三步:计算写使能位 (Write Enable Bit)
根据 Rockchip 的"高 16 位写保护"规则:
- 写使能位 =
数据位 + 16。 13 + 16 = 29。- 结论: 我们需要把 第 29 位 (Bit 29) 置 1,才能修改 Bit 13 的值。
| 引脚 | 所属小组 | 对应寄存器 | 数据位 (Data Bit) | 写使能位 (Mask Bit) |
|---|---|---|---|---|
| GPIOx_A0~7 | A | _DR_L |
0 ~ 7 | 16 ~ 23 |
| GPIOx_B0~7 | B | _DR_L |
8 ~ 15 (B5是13) | 24 ~ 31 (B5是29) |
| GPIOx_C0~7 | C | _DR_H |
0 ~ 7 | 16 ~ 23 |
| GPIOx_D0~7 | D | _DR_H |
8 ~ 15 | 24 ~ 31 |
总结
首先找到相关的寄存器,然后根据需要去读写这个寄存器的值
引脚的输入输出模式

- GPIO_SWPORT_DDR_L:低位引脚数据方向寄存器,控制输入或者输出。
- GPIO_SWPORT_DDR_H:高位引脚数据方向寄存器,控制输入或者输出。

以GPIO_SWPORT_DDR_L寄存器说明,该寄存器有高16bit和低16bit,高16bit控制低16bit的写使能,低16bit控制GPIO的输出方向,GPIO_SWPORT_DDR_H同理。 如果要将GPIO4_B5设置为输出模式,那么就要写GPIO_SWPORT_DDR_L寄存器,因为B5属于GPIO4中A-D组总计64个引脚中的低32引脚范围,所以需要将GPIO_SWPORT_DDR_L寄存器的第13bit位和13+16bit位置1。
引脚的上下拉
对于上下拉,rk3588归到了这里

VCCIO6_IOC_GPIO4B_P Address: Operational Base + offset (0x0144)

VCCIO6_IOC_GPIO4B_P 寄存器有高16bit和低16bit,高16bit控制低16bit的写使能,低16bit控制GPIO的上下拉配置。 如果要将GPIO4_B5设置为上拉模式那么就将VCCIO6_IOC_GPIO4B_P 寄存器的第10bit位设置为1
3 软件代码配置
还记得我们的[led硬件配置过程](# 1.具体步骤 )吗
- 使能 clk
- 引脚模式------设置gpio模式
- 设置 输入 输出模式
- 设置输出高低电平 获取输入引脚的电平
由于1,2已经完成了,所以配3,4
引脚配置成什么模式
来,回顾一下引脚复用 ,我们用的引脚是gpio4_b5,通过芯片手册可以看到它可以被配置成不同的功能,而由于我们要的gpio功能刚好就是默认的功能,所以可以不用配置,如果要配置不同的功能,可以参考一下引脚复用 这边写的配置成pwm的例子,当然我们这边还是象征性地配置一下。
明确,led控制是通过MCU高低电平触发,所以是输出
1️⃣ 找到相应的引脚复用功能的寄存器 BUS_IOC
2️⃣ 对应的gpio4_b5 在这个寄存器上面对应的是哪一个位
3️⃣ 寄存器的读写------>看芯片手册
注意:linux是不能直接写物理地址的,所以需要虚拟化映射之后,再进行数据的读写操作
同时建议先对数据进行更改,然后再写入的操作,不要直接对原始的地址进行操作
比如:同样是操作gpio4_dr,可以直接操作
c
*gpio4_dr=(1<<(GPIO_PIN_BIT+16))|(0<<GPIO_PIN_BIT);
也可以这样
c
unsigned int data = (1 << (GPIO_PIN_BIT + 16)) | (0 << GPIO_PIN_BIT);
iowrite32(data, gpio4_dr);
这边推荐第二种~
好了,回到正轨:
这是配置gpio4_b5为gpio模式:
c
#define BUS_IOC (0xFD5F8000)
#define GPIO_SHITF 4//b5是H寄存器的第2个引脚 每个引脚4bit
#define GPIO4B_IOMUX_SET_L (BUS_IOC + 0x0088)
#define GPIO4B_IOMUX_SET_H (BUS_IOC + 0x008C)
static volatile unsigned int *gpio4_Iomux;
gpio4_Iomux=ioremap(GPIO4B_IOMUX_SET_H,4);
*gpio4_Iomux= (0xF << (shift + 16)) | (0x0 << shift);//把引脚设置为gpio模式
获取输入输出寄存器 和 高低电平寄存器地址
callback一下
手册上有这么一段

翻译一下就是:
在软件控制下,信号的数据与方向控制分别来自端口数据寄存器 (GPIO SWPORT DR L/ GPIO SWPORT DR H)和方向控制寄存器(GPIO SWPORT DDR L/ GPIO SWPORT DDR H)
外部I/O接口的输出方向由端口数据方向寄存器的数值决定。写入这些内存映射寄存器的数据会被映射到 GPIO 外设的输出信号(GPIO端口DDR)上,该信号控制着外部I/O接口的输出方向。默认数据方向为输入。写入端口数据寄存器的数据会驱动I/O接口的输出缓冲器(GPIO端口DR)
所谓数据寄存器,就是配置高低电平
方向寄存器,就是配置输入输出模式
首先确定GOIO4的基地址,其余寄存器均在基地址上进行偏移查询Rockchip_RK3588_TRM_Part1:

刚开始可能找不太到,这个时候可以直接在板卡上看,然后手册里找一下

于是得到GPIO4的基地址是:FEC50000
确定基地址后,需要确定其余寄存器对于基地址的偏移量,如下图所示

从上图可知需要设置的寄存器的地址为base+offset,编写代码如下:
c
#define GPIO4_BASE (FEC50000)
//一个寄存器32位,其中高16位都是写使能位,控制低16位的写使能;低16位对应16个引脚,控制引脚的输出电平
#define GPIO0_DR_L (GPIO4_BASE + 0x0000) // GPIO0的低十六位引脚的数据寄存器地址
#define GPIO0_DR_H (GPIO4_BASE + 0x0004) // GPIO0的高十六位引脚的数据寄存器地址
//一个寄存器32位,其中高16位都是写使能位,控制低16位的写使能;低16位对应16个引脚,控制引脚的输入输出模式
#define GPIO0_DDR_L (GPIO4_BASE + 0x0008) // GPIO0的低十六位引脚的数据方向寄存器地址
#define GPIO0_DDR_H (GPIO4_BASE + 0x000C) // GPIO0的低十六位引脚的数据方向寄存器地址
static volatile unsigned int *gpio4_dr;//数据
static volatile unsigned int *gpio4_ddr;//方向
gpio4_dr=ioremap(GPIO4_DR_L,4);
gpio4_ddr=ioremap(GPIO4_DDR_L,4);
//把引脚设置成输出
*gpio4_ddr=(1<<(GPIO_PIN_BIT+16))|(1<<GPIO_PIN_BIT);
//初始化为高电平
*gpio4_dr=(1<<(GPIO_PIN_BIT+16))|(1<<GPIO_PIN_BIT);
写完这些之后,就是日常的配置字符驱动该有的活了。
注册设备,创建设备节点....
具体可以看看野火官方或者韦东山老师讲的 比我这边详细的多得多
4最终的代码
❗️ 同时注意,鲁班猫系列板卡,系统设备树中均默认使能了 LED 的设备功能,需要关闭设备树的leds节点,可以修改leds节点的 status = "okay"; 为 status = "disabled";,然后编译设备树进行替换,也可以在板卡中直接使用以下命令关闭系统leds驱动对LED的控制:
sudo sh -c 'echo 0 > /sys/class/leds/sys_led/brightness'
将led的亮度调为0,与此同时led的触发条件自动变为none,从而取消leds驱动对LED的控制。
这步做完之后,再去按步骤做编译 运行等工作。
驱动代码
c
#include<linux/init.h>
#include<linux/module.h>
#include<linux/miscdevice.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/io.h>
#include <linux/kernel.h>
//操作GIPO4_B5
#define GPIO4_BASE (0xFEC50000) //GPIO4的基地址
#define GPIO_PIN_BIT 13
//一个寄存器32位,其中高16位都是写使能位,控制低16位的写使能;低16位对应16个引脚,控制引脚的输出电平
#define GPIO4_DR_L (GPIO4_BASE + 0x0000) // GPIO4的低十六位引脚的数据寄存器地址
#define GPIO4_DR_H (GPIO4_BASE + 0x0004) // GPIO4的高十六位引脚的数据寄存器地址
//一个寄存器32位,其中高16位都是写使能位,控制低16位的写使能;低16位对应16个引脚,控制引脚的输入输出模式
#define GPIO4_DDR_L (GPIO4_BASE + 0x0008) // GPIO4的低十六位引脚的数据方向寄存器地址
#define GPIO4_DDR_H (GPIO4_BASE + 0x000C) // GPIO4的高十六位引脚的数据方向寄存器地址
#define BUS_IOC (0xFD5F8000)
#define GPIO_SHITF 4//b5是H寄存器的第2个引脚 每个引脚4bit
#define GPIO4B_IOMUX_SET_L (BUS_IOC + 0x0088)
#define GPIO4B_IOMUX_SET_H (BUS_IOC + 0x008C)
static volatile unsigned int *gpio4_Iomux;
static volatile unsigned int *gpio4_dr;//数据
static volatile unsigned int *gpio4_ddr;//方向
static int major;
static struct class *led_class;
ssize_t led_write (struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
char val;
int ret;
ret=copy_from_user(&val,ubuf,1);
if(val=='0'){
//gpio =1
//假设输入0 为灭灯
//*gpio4_dr=(1<<(GPIO_PIN_BIT+16))|(1<<GPIO_PIN_BIT);
unsigned int data = (1 << (GPIO_PIN_BIT + 16)) | (1 << GPIO_PIN_BIT);
iowrite32(data, gpio4_dr);
}else if(val=='1'){
//假设输入1 为亮灯
//gpio =0
// *gpio4_dr=(1<<(GPIO_PIN_BIT+16))|(0<<GPIO_PIN_BIT);
unsigned int data = (1 << (GPIO_PIN_BIT + 16)) | (0 << GPIO_PIN_BIT);
iowrite32(data, gpio4_dr);
}
return size;
}
static int led_open(struct inode *inode,struct file *file){
//配置gpio
//*gpio4_Iomux= (0xF << (shift + 16)) | (0x0 << shift);//把引脚设置为gpio
//把引脚设置成输出
*gpio4_ddr=(1<<(GPIO_PIN_BIT+16))|(1<<GPIO_PIN_BIT);
//初始化为高电平
*gpio4_dr=(1<<(GPIO_PIN_BIT+16))|(1<<GPIO_PIN_BIT);
return 0;
}
static struct file_operations led_fops={
.owner=THIS_MODULE,
.open=led_open,
.write=led_write,
};
static int __init led_init(void){
printk("led_init\n");
gpio4_Iomux=ioremap(GPIO4B_IOMUX_SET_H,4);
gpio4_dr=ioremap(GPIO4_DR_L,4);
gpio4_ddr=ioremap(GPIO4_DDR_L,4);
major=register_chrdev(0,"myled",&led_fops);
led_class=class_create(THIS_MODULE,"myled");
//创建主设备号为major 次设备号为0 的class 创建/dev/myled 的节点
device_create(led_class,NULL,MKDEV(major,0),NULL,"myled");
return 0;
}
static void __exit led_exit(void){
device_destroy(led_class,MKDEV(major,0));
class_destroy(led_class);
iounmap(gpio4_Iomux);
iounmap(gpio4_dr);
iounmap(gpio4_ddr);
unregister_chrdev(major,"myled");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
应用代码
c
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main(int argc, char const *argv[])
{
// 1. 校验参数,防止用户没输入参数导致崩溃
if (argc != 2) {
printf("Usage: %s <1|0>\n", argv[0]);
return -1;
}
int fd = open("/dev/myled", O_RDWR);
if (fd < 0) {
perror("open error");
return -1;
}
// 2. 直接获取参数的第一个字符
// 如果输入 ./app 1,argv[1]是字符串 "1",argv[1][0] 就是字符 '1'
char mybuf = argv[1][0];
// 3. 写入数据
// 【关键】必须传变量的地址 &mybuf
write(fd, &mybuf, 1);
printf("Set LED to %c\n", mybuf);
close(fd);
return 0;
}