21、基于Firefly-rk3399字符设备驱动寄存器控制LED

文章目录

        • 一、电路分析
          • 引脚配置功能(R/W register)
        • 二、RK3399数据手册分析:
          • 1、GPIO(General-purpose input/output)介绍:
          • 2、CRU(Clock & Reset Unit)介绍
            • 查找GPIO相关内容:
          • 3、PMU(Power Management Uni)
          • 4、GRF(General Register Files)
        • 三、地址映射
          • 1、ioremap函数
          • 2、iounmap函数
          • 3、读操作函数
          • 4、写操作函数(地址为虚拟地址)
        • 四、驱动编写
          • 1、框架编写
            • 编译
          • 2、应用层框架
            • 编译
            • 执行
          • 3、基于Firefly-RK3399开发板编写字符设备驱动
            • (1)流程
            • (2)寄存器分析
            • (3)代码编写(单LED驱动代码)
          • 4、编译LED-filrefly驱动
          • 5、测试
          • 6、控制两个LED驱动
            • (1)寄存器分析:
            • (2)驱动修改
            • (3)编译
            • (4)测试
        • 五、应用层控制两个LED灯交替闪烁
          • 花样彩灯
        • 六、错误排查

开发平台:Firefly-Rk3399 2+16

一、电路分析

在这里插入图片描述

显然电路POS图可以看到两个LED分别为LED1和LED2,我们只需要根据元器件名称(网络名)找到对应的引脚即可,接下来查看电路图。搜索LED1可以找到这部分的模块。

在这里插入图片描述

分析上面电路:三极管正向导通,DIY_LED引脚输出高电平导通:灯亮。

三级管的作用: 兼容低电压(3.3/1.2v),对于NPN正向导通,P电压大于N, 即可导通
在这里插入图片描述
在这里插入图片描述

R307作用:保护led,给LED分流

由命名可知:WORK_LED为工作灯;DIY为用户自定义灯。

查看DIY引脚:GPIO0_B5

在这里插入图片描述

引脚配置功能(R/W register)

​ pwer

​ clock

​ mode

二、RK3399数据手册分析:

下载瑞芯微技术指导手册:

Rockchip_RK3399TRM_V1.4_Part1-20170408.pdf (rock-chips.com)

查看CRU PMU GPIO GRF章节

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

1、GPIO(General-purpose input/output)介绍:

在这里插入图片描述

GPIO 共有5组,GPIO0-GPIO4,分组信息如上。每一组又分为八个一组A B C D。对于GPIO的描述如:GPIO0_A6是指第一组的第6个引脚

GPIO_SWPORTA_DR data寄存器, 对于输出引脚有用, 共32位,对应一组里面的4组(ABCD)引脚。

在这里插入图片描述

GPIO_SWPORTA_DDR 方向寄存器,控制输入输出,0输入, 1输出, 共32位,对应一组里面的4组引脚。

在这里插入图片描述

GPIO_EXT_PORTA 外部寄存器, 输入模式时,读取寄存器对应位的值获取当前引脚的电平。共32位,对应一组里面的4组引脚。

在这里插入图片描述
为了便于后续驱动编写,整理表格如下

模块基地址寄存器偏移地址备注
GPIO00XFF720000(64K)///
GPIO10XFF730000(64K)///
GPIO20xFF780000(32K)///
GPIO30xFF788000(32K)///
GPIO40xFF790000(32K)///
GPIO_SWPORTA_DR0x0000data寄存器, 对于输出引脚有用, 共32位,对应一组里面的4组引脚
GPIO_SWPORTA_DDR0x0004方向寄存器,控制输入输出,0输入, 1输出, 共32位,对应一组里面的4组引脚
GPIO_EXT_PORTA0x0050外部寄存器, 输入模式时,读取寄存器对应位的值获取当前引脚的电平。共32位,对应一组里面的4组引脚
2、CRU(Clock & Reset Unit)介绍

CRU提供时钟流程:

查找时钟使能寄存器(PMUCRU_CLKGATE_CONx CRU_CLKGATE_CONx):PMU全称Power Management Unit

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

查找GPIO相关内容:

PMUCRU_CLKGATE_CON1 和PMUCRU_CLKGATE_CON31配置GPIO0-4

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对应位置0为使能clock。

需要注意:

在这里插入图片描述

修改寄存器之前需要修改对应位的mask。写1(high)使能mask。

在这里插入图片描述

Operational Base:CRU基地址如上图所示。

为了便于后续驱动编写,整理表格如下

模块基地址寄存器偏移地址偏移位数备注(mask位(WO)需置1用于启用对应的GATA位)
CRU0XFF760000
PMUCRU_CLKGATE_CON10x0104bit3(RW)pclk_gpio0 clock disable bit When HIGH, disable clock mask_bit:19(W1)
bit4(RW)pclk_gpio1 clock disable bit When HIGH, disable clock mask_bit:20(W1)
CRU_CLKGATE_CON310x037Cbit3(RW)pclk_gpio2 clock disable bit When HIGH, disable clock mask_bit:19(W1)
bit4(RW)pclk_gpio3 clock disable bit When HIGH, disable clock mask_bit:20(W1)
bit5(RW)pclk_gpio4 clock disable bit When HIGH, disable clock mask_bit:21(W1)
3、PMU(Power Management Uni)

在这里插入图片描述

IOMUX:实现IO口的分时复用,在使用GPIO之前需要先配置IOMUX

根据DATASHEET

在PMU部分配置如下:

在这里插入图片描述

先看PMU_GRF下的引脚配置:

PMUGRF_GPIO0A_IOMUX

在这里插入图片描述

[16:31]位为写使能位,写1使能,后面每两位代表一个引脚,写入对应的值代表不同的功能。比如要配置GPIOA_7为admmc_dectn, 此时15位为0, 14位为1。

reg | (1 << 14)。14位指的是数据的第14位。

PMUGRF_GPIO0A_IOMUX

PMUGRF_GPIO0B_IOMUX

PMUGRF_GPIO1A_IOMUX

PMUGRF_GPIO1B_IOMUX

PMUGRF_GPIO1C_IOMUX

PMUGRF_GPIO1D_IOMUX

为了便于后续驱动编写,整理表格如下

