前言
笔者作为一名入职快一年的嵌入式软件工程师,对于嵌入式的很多知识还不是很熟悉,希望通过不断学习提升自己的能力,决定通过文章记录自己的学习成长过程。最近接触到的项目用到了瑞芯微的RK3566,主要基于该主控提供的SDK在linux系统上开发红外夜视功能,实现日夜场景的自动切换。其中,GPIO涉及红外补光灯的开关和红外滤光片的开关,本文主要介绍这两部分GPIO的控制。
RK3566 概述
RK3566 采用四核 ARM Cortex-A55 CPU,主频高达 1.8GHz,具备较强的计算能力。同时,芯片集成了 ARM Mali-G52 GPU,支持 OpenGL ES 1.1/2.0/3.2、OpenCL 2.0 和 Vulkan 1.1,能够处理复杂的图形渲染任务。芯片支持多种视频编解码格式,包括 H.264、H.265、VP9 等,最高支持 4K@60fps 的视频解码和 1080p@60fps 的视频编码。
此外,RK3566 还集成了 NPU(神经网络处理单元),支持 AI 计算,适用于人脸识别、图像处理等 AI 应用。RK3566 提供了丰富的接口,包括 USB 3.0、PCIe 2.1、SATA 3.0、千兆以太网等,支持多种外设连接。芯片还支持 DDR4/LPDDR4/LPDDR4X 内存,最大支持 8GB,能够满足高性能应用的需求。
GPIO 概述
GPIO(General Purpose Input/Output)是一种通用的输入输出接口,广泛应用于嵌入式系统和微控制器中。它允许开发者通过软件配置来控制硬件设备的输入和输出状态。GPIO 引脚可以设置为输入模式,用于读取外部信号;也可以设置为输出模式,用于驱动外部设备。
GPIO 控制
本次项目所使用到的GPIO有三根,包括IR CUT A-B脚和IR_LED_EN。硬件原理图如下所示,主要的控制逻辑为开启夜视,A低 B高 100ms,关闭夜视,A高 B低 100ms,开启或关闭之后,A 、B脚均需要保持拉低状态,IR_LED_EN拉低,开启红外补光灯,拉高,关闭红外补光灯。
图1.ircut引脚
图2.irled引脚
GPIO 命名
Rockchip Pin的ID按照 控制器(bank)+端口(port)+索引序号(pin) 组成。
-
控制器和GPIO控制器数量⼀致
-
端口固定 A、B、C和D,每个端口仅有8个索引号,(a=0,b=1,c=2,d=3)
-
索引序号固定 0、1、2、3、4、5、6、7
RK3562/RK3566/RK3568具有5个GPIO控制器,每个控制器可以控制32个IO,作为GPIO功能时, 端口⾏为由GPIO控制器寄存器配置。
GPIO1_A4表达的意思为第1组控制器,端口号为A,索引号为4。该引脚号的计算公式为32 x 1 + 0 x 8 + 4 = 36,更多的计算例子如图3所示。本次项目所用到的三个GPIO经过计算分别为RK_IRC_BIN = 122、RK_IRC_AIN = 123和RK_IR_ENABLE = 145。
图3.计算举例
通过sysfs 接口控制GPIO
在Linux中,最常见的读写GPIO方式就是用GPIO sysfs interface, 是通过操作 /sys/class/gpio 目录下的 export 、 unexport 、gpio{N}/direction, gpio{N} /value (用实际引脚号替代{N})等文件实现的,经常出现shell脚本里面。本次的项目采用sysfs接口来实现对GPIO的控制,在开始编码之前可以提前使用脚本检测硬件和GPIO接口是否正常,下图是笔者控制台编写的测试用例,终端设备通过串口接入控制台实现串口控制,通过不断切换ircut引脚的高低电平实现红外滤光片的开关控制。
图4.测试用例
使用libgpiod控制IO
在Linux中,还要另外一种控制GPIO的方法,就是是用libgpiod实现对GPIO的控制,这需要提前在终端设备上通过以下命令安装相应的库后才能使用,本次项目笔者未采用此种方法,这里做个简单介绍。
sudo apt install gpiod
此外,gpiod工具的使用方法与sysfs接口的不同,gpiod是以控制器为单位,然后再详细到端口号和索引号,即gpiod使用两个数据确定引脚。具体的计算例子与常用的命令如下图所示,有兴趣的读者可以自行验证该方法。
图5.计算例子
图6.常用命令
代码实现
本次项目主要是通过sysfs接口实现GPIO控制,其中最为重要的三个函数就是设置GPIO的输入输出方向direction,GPIO出口export和GPIO引脚的值value,以及通过GPIO实现对IRCUT的控制,以下是实现GPIO控制的主要源文件和头文件。
ircut_control.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <inttypes.h>
#include <unistd.h>
#include <errno.h>
#include <log/log.h>
#include <sys/ioctl.h>
#include "ircut_control.h"
#define SYSFS_GPIO_DIR "/sys/class/gpio"
#define SYSFS_GPIO_NAME "/gpio"
#define MAX_BUF 64
const int RK_IRC_BIN = 122;
const int RK_IRC_AIN = 123;
const int RK_IR_ENABLE = 145;
// set gpio export
int prt_gpio_export(unsigned int gpio)
{
int fd, len;
char buf[MAX_BUF];
fd = open(SYSFS_GPIO_DIR "/export", O_WRONLY);
if (fd < 0)
{
perror("gpio/export");
return fd;
}
len = snprintf(buf, sizeof(buf), "%d", gpio);
write(fd, buf, len);
close(fd);
return 0;
}
// set gpio direction
int prt_gpio_set_direction(unsigned int gpio, const char *direction)
{
int fd, len;
char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR SYSFS_GPIO_NAME "%d/direction", gpio);
fd = open(buf, O_WRONLY);
if (fd < 0)
{
perror("gpio/direction");
return fd;
}
write(fd, direction, strlen(direction)+1);
close(fd);
return 0;
}
// set gpio value
int prt_gpio_set_value(unsigned int gpio, unsigned int value)
{
int fd, len;
char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR SYSFS_GPIO_NAME "%d/value", gpio);
fd = open(buf, O_WRONLY);
if (fd < 0)
{
perror("gpio/set-value");
return fd;
}
if (value)
write(fd, "1", 2);
else
write(fd, "0", 2);
close(fd);
return 0;
}
// set gpio
int prt_gpio_set(int gpio_num,int value)
{
char buf_name[50] = {0};
if (value != 1 && value != 0)
{
perror("invalid_argument value.\n");
return -1;
}
snprintf(buf_name,sizeof(buf_name),"/sys/class/gpio/gpio%d",gpio_num);
if (access (buf_name,F_OK) != 0 )
{
// set gpio export
prt_gpio_export(gpio_num);
}
// set gpio direction
prt_gpio_set_direction(gpio_num,"out");
// set gpio value
prt_gpio_set_value(gpio_num,value);
return 0;
}
void delay_ms(unsigned int ms)
{
usleep(ms * 1000);
}
// control the ir_filter sheet
void ir_cutter_ctrl(bool on)
{
if (on) {
// open the ir_filter sheet RC_AIN low, IRC_BIN high
prt_gpio_set(RK_IRC_AIN, 0);
prt_gpio_set(RK_IRC_BIN, 1);
} else {
// close the ir_filter sheet RC_AIN high, IRC_BIN low
prt_gpio_set(RK_IRC_AIN, 1);
prt_gpio_set(RK_IRC_BIN, 0);
}
delay_ms(200);
prt_gpio_set(RK_IRC_AIN, 0);
prt_gpio_set(RK_IRC_BIN, 0);
}
ircut_control.h
#ifndef __IRCUT_CONTROL_H
#define __IRCUT_CONTROL_H
#ifdef __cplusplus
extern "C" {
#endif
extern const int RK_IRC_BIN;
extern const int RK_IRC_AIN;
extern const int RK_IR_ENABLE;
int prt_gpio_export(unsigned int gpio);
int prt_gpio_set_direction(unsigned int gpio, const char *direction);
int prt_gpio_set_value(unsigned int gpio, unsigned int value);
int prt_gpio_set(int gpio_num,int value);
void ir_cutter_ctrl(bool on);
#ifdef __cplusplus
}
#endif // __cplusplus
#endif //__IRCUT_CONTROL_H
总结
这是笔者发布的第一篇的博客,主要用于记录自己的学习和成长过程,对于其中理解不到位的地方还请各位大佬评论指正,大家共同学习,一起进步。文末附上笔者在该SDK下的功能实现位置,以便再次学习查看。
/external/camera_engine_rkaiq/hal_interface2.0/interface/ircut_control.c
/external/camera_engine_rkaiq/hal_interface2.0/interface/ircut_control.h