【嵌入式linux驱动——点亮led】基于鲁班猫4 rk3588s

【嵌入式linux驱动------点亮led】基于鲁班猫4 rk3588s

见野火给的官方资料手册:提取码:hslu

我是参考野火官方进行编程的,里面用的是rk3568,这边就是迁移记录一下,下面的图片是鲁班猫给出的修改代码,可以自行去看。 我这边走一遍3588的流程,自己去找一下手册 然后迁移一下。

这边要注意的是,寄存器的移位一定要熟练~ 同时在新手期也是比较容易出错的点~

文章目录

0 看看鲁班猫是哪个版本

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

bash 复制代码
cat /proc/device-tree/model

我这个版本是鲁班猫4-V1

1.具体步骤

这个步骤,如果学过单片机的uu应该很熟悉。点亮一盏灯

  1. 使能 clk
  2. 引脚模式------设置gpio模式
  3. 设置 输入 输出模式
  4. 设置输出高低电平 获取输入引脚的电平

所以我们的步骤------>

  1. 首先去找个引脚 使能一下这个gpio
  2. 然后找到gpio对应的地址(方便对其虚拟地址映射 和读写操作)
  3. 编写对应的驱动程序和应用程序

由于这边前两步骤------>配置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)内部结构如下:

  1. 分组 (Bank): 每个 GPIO 控制器(大组)有 32 个引脚
  2. 细分 (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.具体步骤 )吗

  1. 使能 clk
  2. 引脚模式------设置gpio模式
  3. 设置 输入 输出模式
  4. 设置输出高低电平 获取输入引脚的电平

由于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;
}
相关推荐
yuanmenghao2 小时前
车载Linux 系统问题定位方法论与实战系列 - 车载 Linux 平台问题定位规范
linux·运维·服务器·网络·c++
qq_589568103 小时前
centos6.8镜像源yum install不成功,无法通过镜像源下载的解决方式
linux·运维·centos
weixin_516023074 小时前
linux下fcitx5拼音的安装
linux·运维·服务器
hunter14504 小时前
Linux 进程与计划任务
linux·运维·服务器
楼田莉子5 小时前
Linux学习之磁盘与Ext系列文件
linux·运维·服务器·c语言·学习
陌上花开缓缓归以5 小时前
linux 怎么模拟系统panic重启
linux·运维·服务器
KL's pig/猪头/爱心/猪头5 小时前
写一个rv1106的led驱动3-功能函数编写
linux·驱动开发·rv1106
月白风清江有声5 小时前
vscode使用git
linux·运维·服务器
zl_dfq6 小时前
Linux 之 【文件】(ext2文件系统、目录、软硬链接)
linux