模块基地址寄存器偏移地址备注
PMU_GRF0xFF320000PMUGRF_GPIO0A_IOMUX0x00000GPIO0A iomux control
PMUGRF_GPIO0B_IOMUX0x00004GPIO0B iomux control
PMUGRF_GPIO1A_IOMUX0x00010GPIO1A iomux control
PMUGRF_GPIO1B_IOMUX0x00014GPIO1B iomux control
PMUGRF_GPIO1C_IOMUX0x00018GPIO1C iomux control
PMUGRF_GPIO1D_IOMUX0x0001cGPIO1D iomux control
4、GRF(General Register Files)

在这里插入图片描述
为了便于后续驱动编写,整理表格如下

模块基地址寄存器偏移地址备注
GRF0xFF770000
GRF_GPIO2A_IOMUX0x0e000GPIO2A iomux control
GRF_GPIO2B_IOMUX0x0e004GPIO2B iomux control
GRF_GPIO2C_IOMUX0x0e008GPIO2C iomux control
GRF_GPIO2D_IOMUX0x0e00cGPIO2D iomux control
GRF_GPIO3A_IOMUX0x0e010GPIO3A iomux control
GRF_GPIO3B_IOMUX0x0e014GPIO3B iomux control
GRF_GPIO3C_IOMUX0x0e018GPIO3C iomux control
GRF_GPIO3D_IOMUX0x0e01CGPIO3D iomux control
GRF_GPIO4A_IOMUX0x0e020GPIO4A iomux control
GRF_GPIO4B_IOMUX0x0e024GPIO4B iomux control
GRF_GPIO4C_IOMUX0x0e028GPIO4C iomux control
GRF_GPIO4D_IOMUX0x0e02CGPIO4D iomux control
三、地址映射

​ Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚拟地址。

1、ioremap函数

ioremap 函 数 用 于 获 取 指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间 , 定 义 在arch/arm/include/asm/io.h 文件中,定义如下:

void __iomem *ioremap(resource_size_t res_cookie, size_t size);
res_cookie:要映射的物理起始地址。
size:要映射的内存空间大小。
返回值:__iomem 类型的指针,指向映射后的虚拟空间首地址。整页映射:4096字节

例如:

假如我们要获取GPIOI_MODER 寄存器对应的虚拟地址,使用如下代码即可:
#define GPIOI_MODER (0X5000A000)
static void __iomem* GPIO_MODER_PI;
GPIO_MODER_PI = ioremap(GPIOI_MODER, 4);

宏 GPIOI_MODER 是寄存器物理地址,GPIO_MODER_PI 是映射后的虚拟地址。对于RK3399来说一个寄存器是 4 字节(32 位),因此映射的内存长度为 4。映射完成以后直接对 GPIO_MODER_PI 进行读写操作即可。

2、iounmap函数

卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射,iounmap 函数原型如下:

void iounmap (volatile void __iomem *addr)

iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址。假如我们现在要取消掉 GPIO_MODER_PI 寄存器的地址映射,使用如下代码即可:

iounmap(GPIO_MODER_PI);

申请到的虚拟地址有MMU(Memory manager UNIT)做隔离之后MMU进行地址映射,并且会做权限保护。

3、读操作函数
u8 readb(const volatile void __iomem *addr) 	8bit读操作,addr 就是要读取写内存地址,返回值就是读取到的数据
u16 readw(const volatile void __iomem *addr)	16bit读操作,addr 就是要读取写内存地址,返回值就是读取到的数据
u32 readl(const volatile void __iomem *addr)	32bit读操作,addr 就是要读取写内存地址,返回值就是读取到的数据
4、写操作函数(地址为虚拟地址)
void writeb(u8 value, volatile void __iomem *addr)		8bit,参数 value 是要写入的数值,addr 是要写入的地址。
void writew(u16 value, volatile void __iomem *addr)		16bit,参数 value 是要写入的数值,addr 是要写入的地址。
void writel(u32 value, volatile void __iomem *addr)		32bit,参数 value 是要写入的数值,addr 是要写入的地址。
四、驱动编写
1、框架编写

leds.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/device.h>#include "led_operation.h"/* 设备节点名 加载驱动后:cat /proc/devices | grep diy_led 可以查到它的设备号 */
#define LED_NAME "diy_led"
/* 设备子节点个数 */
#define LED_NUM 2/* 设备号 static防止命名污染 */
static int major = 0;
/* 设备节点类 */
static struct class *led_class;
/* 自定义封装的结构体的指针,详见led_operations.h文件内的struct led_operations,下文有写 */
struct led_operations *p_ledopr;/* 对应app中的open() */
static int led_open(struct inode *inode, struct file *file) {/* 通过文件的inode号唯一标识获取子设备号 */int minor = iminor(inode);printk("%s  %s  %d led device open\r\n", __FILE__, __FUNCTION__, __LINE__);/* init(minor)通过子设备号初始化LED, init函数见board_demo文件 */p_ledopr->init(minor);return 0;
}static ssize_t led_read(struct file *file, char __user *buf, size_t cnt, loff_t *offt) {printk("%s  %s  %d led device read\r\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}static ssize_t led_write(struct file *file, const char __user *buf, size_t cnt, loff_t *offt) {int err;char status;/* 同上 参考的drivers/char/dsp56k.c文件用法*/struct inode *inode = file_inode(file);int minor = iminor(inode);printk("%s  %s  %d led device write\r\n", __FILE__, __FUNCTION__, __LINE__);err = copy_from_user(&status, buf, 1);/* Control LED by status and sub-device number.*/p_ledopr->ctl(minor, status);return 1;
}static int led_release(struct inode *inode, struct file *file) {printk("%s  %s  %d led device release\r\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}const struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.release = led_release,.read = led_read,.write = led_write,
};/* First run function */
static int __init led_init(void)
{int i = 0;printk("led init.\r\n");/* 参数 1 设置 0 , 动态申请主设备号, 返回值为申请到的主设备号 注册设备驱动*/major = register_chrdev(0, LED_NAME, &led_fops);/* 申请类 */led_class = class_create(THIS_MODULE, "LED_CLASS");/* 在led_class类下申请两个设备节点 */for(i=0; i<LED_NUM; i++) {/* MKDEV(major, i) 通过主设备号和次设备号生成设备号  "diy_led_%d", i 为设备节点名 */device_create(led_class, NULL, MKDEV(major, i), NULL, "diy_led_%d", i);}/*调用 board_demo 文件(见下文)中的 get_board_led_opr,参数为 void 返回值为struct led_operations *的函数结构体指针*/p_ledopr = get_board_led_opr();return 0;
}static void __exit led_exit(void) 
{int i = 0;printk("led exit!!!\r\n");/* 销毁子设备节点 */for(i=0; i<LED_NUM; i++) {device_destroy(led_class, MKDEV(major, i));}/* 销毁类 */class_destroy(led_class); /* 取消注册设备驱动 */unregister_chrdev(major, LED_NAME);
}module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("liu");
MODULE_INFO(intree, "Y");

led_operations.h

#ifndef _LED_OPR_H
#define _LED_OPR_H struct led_operations {/* 函数指针init和ctl */int (*init) (int which);int (*ctl) (int which, char status);
};
/* 函数声明 */
struct led_operations *get_board_led_opr(void);#endif

board_demo.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/device.h>#include "led_operation.h"static int board_demo_init(int which) {printk("%s %s %d led %d\r\n", __FILE__, __FUNCTION__, __LINE__, which);return 0;
}static int board_demo_ctl(int which, char status) {printk("%s %s %d led %d %s\r\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");return 0;
}static struct led_operations board_demo_led_opr = {.init = board_demo_init,.ctl = board_demo_ctl,
};struct led_operations *get_board_led_opr(void) {return &board_demo_led_opr;
}

Makefile

KERNELDIR := /home/liu/rockchip/kernel/kernel-develop-4.4_back
CURRENT_PATH := $(shell pwd)
leds-y := led.o board_demo.o
obj-m := leds.obuild:kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
编译

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

生成 leds.ko

2、应用层框架

led_test.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>int main(int argc, const char *argv[]) {int fd; char status;int ret;if(argc != 3) {printf("para must rathor than three, Usage: ./%s <dev> <on/off>", argv[0]);return -1;}fd = open(argv[1], O_RDWR);if(fd < 0) {printf("can't open file %s\n", argv[1]);return -1;}if(strcmp("on", argv[2]) == 0) {printf("I'll open led\n");status = 1;}else {printf("I'll close led\n");status = 0;}/* 写0或写1 */ret = write(fd, &status, 1);close(fd);return 0;
}
编译

gcc led_test.c -o led_test

执行

./led_test /dev/diy_led_0 on

./led_test /dev/diy_led_0 off

./led_test /dev/diy_led_1 on

./led_test /dev/diy_led_1 off

3、基于Firefly-RK3399开发板编写字符设备驱动

由电路分析章节可以得到:DIY引脚为:GPIO0_B5,且输出高电平LED灯亮,输出低电平LED灯灭,且兼容1.2v和3.3v

(1)流程

​ 使能时钟引脚CRU(clock reset unit)

​ 设置GPIO功能IOMUX

​ 设置GPIO输出GPIO方向

​ 设置GPIO data寄存器

(2)寄存器分析

写使能位对应关系:

​ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

​ 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

GPIO0_B5需要用到的地址有:

PMUCRU_CLKGATE_CON1:使能GPIO0需要给它bit3置0,bit19(mask位)置一

#define PMUCRU_CLKGATE_CON1 (0xFF760000 + 0x0104)
(*PMUCRU_CLKGATE_CON1) | (1 << 19) & (~(1 << 3))
// 因为只有把mask位写1才会写入寄存器的值,所以不需要按位去写入
// 直接赋值
*PMUCRU_CLKGATE_CON1 = (1 << 3 + 16) & (0 << 3);

​ PMUGRF_GPIO0B_IOMUX:设置 GPIO0B 用于 GPIO,查找GPIO0_B5

在这里插入图片描述

需要给第10 11位写0,并且根据上面的写使能对应关系可以得知给26、27位置一。

#define PMUGRF_GPIO0B_IOMUX (0xFF320000 + 0x0004);
(*PMUGRF_GPIO0B_IOMUX) | (3 << 26) & (~(3 << 10))
// 因为只有把mask位写1才会写入寄存器的值,所以不需要按位去写入
// 直接赋值
*PMUGRF_GPIO0B_IOMUX = (3 << 10 + 16) & (0 << 10);

GPIO_SWPORTA_DR:设置 GPIO 输出高电平

在这里插入图片描述

根据手册:需要将B5引脚置一,B5为GPIO0的第二组的第5个(0开始数)引脚,所以需要偏移 8 + 5 = 13

#define GPIO_SWPORTA_DR (0XFF720000 + 0x0000);
(*GPIO_SWPORTA_DR) | (1 << 13);

GPIO_SWPORTA_DR:设置 GPIO 输出低电平

*GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) & (~(1 << 13));

GPIO_SWPORTA_DDR:设置 GPIO 作为 output 引脚

在这里插入图片描述

同GPIO_SWPORTA_DR,将第13位置一设置为输出引脚

#define GPIO_SWPORTA_DDR (0XFF720000 + 0x0004);

​ GPIO_EXT_PORTA:读取 GPIO 引脚电平

#define GPIO_EXT_PORTA (0XFF720000 + 0x0050);
(3)代码编写(单LED驱动代码)

led_firefly.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/device.h>#include "led_operation.h"#define LED_NAME "diy_led"
// #define LED_NUM 2static int major = 0;
static struct class *led_class;
struct led_operations *p_ledopr;static int led_open(struct inode *inode, struct file *file) {int minor = iminor(inode);printk("%s  %s  %d led device open\r\n", __FILE__, __FUNCTION__, __LINE__);/*Init LED device by sub-device number */p_ledopr->init(minor);return 0;
}static ssize_t led_read(struct file *file, char __user *buf, size_t cnt, loff_t *offt) {printk("%s  %s  %d led device read\r\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}static ssize_t led_write(struct file *file, const char __user *buf, size_t cnt, loff_t *offt) {int err;char status;struct inode *inode = file_inode(file);int minor = iminor(inode);printk("%s  %s  %d led device write\r\n", __FILE__, __FUNCTION__, __LINE__);err = copy_from_user(&status, buf, 1);/*Control LED by status and sub-device number.*/p_ledopr->ctl(minor, status);return 1;
}static int led_release(struct inode *inode, struct file *file) {printk("%s  %s  %d led device release\r\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}const struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.release = led_release,.read = led_read,.write = led_write,
};static int __init led_init(void)
{int i = 0;printk("led init.\r\n");major = register_chrdev(0, LED_NAME, &led_fops);led_class = class_create(THIS_MODULE, "LED_CLASS");p_ledopr = get_board_led_opr();for(i=0; i<p_ledopr->num; i++) {device_create(led_class, NULL, MKDEV(major, i), NULL, "diy_led_%d", i);}return 0;
}static void __exit led_exit(void) 
{int i = 0;printk("led exit!!!\r\n");for(i=0; i<p_ledopr->num; i++) {device_destroy(led_class, MKDEV(major, i));}class_destroy(led_class); unregister_chrdev(major, LED_NAME);
}module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("liu");
MODULE_INFO(intree, "Y");

board_firefly.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/device.h>#include "led_operation.h"static volatile unsigned  int *PMUCRU_CLKGATE_CON1;
static volatile unsigned  int *PMUGRF_GPIO0B_IOMUX;
static volatile unsigned  int *GPIO_SWPORTA_DR;
static volatile unsigned  int *GPIO_SWPORTA_DDR;
// static volatile unsigned  int *GPIO_EXT_PORTA;/* 此处代码由3、2寄存器分析章节详细分析 */
static int board_demo_init(int which) {printk("%s %s %d led %d\r\n", __FILE__, __FUNCTION__, __LINE__, which);if(!PMUCRU_CLKGATE_CON1)PMUCRU_CLKGATE_CON1 = ioremap(0xFF760000 + 0x0104, 4);if(!PMUGRF_GPIO0B_IOMUX)PMUGRF_GPIO0B_IOMUX = ioremap(0xFF320000 + 0x0004, 4);if(!GPIO_SWPORTA_DR)GPIO_SWPORTA_DR = ioremap(0XFF720000 + 0x0000, 4);	if(!GPIO_SWPORTA_DDR)GPIO_SWPORTA_DDR = ioremap(0XFF720000 + 0x0004, 4);/* operations register */*PMUCRU_CLKGATE_CON1 = (1 << (3 + 16)) & (0 << 3);*PMUGRF_GPIO0B_IOMUX = (3 << (10 + 16)) & (0 << 10);*GPIO_SWPORTA_DDR = (*GPIO_SWPORTA_DDR) | (1 << 13);return 0;
}static int board_demo_ctl(int witch, char status) {printk("%s %s %d led %d %s\r\n", __FILE__, __FUNCTION__, __LINE__, witch, status ? "on" : "off");/* 此处代码由3、2寄存器分析章节详细分析 */if(witch == 0) {if(status)*GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) | (1 << 13);else*GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) & (~(1 << 13));}return 0;
}static struct led_operations board_demo_led_opr = {.num = 1,.init = board_demo_init,.ctl = board_demo_ctl,
};struct led_operations *get_board_led_opr(void) {return &board_demo_led_opr;
}

led_operation.h

#ifndef _LED_OPR_H
#define _LED_OPR_H struct led_operations {int num;int (*init) (int which);int (*ctl) (int which, char status);
};struct led_operations *get_board_led_opr(void);#endif

Makefile

KERNELDIR := /home/liu/rockchip/kernel/kernel-develop-4.4_back
CURRENT_PATH := $(shell pwd)
leds_firefly-y := led_firefly.o board_firefly.o
obj-m := leds_firefly.obuild:kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
4、编译LED-filrefly驱动

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

5、测试

加载驱动

insmod leds_firefly.ko

applycation程序测试亮灭

./led_test /dev/diy_led_0 on

./led_test /dev/diy_led_0 off

可以观察到LED灯的亮灭

内核打印如下:

[16159.055914] /home/liu/rockchip/kernel/Drivers/4_led_firefly/led_firefly.c  led_open  21 led device open
[16159.055928] /home/liu/rockchip/kernel/Drivers/4_led_firefly/board_firefly.c board_demo_init 18 led 0
[16159.056226] /home/liu/rockchip/kernel/Drivers/4_led_firefly/led_firefly.c  led_write  41 led device write
[16159.056234] /home/liu/rockchip/kernel/Drivers/4_led_firefly/board_firefly.c board_demo_ctl 38 led 0 on
[16159.056244] /home/liu/rockchip/kernel/Drivers/4_led_firefly/led_firefly.c  led_release  51 led device release
[16229.367954] /home/liu/rockchip/kernel/Drivers/4_led_firefly/led_firefly.c  led_open  21 led device open
[16229.367967] /home/liu/rockchip/kernel/Drivers/4_led_firefly/board_firefly.c board_demo_init 18 led 0
[16229.368161] /home/liu/rockchip/kernel/Drivers/4_led_firefly/led_firefly.c  led_write  41 led device write
[16229.368169] /home/liu/rockchip/kernel/Drivers/4_led_firefly/board_firefly.c board_demo_ctl 38 led 0 off
[16229.368193] /home/liu/rockchip/kernel/Drivers/4_led_firefly/led_firefly.c  led_release  51 led device release
6、控制两个LED驱动

已知GPIO0_B5为DIY_LED,且实现了点亮

对于另一颗LED Work_Led,通过网络号查找电路原理图:

在这里插入图片描述

所以我们只需要配置GPIO2_D3的引脚即可。

(1)寄存器分析:

​ 使能时钟引脚CRU(clock reset unit)

​ GPIO2:CRU_CLKGATE_CON31

#define PMUCRU_CLKGATE_CON31 (0xFF760000 + 0x037C)
/*使能GPIO Clock*/
*PMUCRU_CLKGATE_CON31 = (1 << 3 + 16) | (0 << 3);

​ 设置GPIO功能IOMUX

​ GPIO2_D3:GRF_GPIO2D_IOMUX

在这里插入图片描述

#define GRF_GPIO2D_IOMUX (0xFF770000 + 0x0e00c);
/*设置引脚为GPIO引脚功能*/
*PMUGRF_GPIO2D_IOMUX = (3 << 6 + 16) | (0 << 6);

​ 设置GPIO输出GPIO方向

GPIO_SWPORTA_DR

​ D3: <==> GPIO2的第 24 + 3 = 27 个引脚

​ 基地址如下

在这里插入图片描述

#define GPIO_SWPORTA_DR (0XFF780000 + 0x0000);
/*输出高电平*/
(*GPIO_SWPORTA_DR) | (1 << 27);
/*输出低电平*/
*GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) & (~(1 << 27));

​ 设置GPIO data寄存器

GPIO_SWPORTA_DDR

#define GPIO_SWPORTA_DDR (0XFF780000 + 0x0004);
*GPIO2_SWPORTA_DDR = (*GPIO2_SWPORTA_DDR) | (1 << 27);
(2)驱动修改

修改 board_firefly.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/device.h>#include "led_operation.h"static volatile unsigned  int *PMUCRU_CLKGATE_CON1;
static volatile unsigned  int *PMUGRF_GPIO0B_IOMUX;
static volatile unsigned  int *GPIO_SWPORTA_DR;
static volatile unsigned  int *GPIO_SWPORTA_DDR;static volatile unsigned  int *PMUCRU_CLKGATE_CON31;
static volatile unsigned  int *PMUGRF_GPIO2D_IOMUX;
static volatile unsigned  int *GPIO2_SWPORTA_DR;
static volatile unsigned  int *GPIO2_SWPORTA_DDR;// static volatile unsigned  int *GPIO_EXT_PORTA;static int board_demo_init(int witch) {printk("%s %s %d led %d\r\n", __FILE__, __FUNCTION__, __LINE__, witch);if(witch == 0) {/* GPIO0_B5 */if(!PMUCRU_CLKGATE_CON1)PMUCRU_CLKGATE_CON1 = ioremap(0xFF760000 + 0x0104, 4);if(!PMUGRF_GPIO0B_IOMUX)PMUGRF_GPIO0B_IOMUX = ioremap(0xFF320000 + 0x0004, 4);if(!GPIO_SWPORTA_DR)GPIO_SWPORTA_DR = ioremap(0XFF720000 + 0x0000, 4);	if(!GPIO_SWPORTA_DDR)GPIO_SWPORTA_DDR = ioremap(0XFF720000 + 0x0004, 4);/* GPIO0_B5 operations register */*PMUCRU_CLKGATE_CON1 = (1 << (3 + 16)) | (0 << 3);*PMUGRF_GPIO0B_IOMUX = (3 << (10 + 16)) | (0 << 10);*GPIO_SWPORTA_DDR = (*GPIO_SWPORTA_DDR) | (1 << 13);}/* 新增work_led */if(witch == 1) {/* GPIO2_D3 */if(!PMUCRU_CLKGATE_CON31)PMUCRU_CLKGATE_CON31 = ioremap(0xFF760000 + 0x037C, 4);if(!PMUGRF_GPIO2D_IOMUX)PMUGRF_GPIO2D_IOMUX = ioremap(0xFF770000 + 0x0e00c, 4);if(!GPIO2_SWPORTA_DR)GPIO2_SWPORTA_DR = ioremap(0XFF780000 + 0x0000, 4);	if(!GPIO2_SWPORTA_DDR)GPIO2_SWPORTA_DDR = ioremap(0XFF780000 + 0x0004, 4);/* GPIO2_D3 operations register */*PMUCRU_CLKGATE_CON31 = (1 << (3 + 16)) | (0 << 3);*PMUGRF_GPIO2D_IOMUX = (3 << (6 + 16)) | (0 << 6);*GPIO2_SWPORTA_DDR = (*GPIO2_SWPORTA_DDR) | (1 << 27);}return 0;
}static int board_demo_ctl(int witch, char status) {printk("%s %s %d led %d %s\r\n", __FILE__, __FUNCTION__, __LINE__, witch, status ? "on" : "off");if(witch == 0) {if(status)*GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) | (1 << 13);else*GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) & (~(1 << 13));}/* 新增work_led */else if(witch == 1) {if(status)*GPIO2_SWPORTA_DR = (*GPIO2_SWPORTA_DR) | (1 << 27);else*GPIO2_SWPORTA_DR = (*GPIO2_SWPORTA_DR) & (~(1 << 27));}return 0;
}static int board_demo_exit(int witch) {if(witch == 0) {iounmap(PMUCRU_CLKGATE_CON1);iounmap(PMUGRF_GPIO0B_IOMUX);iounmap(GPIO_SWPORTA_DR);iounmap(GPIO_SWPORTA_DDR);}/* 新增work_led*/else if(witch == 1) {iounmap(PMUCRU_CLKGATE_CON31);iounmap(PMUGRF_GPIO2D_IOMUX);iounmap(GPIO2_SWPORTA_DR);iounmap(GPIO2_SWPORTA_DDR);}return 0;
}static struct led_operations board_demo_led_opr = {/* 创建两个子设备节点 */ .num = 2,.init = board_demo_init,.ctl = board_demo_ctl,/* 取消映射 */.exit = board_demo_exit,
};struct led_operations *get_board_led_opr(void) {return &board_demo_led_opr;
}

修改board_firefly.c的头文件:led_operation.h

#ifndef _LED_OPR_H
#define _LED_OPR_H struct led_operations {int num;int (*init) (int which);int (*ctl) (int which, char status);/*增加iounmap*/int (*exit) (int which);
};struct led_operations *get_board_led_opr(void);#endif

修改led_firefly.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/device.h>#include "led_operation.h"#define LED_NAME "diy_led"static int major = 0;
static struct class *led_class;
struct led_operations *p_ledopr;static int led_open(struct inode *inode, struct file *file) {int minor = iminor(inode);printk("%s  %s  %d led device open\r\n", __FILE__, __FUNCTION__, __LINE__);/*Init LED device by sub-device number */p_ledopr->init(minor);return 0;
}static ssize_t led_read(struct file *file, char __user *buf, size_t cnt, loff_t *offt) {printk("%s  %s  %d led device read\r\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}static ssize_t led_write(struct file *file, const char __user *buf, size_t cnt, loff_t *offt) {int err;char status;struct inode *inode = file_inode(file);int minor = iminor(inode);printk("%s  %s  %d led device write\r\n", __FILE__, __FUNCTION__, __LINE__);err = copy_from_user(&status, buf, 1);/*Control LED by status and sub-device number.*/p_ledopr->ctl(minor, status);return 1;
}static int led_release(struct inode *inode, struct file *file) {int minor = iminor(inode);printk("%s  %s  %d led device release\r\n", __FILE__, __FUNCTION__, __LINE__);//iounmap 对应的设备子节点p_ledopr->exit(minor);return 0;
}const struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.release = led_release,.read = led_read,.write = led_write,
};static int __init led_init(void)
{int i = 0;printk("led init.\r\n");major = register_chrdev(0, LED_NAME, &led_fops);led_class = class_create(THIS_MODULE, "LED_CLASS");p_ledopr = get_board_led_opr();/*创建两个设备子节点*/for(i=0; i<p_ledopr->num; i++) {device_create(led_class, NULL, MKDEV(major, i), NULL, "diy_led_%d", i);}return 0;
}static void __exit led_exit(void) 
{int i = 0;printk("led exit!!!\r\n");/* 销毁两个设备子节点 */for(i=0; i<p_ledopr->num; i++) {device_destroy(led_class, MKDEV(major, i));}class_destroy(led_class); unregister_chrdev(major, LED_NAME);
}module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("liu");
MODULE_INFO(intree, "Y");
(3)编译

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

(4)测试
root@liu:/home/zhisensor# rmmod leds_firefly.ko
root@liu:/home/zhisensor# insmod leds_firefly.ko
root@liu:/home/zhisensor# ./leds_ctl /dev/diy_led_0 off
I'll close led
root@liu:/home/zhisensor# ./leds_ctl /dev/diy_led_0 on
I'll open led
root@liu:/home/zhisensor# ./leds_ctl /dev/diy_led_1 on
I'll open led
root@liu:/home/zhisensor# ./leds_ctl /dev/diy_led_1 off
内核日志:
[ 1583.113405] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c  led_open  21 led device open
[ 1583.113435] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_init 24 led 0
[ 1583.113737] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c  led_write  41 led device write
[ 1583.113744] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_ctl 59 led 0 off
[ 1583.113753] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c  led_release  51 led device release
[ 1585.539956] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c  led_open  21 led device open
[ 1585.539986] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_init 24 led 0
[ 1585.540399] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c  led_write  41 led device write
[ 1585.540423] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_ctl 59 led 0 on
[ 1585.540447] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c  led_release  51 led device release
[ 1591.806877] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c  led_open  21 led device open
[ 1591.806890] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_init 24 led 1
[ 1591.807037] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c  led_write  41 led device write
[ 1591.807044] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_ctl 59 led 1 on
[ 1591.807053] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c  led_release  51 led device release
[ 1595.141492] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c  led_open  21 led device open
[ 1595.141521] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_init 24 led 1
[ 1595.141856] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c  led_write  41 led device write
[ 1595.141863] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_ctl 59 led 1 off
[ 1595.141872] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c  led_release  51 led device release
五、应用层控制两个LED灯交替闪烁
花样彩灯
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>int main(int argc, const char *argv[])
{int fd_led1, fd_led2;unsigned char led1, led2;// Open the LED0 filesfd_led1 = open("/dev/diy_led_0", O_WRONLY);if (fd_led1 < 0) {printf("Error opening /dev/diy_led_0\n");return 1;}// Open the LED1 filesfd_led2 = open("/dev/diy_led_1", O_WRONLY);if (fd_led2 < 0) {printf("Error opening /dev/diy_led_1\n");return 1;}led1 = 0;led2 = 0;int mode = 0;while(1) {// Set LED0 to ONif(mode == 0) {mode = 30;}if(mode < 5) {led1 = !led1;write(fd_led1, &led1, 1);usleep(1000 * 100);// Set LED1 to OFFled2 = !led2;write(fd_led2, &led2, 1);usleep(1000 * 100);}else if (mode < 10) {led1 = !led1;write(fd_led1, &led1, 1);// Set LED1 to OFFled2 = !led2;write(fd_led2, &led2, 1);usleep(1000 * 100);}else if (mode < 15) {led1 = !led1;write(fd_led1, &led1, 1);usleep(1000 * 100); }else if (mode <= 20) {led2 = !led2;write(fd_led2, &led2, 1);usleep(1000 * 100); }else if (mode < 30) {led1 = !led1;write(fd_led1, &led1, 1);// Set LED1 to OFFled2 = !led2;write(fd_led2, &led2, 1);usleep(1000 * 300);}mode--;}close(fd_led1);close(fd_led2);return 0;
}
六、错误排查

多次测试报错如下

 /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c  led_open  21 led device open
[ 7407.030865] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_init 24 led 0
[ 7407.031756] Unable to handle kernel paging request at virtual address ffffff800b2aa104
[ 7407.032466] pgd = ffffffc076bb9000
[ 7407.032768] [ffffff800b2aa104] *pgd=0000000000000000, *pud=0000000000000000
[ 7407.033416] Internal error: Oops: 96000047 [#4] SMP
[ 7407.033844] Modules linked in: leds_firefly
[ 7407.034235] CPU: 4 PID: 1247 Comm: blingblingled Tainted: G      D         4.4.194 #2
[ 7407.034925] Hardware name: Rockchip RK3399 Firefly Board (Linux Opensource) (DT)
[ 7407.035579] task: ffffffc07836de80 task.stack: ffffffc07255c000
[ 7407.036107] PC is at board_demo_init+0xcc/0x1e0 [leds_firefly]
[ 7407.036627] LR is at board_demo_init+0x30/0x1e0 [leds_firefly]

修改board_firefly.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/device.h>#include "led_operation.h"static volatile unsigned  int *PMUCRU_CLKGATE_CON1;
static volatile unsigned  int *PMUGRF_GPIO0B_IOMUX;
static volatile unsigned  int *GPIO_SWPORTA_DR;
static volatile unsigned  int *GPIO_SWPORTA_DDR;static volatile unsigned  int *PMUCRU_CLKGATE_CON31;
static volatile unsigned  int *PMUGRF_GPIO2D_IOMUX;
static volatile unsigned  int *GPIO2_SWPORTA_DR;
static volatile unsigned  int *GPIO2_SWPORTA_DDR;// static volatile unsigned  int *GPIO_EXT_PORTA;static int board_demo_init(void) {printk("%s %s %d\r\n", __FILE__, __FUNCTION__, __LINE__);/* GPIO0_B5 */if(!PMUCRU_CLKGATE_CON1)PMUCRU_CLKGATE_CON1 = ioremap(0xFF760000 + 0x0104, 4);elseprintk(KERN_ERR "Failed to map PMUCRU_CLKGATE_CON1\r\n");if(!PMUGRF_GPIO0B_IOMUX)PMUGRF_GPIO0B_IOMUX = ioremap(0xFF320000 + 0x0004, 4);elseprintk(KERN_ERR "Failed to map PMUGRF_GPIO0B_IOMUX\r\n");if(!GPIO_SWPORTA_DR)GPIO_SWPORTA_DR = ioremap(0XFF720000 + 0x0000, 4);	elseprintk(KERN_ERR "Failed to map GPIO_SWPORTA_DR\r\n");if(!GPIO_SWPORTA_DDR)GPIO_SWPORTA_DDR = ioremap(0XFF720000 + 0x0004, 4);elseprintk(KERN_ERR "Failed to map GPIO_SWPORTA_DDR\r\n");/* GPIO0_B5 operations register */*PMUCRU_CLKGATE_CON1 = (1 << (3 + 16)) | (0 << 3);*PMUGRF_GPIO0B_IOMUX = (3 << (10 + 16)) | (0 << 10);*GPIO_SWPORTA_DDR = (*GPIO_SWPORTA_DDR) | (1 << 13);/* GPIO2_D3 */if(!PMUCRU_CLKGATE_CON31)PMUCRU_CLKGATE_CON31 = ioremap(0xFF760000 + 0x037C, 4);elseprintk(KERN_ERR "Failed to map PMUCRU_CLKGATE_CON31\r\n");if(!PMUGRF_GPIO2D_IOMUX)PMUGRF_GPIO2D_IOMUX = ioremap(0xFF770000 + 0x0e00c, 4);elseprintk(KERN_ERR "Failed to map PMUGRF_GPIO2D_IOMUX\r\n");if(!GPIO2_SWPORTA_DR)GPIO2_SWPORTA_DR = ioremap(0XFF780000 + 0x0000, 4);elseprintk(KERN_ERR "Failed to map GPIO2_SWPORTA_DR\r\n");if(!GPIO2_SWPORTA_DDR)GPIO2_SWPORTA_DDR = ioremap(0XFF780000 + 0x0004, 4);elseprintk(KERN_ERR "Failed to map GPIO2_SWPORTA_DDR\r\n");/* GPIO2_D3 operations register */*PMUCRU_CLKGATE_CON31 = (1 << (3 + 16)) | (0 << 3);*PMUGRF_GPIO2D_IOMUX = (3 << (6 + 16)) | (0 << 6);*GPIO2_SWPORTA_DDR = (*GPIO2_SWPORTA_DDR) | (1 << 27);return 0;
}static int board_demo_ctl(int witch, char status) {printk("%s %s %d led %d %s\r\n", __FILE__, __FUNCTION__, __LINE__, witch, status ? "on" : "off");if(witch == 0) {if(status)*GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) | (1 << 13);else*GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) & (~(1 << 13));}else if(witch == 1) {if(status)*GPIO2_SWPORTA_DR = (*GPIO2_SWPORTA_DR) | (1 << 27);else*GPIO2_SWPORTA_DR = (*GPIO2_SWPORTA_DR) & (~(1 << 27));}return 0;
}static int board_demo_exit(void) {iounmap(PMUCRU_CLKGATE_CON1);iounmap(PMUGRF_GPIO0B_IOMUX);iounmap(GPIO_SWPORTA_DR);iounmap(GPIO_SWPORTA_DDR);iounmap(PMUCRU_CLKGATE_CON31);iounmap(PMUGRF_GPIO2D_IOMUX);iounmap(GPIO2_SWPORTA_DR);iounmap(GPIO2_SWPORTA_DDR);printk("led io unmap end\\r\n");return 0;
}static struct led_operations board_demo_led_opr = {.num = 2,.init = board_demo_init,.ctl = board_demo_ctl,.exit = board_demo_exit,
};struct led_operations *get_board_led_opr(void) {return &board_demo_led_opr;
}

修改board_firefly.c的头文件

#ifndef _LED_OPR_H
#define _LED_OPR_H struct led_operations {int num;int (*init) (void);int (*ctl) (int which, char status);int (*exit) (void);
};struct led_operations *get_board_led_opr(void);#endif

修改led_firefly.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/device.h>#include "led_operation.h"#define LED_NAME "diy_led"
// #define LED_NUM 2static int major = 0;
static struct class *led_class;
struct led_operations *p_ledopr;static int led_open(struct inode *inode, struct file *file) {printk("%s  %s  %d led device open\r\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}static ssize_t led_read(struct file *file, char __user *buf, size_t cnt, loff_t *offt) {printk("%s  %s  %d led device read\r\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}static ssize_t led_write(struct file *file, const char __user *buf, size_t cnt, loff_t *offt) {int err;char status;struct inode *inode = file_inode(file);int minor = iminor(inode);printk("%s  %s  %d led device write\r\n", __FILE__, __FUNCTION__, __LINE__);err = copy_from_user(&status, buf, 1);/*Control LED by status and sub-device number.*/p_ledopr->ctl(minor, status);return 1;
}static int led_release(struct inode *inode, struct file *file) { printk("%s  %s  %d led device release\r\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}const struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.release = led_release,.read = led_read,.write = led_write,
};static int __init led_init(void)
{int i = 0;printk("led init.\r\n");major = register_chrdev(0, LED_NAME, &led_fops);led_class = class_create(THIS_MODULE, "LED_CLASS");p_ledopr = get_board_led_opr();for(i=0; i<p_ledopr->num; i++) {device_create(led_class, NULL, MKDEV(major, i), NULL, "diy_led_%d", i);}/*Init LED device by sub-device number */p_ledopr->init();return 0;
}static void __exit led_exit(void) 
{int i = 0;printk("led exit!!!\r\n");for(i=0; i<p_ledopr->num; i++) {device_destroy(led_class, MKDEV(major, i));}class_destroy(led_class); unregister_chrdev(major, LED_NAME);p_ledopr->exit();
}module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("liu");
MODULE_INFO(intree, "Y");

具体修改位置,请使用代码比较工具(如diffuse)查看。

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

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

相关文章

git 报错 SSL certificate problem: certificate has expired

git小乌龟 报错 SSL certificate problem: certificate has expired 场景复现&#xff1a; 原因&#xff1a; 这个错误表明你在使用Git时尝试通过HTTPS进行通信&#xff0c;但是SSL证书已经过期。这通常发生在使用自签名证书或证书有效期已到期的情况下。 解决方法: 1.如果是…

WTN6 E 系列语音芯片 单线时序及示例代码

1. 概述: WTN6 系列为多功能&#xff0c;低功耗&#xff0c;高性能的 CMOS 语音芯片。现有 WTN6020E、WTN6040E、 WTN6080E、WTN6170E 四种芯片&#xff08;语音长度分别为 20s、40s、80s、170s&#xff09;&#xff0c;已投入市场。 音频采样率目前最高可达 32kHz&#xff0…

streamlit 实现 flink SQL运行界面

实现效果 streamlit flink-playground.py 文件如下&#xff1a; import streamlit as st import io import contextlib import sys import os import uuid import subprocess from jinja2 import Templatest.set_page_config(layout"wide")# 设置页面标题 st.title…

SL3160 dcdc150V降压5.1V/1A 车载GPS定位器供电芯片

一、主要特性 宽输入电压范围&#xff1a;SL3160支持10~150V的宽输入电压范围&#xff0c;使其能够适应各种电源电压波动&#xff0c;确保稳定输出。 高效降压转换&#xff1a;该芯片采用先进的电源管理技术&#xff0c;转换效率高达90%以上&#xff0c;降低了散热压力和整体…

点云标注工具开发记录(五)之点云文件加载、视角转换

在Open3D中&#xff0c;通过read方法&#xff0c;我们可以读取不同格式的点云数据&#xff0c;那么&#xff0c;在不使用Open3D的相关接口时&#xff0c;我们就需要自己重写文件读入、加载、渲染展示方法&#xff0c;效果如下&#xff1a; 点云文件读入 首先&#xff0c;我们要…

vue开发的一个小插件vue.js devtools

可打开谷歌商城的情况下&#xff0c;不可打开的可以到极简插件里面去下载 极简插件官网_Chrome插件下载_Chrome浏览器应用商店 搜索vue即可

Flutter仿京东商城APP实战 用户中心基础布局

用户中心界面 pages/tabs/user/user.dart import package:flutter/material.dart; import package:jdshop/utils/zdp_screen.dart; import package:provider/provider.dart;import ../../../store/counter_store.dart;class UserPage extends StatefulWidget {const UserPage…

Maven入门到实践:从安装到项目构建与IDEA集成

目录 1. Maven的概念 1.1 什么是Maven 1.2 什么是依赖管理 1.3 什么是项目构建 1.4 Maven的应用场景 1.5 为什么使用Maven 1.6 Maven模型 2.初识Maven 2.1 Maven安装 2.1.1 安装准备 2.1.2 Maven安装目录分析 2.1.3 Maven的环境变量 2.2 Maven的第一个项目 2.2.1…

古埃及象形文字在线字典

我在个人网站“小孔的埃及学站点”上推出了在线的象形文字字典&#xff0c;总共收罗了将近700条的象形文字&#xff08;词&#xff09;。在线字典的使用方法很简单&#xff0c;在网站各大版块首页的右上方会有如下图所示的查询入口。 点击文本框&#xff0c;输入中文或英文关键…

公交IC卡收单管理系统 assets 信息泄露

0x01 产品描述&#xff1a; 公交IC卡系统是公交一卡通系统核心建设部分&#xff0c;是高时尚、高科技的管理系统&#xff0c;大大提升了公交行业的服务&#xff0c;能让公交企业信息化和电子化打下一个良好的硬件基础和软件基0x02 漏洞描述&#xff1a; 公交IC卡系统在/assets/…

FRIDA-JSAPI:Instruction使用

官方API文档介绍 Instruction.parse(target) 解析内存中 target 地址处的指令。 返回的对象具有的字段&#xff1a; address: 此指令的地址&#xff08;EIP&#xff09;&#xff0c;类型为 NativePointernext: 指向下一条指令的指针&#xff0c;您可以使用 parse() 解析它size…

详解如何使用WGCLOUD监测日志文件

WGCLOUD可以监控日志文件&#xff0c;包括.log、.txt、.out等类型的文件 WGCLOUD既可以监测文件夹下按天生成的日志文件&#xff0c;也可以监控指定的日志文件&#xff0c;非常灵活 我们只需要设置好日志中出现什么关键字符&#xff0c;那么WGCLOUD就可以自动进行这些监控工作…

【react 和 vue】 ---- 实现组件的递归渲染

1. 需求场景 今天遇到了一个需求&#xff0c;就是 HTML 的递归渲染。问题就是商品的可用时间&#xff0c;使用规则等数据是后端配置&#xff0c;然后配置规则则是可以无限递归的往下配置&#xff0c;可以存在很多级。后端实现后&#xff0c;数据返回前端&#xff0c;就需要前端…

一招教你解决Facebook广告账号问题

这段时间&#xff0c;我们写了很多文章来探讨Facebook的广告账户问题&#xff1a;《Facebook被封号该怎么办》《Facebook二不限、三不限账号是什么》《Facebook海外户&#xff08;三不限&#xff09;和账单户该如何选择》《如何区分真假Facebook三不限海外户》相信看过这些文章…

【传知代码】智能推荐与隐私保护的融合(论文复现)

本文将深入探讨这样一种系统的设计理念、关键技术以及其在实际应用中的潜力和优势。通过探索如何在保证个性化推荐效果的同时&#xff0c;有效保护用户隐私&#xff0c;我们将揭示出一种新兴的技术趋势&#xff0c;为未来智能化应用的发展开辟新的可能性。 目录 概述 项目设计…

基于SSM+小程序的就业管理系统(就业1)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 学生实习与就业管理系统的设计与实现管理员、辅导员管理、企业管理、工作管理人、用户管理5个角色。 1、管理员实现了基础数据管理、辅导员管理、企业管理、工作管理人管理、公告信息管理…

js 填充数组

let arr Array.from({ length: 10 }, (_, index) > index)console.log(arr) 人工智能学习网站 https://chat.xutongbao.top

如何用示波器检测次级点火系统(一)

写在最前面&#xff1a; 单看标题可能会让你觉得这篇文章的主题是关于检测线圈&#xff0c;火花塞和火花塞插头电线。但我们指的是分析燃烧室内电子的行为。目标是看燃料混合物&#xff0c;阀座&#xff0c;压缩&#xff0c;积碳和其它影响这种特性的症状。最终目的是要学会分…

FIR数字滤波器在MATLAB中的实现

摘要 数字滤波器是由数字乘法器、加法器和延时单元组成的一种装置。数字滤波器的功能是对输入离散信号的数字代码进行运算处理&#xff0c;以达到改变信号频谱的目的。近年来数字滤波在通信、图像编码、语言编码、雷达等许多领域中有着十分广泛的应用。 本文首先介绍了数字滤波…

为什么诺贝尔物理学奖颁给了 AI 大神

瑞典皇家科学院刚宣布&#xff0c;科学家约翰霍普菲尔德&#xff08;John J. Hopfield) 和杰弗里辛顿 (Geoffrey E. Hinton) 荣膺 2024年诺贝尔物理学奖&#xff0c;以表彰他们通过人工神经网络 (ANN) 实现机器学习而作出的基础性发现和发明 (for foundational discoveries and…