Linux GPIO 通过sysfs 方式进行操控, 进入到/sys/class/gpio 目录下。
export:用于将指定编号的GPIO 引脚导出。在使用GPIO 引脚之前,需要将其导出,导出成功之后才能使用它。
unexport:将导出的GPIO 引脚删除。当使用完GPIO 引脚之后,我们需要将导出的引脚删除。
将指定的编号写入到export 文件中,可以导出指定编号的GPIO 引脚,导出成功之后会在/sys/class/gpio目录下生成对应的gpioX(X 表示GPIO 的编号)。
direction:配置GPIO 引脚为输入或输出模式。该文件可读、可写,读表示查看GPIO 当前是输入还是输出模式,写表示将GPIO 配置为输入或输出模式;读取或写入操作可取的值为"out"(输出模式)和"in"(输入模式)。
value:在GPIO 配置为输出模式下,向value 文件写入"0"控制GPIO 引脚输出低电平,写入"1"则控制GPIO 引脚输出高电平。在输入模式下,读取value 文件获取GPIO 引脚当前的输入电平状态。
active_low:这个属性文件用于控制极性,可读可写。
edge:控制中断的触发模式,该文件可读可写。在配置GPIO 引脚的中断触发模式之前,需将其设置为输入模式:
非中断引脚:echo “none” > edge
上升沿触发:echo “rising” > edge
下降沿触发:echo “falling” > edge
边沿触发:echo “both” > edge
瑞芯微GPIO对应用户空间的gpio编号如下表。
| GPIO | GPIO编号 |
|---|---|
| GPIO0_B0 | 8 |
| GPIO2_C0 | 80 |
| GPIO4_C3 | 147 |
| GPIO0_C5 | 21 |
| GPIO4_C4 | 148 |
| GPIO4_C5 | 149 |
| GPIO2_B2 | 74 |
| GPIO2_B1 | 73 |
cd /sys/class/gpio/
# 将文件用于者改为youyeetoo
sudo chown youyeetoo:youyeetoo export
sudo chown youyeetoo:youyeetoo unexport
# 以GPIO0_B0为例操作gpio输出,其他gpio操作只需要根据上一个表格的对应关系导出即可。
# 导出 GPIO0_B0
echo 8 > export
# 将文件用于者改为youyeetoo
sudo chown youyeetoo:youyeetoo gpio8/direction
sudo chown youyeetoo:youyeetoo gpio8/value
cd gpio8
# 设置输出
echo out > direction
# 输出高电平
echo 1 > value
# 输出低电平
echo 0 > value
# 删除导出gpio引脚
cd ..
echo 8 > unexport
vim gpio_output.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
static char gpio_path[32];
static int gpio_config(const char *attr, const char *val)
{
char file_path[64];
int len;
int fd;
sprintf(file_path, "%s/%s", gpio_path, attr);
fd = open(file_path, O_WRONLY);
if (fd < 0) {
perror("open error");
return fd;
}
len = strlen(val);
if (len != write(fd, val, len)) {
perror("write error");
close(fd);
return -1;
}
close(fd); //关闭文件
return 0;
}
int main(int argc, char *argv[])
{
int fd;
int len;
/* 校验传参 */
if (argc != 3) {
fprintf(stderr, "usage: %s <gpio> <value>\n", argv[0]);
return -1;
}
/* 判断指定编号的GPIO是否导出 */
sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);
if (access(gpio_path, F_OK)) {//如果目录不存在 则需要导出
fd = open("/sys/class/gpio/export", O_WRONLY);
if (fd < 0) {
perror("open error");
return -1;
}
len = strlen(argv[1]);
if (len != write(fd, argv[1], len)) {//导出gpio
perror("write error");
close(fd);
return -1;
}
close(fd); //关闭文件
}
/* 配置为输出模式 */
if (gpio_config("direction", "out")) return -1;
/* 极性设置 */
if (gpio_config("active_low", "0")) return -1;
/* 控制GPIO输出高低电平 */
if (gpio_config("value", argv[2])) return -1;
close(fd);
/* 退出程序 */
return 0;
}
gcc gpio_output.c -o gpio_output
# 输出高电平
sudo ./gpio_output 8 1
# 输出低电平
sudo ./gpio_output 8 0
cd /sys/class/gpio/
# 将文件用于者改为youyeetoo
sudo chown youyeetoo:youyeetoo export
sudo chown youyeetoo:youyeetoo unexport
# 以GPIO0_B0为例操作gpio输出,其他gpio操作只需要根据上一个表格的对应关系导出即可。
# 导出 GPIO0_B0
echo 8 > export
# 将文件用于者改为youyeetoo
sudo chown youyeetoo:youyeetoo gpio8/direction
sudo chown youyeetoo:youyeetoo gpio8/value
cd gpio8
# 设置输出
echo in > direction
# 查看gpio电平
cat value
# 删除导出gpio引脚
cd ..
echo 8 > unexport
vim gpio_input.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
static char gpio_path[32];
static int gpio_config(const char *attr, const char *val)
{
char file_path[64];
int len;
int fd;
sprintf(file_path, "%s/%s", gpio_path, attr);
if (0 > (fd = open(file_path, O_WRONLY))) {
perror("open error");
return fd;
}
len = strlen(val);
if (len != write(fd, val, len)) {
perror("write error");
close(fd);
return -1;
}
close(fd); //关闭文件
return 0;
}
int main(int argc, char *argv[])
{
char file_path[64];
char val;
int fd;
int len;
/* 校验传参 */
if (argc != 2) {
fprintf(stderr, "usage: %s <gpio>\n", argv[0]);
return -1;
}
/* 判断指定编号的GPIO是否导出 */
sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);
if (access(gpio_path, F_OK)) {//如果目录不存在 则需要导出
if ((fd = open("/sys/class/gpio/export", O_WRONLY)) < 0) {
perror("open error");
return -1;
}
len = strlen(argv[1]);
if (len != write(fd, argv[1], len)) {//导出gpio
perror("write error");
close(fd);
return -1;
}
close(fd); //关闭文件
}
/* 配置为输入模式 */
if (gpio_config("direction", "in")) return -1;
/* 极性设置 */
if (gpio_config("active_low", "0")) return -1;
/* 配置为非中断方式 */
if (gpio_config("edge", "none")) return -1;
/* 读取GPIO电平状态 */
sprintf(file_path, "%s/%s", gpio_path, "value");
if ((fd = open(file_path, O_RDONLY)) < 0) {
perror("open error");
return -1;
}
if (read(fd, &val, 1) < 0) {
perror("read error");
close(fd);
return -1;
}
printf("value: %c\n", val);
/* 退出程序 */
close(fd);
return 0;
}
编译运行,读取gpio管脚电平
gcc gpio_input.c -o gpio_input
sudo ./gpio_input 8
vim gpio_irq.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>
static char gpio_path[32];
static int gpio_config(const char *attr, const char *val)
{
char file_path[64];
int len;
int fd;
sprintf(file_path, "%s/%s", gpio_path, attr);
fd = open(file_path, O_WRONLY);
if (fd < 0) {
perror("open error");
return fd;
}
len = strlen(val);
if (len != write(fd, val, len)) {
perror("write error");
return -1;
}
close(fd); //关闭文件
return 0;
}
int main(int argc, char *argv[])
{
struct pollfd pfd;
char file_path[64];
int ret;
char val;
int len;
int fd;
/* 校验传参 */
if (argc != 2) {
fprintf(stderr, "usage: %s <gpio>\n", argv[0]);
return -1;
}
/* 判断指定编号的GPIO是否导出 */
sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);
if (access(gpio_path, F_OK)) {//如果目录不存在 则需要导出
fd = open("/sys/class/gpio/export", O_WRONLY);
if (fd < 0) {
perror("open error");
return -1;
}
len = strlen(argv[1]);
if (len != write(fd, argv[1], len)) {//导出gpio
perror("write error");
return -1;
}
close(fd); //关闭文件
}
/* 配置为输入模式 */
if (gpio_config("direction", "in")) return -1;
/* 极性设置 */
if (gpio_config("active_low", "0")) return -1;
/* 配置中断触发方式: 上升沿和下降沿 */
if (gpio_config("edge", "both")) return -1;
/* 打开value属性文件 */
sprintf(file_path, "%s/%s", gpio_path, "value");
pfd.fd = open(file_path, O_RDONLY);
if (pfd.fd < 0) {
perror("open error");
return -1;
}
/* 调用poll */
pfd.events = POLLPRI; //只关心高优先级数据可读(中断)
read(pfd.fd, &val, 1);//先读取一次清除状态
while (1) {
ret = poll(&pfd, 1, -1); //调用poll
if (ret < 0) {
perror("poll error");
return -1;
} else if (ret == 0) {
fprintf(stderr, "poll timeout.\n");
continue;
} else {
/* 校验高优先级数据是否可读 */
if(pfd.revents & POLLPRI) {
if (lseek(pfd.fd, 0, SEEK_SET) < 0) {//将读位置移动到头部
perror("lseek error");
return -1;
}
if (read(pfd.fd, &val, 1) < 0) {
perror("read error");
return -1;
}
printf("GPIO Interrupt Trigger <value=%c>\n", val);
}
}
}
/* 退出程序 */
return 0;
}
编译运行
gcc gpio_irq.c -o gpio_irq
sudo ./gpio_irq
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
int set_opt(int,int,int,char,int);
void main(){
int fd,nByte;
char *uart3 = "/dev/ttyS2";
char bufferR[2];
char bufferW[2];
memset(bufferR, '\0', 2);
memset(bufferW, '\0', 2);
if((fd=open(uart3,O_RDWR,0777))<0)
{
printf("failed\n");
}
else{
printf("success\n");
set_opt(fd, 115200, 8, 'N', 1);
}
while(1){
nByte = 0;
memset(bufferR, '\0', 2);
memset(bufferW, '\0', 2);
// printf("hello\n");
if((nByte = read(fd, bufferR, 1)) == 1){ //MCU串口发送接收都是单字节(单字符)函数
printf("receive:%c\n",bufferR[0]);
bufferW[0] = 'A';
write(fd,bufferW,1); //串口发送单字节(单字符) buffer[0] = data
memset(bufferR, '\0', 2);
memset(bufferW, '\0', 2);
nByte = 0;
}
}
close(fd);
}
//串口通用初始化函数
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
struct termios newtio,oldtio;//定义结构体newtio和oldtio
//将原串口的数据取到oldtio
if ( tcgetattr( fd,&oldtio) != 0) {
perror("SetupSerial 1");
return -1;
}
//将newio清零和设置c_cflag
bzero( &newtio, sizeof( newtio ) );
newtio.c_cflag |= CLOCAL | CREAD;//使能接收和忽略控制线
newtio.c_cflag &= ~CSIZE;
//设置数据位
switch( nBits )
{
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
//设置校验位
switch( nEvent )
{
//偶校验
case 'O':
newtio.c_cflag |= PARENB;//使能奇偶校验
newtio.c_cflag |= PARODD;//偶校验
newtio.c_iflag |= (INPCK | ISTRIP);//输入校验并忽略第八位
break;
case 'E':
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;//取消偶校验(置零偶校验位),开启奇校验
break;
case 'N':
newtio.c_cflag &= ~PARENB;//不进行奇偶校验
break;
}
//设置波特率
switch( nSpeed )
{
case 2400:
cfsetispeed(&newtio, B2400);
cfsetospeed(&newtio, B2400);
break;
case 4800:
cfsetispeed(&newtio, B4800);
cfsetospeed(&newtio, B4800);
break;
case 9600:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
case 115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
case 460800:
cfsetispeed(&newtio, B460800);
cfsetospeed(&newtio, B460800);
break;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
//设置停止位
if( nStop == 1 )
newtio.c_cflag &= ~CSTOPB;//一位停止位
else if ( nStop == 2 )
newtio.c_cflag |= CSTOPB;//两位停止位
newtio.c_cc[VTIME] = 0;//不设置读取超时
newtio.c_cc[VMIN] = 0;//读取最小字符数为0
tcflush(fd,TCIFLUSH);//清空缓冲区
//使配置生效
if((tcsetattr(fd,TCSANOW,&newtio))!=0)
{
perror("com set error");
return -1;
}
// printf("set done!\n\r");
return 0;
}
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
/* eeprom所对应的I2C控制器的设备节点 */
#define EEPROM_DEVICE "/dev/i2c-0"
/* eeprom的I2C设备地址 */
#define EEPROM_ADDR 0x50
/* 函数名:eeprom_write
** 功能:向eeprom写数据
**参数:fd:eeprom对应I2C控制器设备节点的文件名
** dev_addr:eeprom的I2C从设备地址
** reg_addr:eeprom的寄存器地址
** data_buf:要向eeprom写数据的数据buf
** len:要写多少个字节。本例中当前最大支持为8个字节
**返回值:负数表示操作失败,其他为成功
*/
int eeprom_write(int fd, unsigned char dev_addr, unsigned char reg_addr, unsigned char * data_buf,int len)
{
int ret;
unsigned char msg_buf[9];
struct i2c_rdwr_ioctl_data data;
struct i2c_msg messages;
/* 1. 构建msg_buf*/
/* 1.1. 将要操作的寄存器首地址赋给要进行I2C数据通信的首字节数据 */
msg_buf[0] = reg_addr;
/* 1.2. 将要向eeprom写数据的数据buf赋在I2C数据通信中eeprom寄存器的后面 */
if (len < 9) { /* 本demo最大支持一次向eeprom写一页大小的8个字节数据 */
memcpy((void *) &msg_buf[1], data_buf, len); //第1位之后是数据
} else {
printf("This function supports up to 8 bytes at a time !!!\n");
return -1;
}
/* 2. 构建 struct i2c_msg messages */
/* 2.1. 赋值eeprom的I2C从设备地址 */
messages.addr = dev_addr;
/* 2.2. 赋值flags为本次I2C通信完成写功能 */
messages.flags = 0;
/* 2.3. 赋值len为数据buf的长度 + eeprom寄存器地址的数据长度 */
messages.len = len+1;
/* 2.4. 构建消息包的数据buf*/
messages.buf = msg_buf;
/* 3. 构建struct i2c_rdwr_ioctl_data data */
/* 3.1. 将准备好的消息包赋值给i2c_rdwr_ioctl_data中的msgs消息*/
data.msgs = &messages;
/* 3.2. 由于本次I2C通信只有写动作,所以消息数为1次 */
data.nmsgs = 1;
/* 4. 调用驱动层的读写组合的I2C数据传输 */
if(ioctl(fd, I2C_RDWR, &data) < 0)
{
printf("I2C_RDWR err \n");
return -1;
}
/* 5. 等待I2C总线写入完成 */
sleep(1);
return 0;
}
/*函数名:eeprom_read
**功能:从eeprom读数据
**参数:fd:eeprom对应I2C控制器设备节点的文件名
** dev_addr:eeprom的I2C从设备地址
** reg_addr:eeprom的寄存器地址
** data_buf:存放从eeprom读数据的buf
** len:要读多少个字节。
**返回值:负数表示操作失败,其他为成功
*/
int eeprom_read(int fd, unsigned char dev_addr, unsigned char reg_addr, unsigned char * data_buf,int len)
{
int ret;
unsigned char msg_buf[9];
struct i2c_rdwr_ioctl_data data;
struct i2c_msg messages[2];
/* 1. 构建 struct i2c_msg messages */
/* 1.1. 构建第一条消息 messages[0] */
/* 1.1.1. 赋值eeprom的I2C从设备地址 */
messages[0].addr = dev_addr;
/* 1.1.2. 赋值flags为本次I2C通信完成写动作 */
messages[0].flags = 0;
/* 1.1.3. 赋值len为eeprom寄存器地址的数据长度是1 */
messages[0].len = 1;
/* 1.1.4. 本次写动作的数据是要读取eeprom的寄存器首地址*/
messages[0].buf = ®_addr;
/* 1.2. 构建第二条消息 messages[1] */
/* 1.2.1. 赋值eeprom的I2C从设备地址 */
messages[1].addr = dev_addr;
/* 1.1.2. 赋值flags为本次I2C通信完成读动作 */
messages[1].flags = I2C_M_RD;
/* 1.1.3. 赋值len为要读取eeprom寄存器数据长度len */
messages[1].len = len;
/* 1.1.4. 本次读动作的数据要存放的buf位置*/
messages[1].buf = data_buf;
/* 2. 构建struct i2c_rdwr_ioctl_data data */
/* 2.1. 将准备好的消息包赋值给i2c_rdwr_ioctl_data中的msgs消息*/
data.msgs = messages;
/* 2.2. 由于本次I2C通信既有写动作也有读动作,所以消息数为2次 */
data.nmsgs = 2;
/* 3. 调用驱动层的读写组合的I2C数据传输 */
if(ioctl(fd, I2C_RDWR, &data) < 0)
{
printf("I2C_RDWR err \n");
return -1;
}
/* 4. 等待I2C总线读取完成 */
sleep(1);
return 0;
}
int main()
{
int fd,i,ret=0;
unsigned char w_add=0x10;
/* 将要读取的数据buf*/
unsigned char rd_buf[8] = {0};
/* 要写的数据buf*/
unsigned char wr_buf[8] = {0};
printf("hello,this is I2C_RDWR i2c test \n");
/* 打开eeprom对应的I2C控制器文件 */
fd =open(EEPROM_DEVICE, O_RDWR);
if (fd< 0)
{
printf("open"EEPROM_DEVICE"failed \n");
}
/* 把要写入的数据写入到后面的buf中 */
for(i=0;i<8;i++)
wr_buf[i]=i;
/* 通过I2C_RDWR完成向eeprom读数据的功能 */
eeprom_write(fd,EEPROM_ADDR,w_add,wr_buf,8);
/* 通过I2C_RDWR完成向eeprom写数据的功能 */
eeprom_read(fd,EEPROM_ADDR,w_add,rd_buf,8);
for(i=0;i<8;i++)
{
printf("rd_buf is :%d\n",rd_buf[i]);
}
/* 完成操作后,关闭eeprom对应的I2C控制器的设备文件 */
close(fd);
return 0;
}
CAN发送应用程序示例
/* 1. 报文发送程序 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int main()
{
int s, nbytes;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame[2] = {{0}};
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);//创建套接字
strcpy(ifr.ifr_name, "can0" );
ioctl(s, SIOCGIFINDEX, &ifr); //指定 can0 设备
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr));//将套接字与 can0 绑定
//禁用过滤规则,本进程不接收报文,只负责发送
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
//生成两个报文
frame[0].can_id = 0x11;
frame[0]. can_dlc = 1;
frame[0].data[0] = 'Y';
frame[1].can_id = 0x22;
frame[1]. can_dlc = 1;
frame[1].data[0] = 'N';
//循环发送两个报文
while(1)
{
nbytes = write(s, &frame[0], sizeof(frame[0])); //发送 frame[0]
if(nbytes != sizeof(frame[0]))
{
printf("Send Error frame[0]\n!");
break; //发送错误,退出
}
sleep(1);
nbytes = write(s, &frame[1], sizeof(frame[1])); //发送 frame[1]
if(nbytes != sizeof(frame[0]))
{
printf("Send Error frame[1]\n!");
break;
}
sleep(1);
}
close(s);
return 0;
}
CAN接收程序示例
/* 2. 报文过滤接收程序 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int main()
{
int s, nbytes;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame;
struct can_filter rfilter[1];
s = socket(PF_CAN, SOCK_RAW, CAN_RAW); //创建套接字
strcpy(ifr.ifr_name, "can0" );
ioctl(s, SIOCGIFINDEX, &ifr); //指定 can0 设备
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr)); //将套接字与 can0 绑定
//定义接收规则,只接收表示符等于 0x11 的报文
rfilter[0].can_id = 0x11;
rfilter[0].can_mask = CAN_SFF_MASK;
//设置过滤规则
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
while(1)
{
nbytes = read(s, &frame, sizeof(frame)); //接收报文
//显示报文
if(nbytes > 0)
{
printf(“ID=0x%X DLC=%d data[0]=0x%X\n”, frame.can_id,
frame.can_dlc, frame.data[0]);
}
}
close(s);
return 0;
}
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习软件库。由一系列 C函数和少量 C++ 类构成,同时提供了 Python、Java、MATLAB 等语言的接口。
pkg-config --modversion opencv4
本系统默认内置版本为4.13.0。
mkdir OpenCV_Demo
cd OpenCV_Demo
mkdir build image src
cmake_minimum_required(VERSION 3.8)
project( OpenCV_Demo )
find_package( OpenCV REQUIRED )
include_directories( ${OpenCV_INCLUDE_DIRS} )
add_executable( image_demo src/image_demo.cpp )
add_executable( video_demo src/video_demo.cpp )
add_executable( camera_demo src/camera_demo.cpp )
target_link_libraries( image_demo ${OpenCV_LIBS} )
target_link_libraries( video_demo ${OpenCV_LIBS} )
target_link_libraries( camera_demo ${OpenCV_LIBS} )
#include <opencv2/opencv.hpp>
#include <iostream>
int main(int argc, char** argv) {
// 读取图片
cv::Mat image = cv::imread("../image/image.png");
// 确认图片读取成功
if(image.empty()) {
std::cerr << "Failed to open image file." << std::endl;
return -1;
}
//控制照片比例
resize(image, image, cv::Size(1280, 720));
// 显示图片
cv::imshow("Image with Box", image);
// 等待按键
cv::waitKey(0);
return 0;
}
#include <opencv2/opencv.hpp>
using namespace cv;
int main() {
// 打开视频文件
VideoCapture cap("../image/video.mp4");
// 检查视频是否成功打开
if (!cap.isOpened()) {
std::cout << "Error opening video stream or file" << std::endl;
return -1;
}
// 循环读取视频帧
while (true) {
Mat frame;
// 读取当前帧
cap >> frame;
// 检查是否成功读取帧
if (frame.empty())
break;
// 显示当前帧
imshow("Frame", frame);
// 按下 Esc 键退出循环
if (waitKey(25) == 27)
break;
}
// 释放VideoCapture对象和所有窗口
cap.release();
destroyAllWindows();
return 0;
}
#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
using namespace std;
using namespace cv;
int main() {
// 打开默认摄像头
VideoCapture cap(41);
if (!cap.isOpened()) {
cout << "无法打开摄像头!" << endl;
return -1;
}
namedWindow("摄像头", WINDOW_NORMAL);
while (true) {
Mat frame;
cap >> frame;
// 显示视频帧
imshow("摄像头", frame);
// 按下空格键拍照
if (waitKey(30) == ' ')
{
// 生成文件名
time_t now = time(NULL);
tm *ltm = localtime(&now);
string filename = to_string(ltm->tm_year + 1900) + "-" + to_string(ltm->tm_mon + 1) + "-" + to_string(ltm->tm_mday) + "-" + to_string(ltm->tm_hour) + "-" + to_string(ltm->tm_min) + "-" + to_string(ltm->tm_sec) + ".jpg";
// 保存图片
imwrite(filename, frame);
cout << "已保存照片:" << filename << endl;
}
}
return 0;
}
cd build
cmake ..
make
./image_demo
./video_demo
./camera_demo

pkg-config --modversion rockchip_mpp
pkg-config --modversion rockchip_vpu
git clone https://github.com/rockchip-linux/mpp.git
在下载的mpp目录里面 test目录,里面是一些mpp官方调用例程,用户可参考test目录和doc文档目录。
这里参考 test/mpi_dec_test.c 例程编写
vim mpp_dec_test.c
在官方例程中的头文件和在gcc编译时指定连接库。
源码如下
#define MODULE_TAG "mpp_dec_test"
#include <string.h>
#include <rockchip/rk_mpi.h>
#include <rockchip/mpp_mem.h>
#include <rockchip/mpp_env.h>
#include <rockchip/mpp_time.h>
#include <rockchip/mpp_common.h>
#include <rockchip/mpi_dec_utils.h>
typedef struct {
MpiDecTestCmd *cmd;
MppCtx ctx;
MppApi *mpi;
RK_U32 quiet;
/* end of stream flag when set quit the loop */
RK_U32 loop_end;
/* input and output */
DecBufMgr buf_mgr;
MppBufferGroup frm_grp;
MppPacket packet;
MppFrame frame;
FILE *fp_output;
RK_S32 frame_count;
RK_S32 frame_num;
RK_S64 first_pkt;
RK_S64 first_frm;
size_t max_usage;
float frame_rate;
RK_S64 elapsed_time;
RK_S64 delay;
FILE *fp_verify;
FrmCrc checkcrc;
} MpiDecLoopData;
static int dec_simple(MpiDecLoopData *data)
{
RK_U32 pkt_done = 0;
RK_U32 pkt_eos = 0;
MPP_RET ret = MPP_OK;
MpiDecTestCmd *cmd = data->cmd;
MppCtx ctx = data->ctx;
MppApi *mpi = data->mpi;
MppPacket packet = data->packet;
FileBufSlot *slot = NULL;
RK_U32 quiet = data->quiet;
FrmCrc *checkcrc = &data->checkcrc;
// when packet size is valid read the input binary file
ret = reader_read(cmd->reader, &slot);
mpp_assert(ret == MPP_OK);
mpp_assert(slot);
pkt_eos = slot->eos;
if (pkt_eos) {
if (data->frame_num < 0 || data->frame_num > data->frame_count) {
mpp_log_q(quiet, "%p loop again\n", ctx);
reader_rewind(cmd->reader);
pkt_eos = 0;
} else {
mpp_log_q(quiet, "%p found last packet\n", ctx);
data->loop_end = 1;
}
}
mpp_packet_set_data(packet, slot->data);
mpp_packet_set_size(packet, slot->size);
mpp_packet_set_pos(packet, slot->data);
mpp_packet_set_length(packet, slot->size);
// setup eos flag
if (pkt_eos)
mpp_packet_set_eos(packet);
do {
RK_U32 frm_eos = 0;
RK_S32 times = 30;
// send the packet first if packet is not done
if (!pkt_done) {
ret = mpi->decode_put_packet(ctx, packet);
if (MPP_OK == ret) {
pkt_done = 1;
if (!data->first_pkt)
data->first_pkt = mpp_time();
}
}
// then get all available frame and release
do {
RK_S32 get_frm = 0;
MppFrame frame = NULL;
try_again:
ret = mpi->decode_get_frame(ctx, &frame);
if (MPP_ERR_TIMEOUT == ret) {
if (times > 0) {
times--;
msleep(1);
goto try_again;
}
mpp_err("%p decode_get_frame failed too much time\n", ctx);
}
if (ret) {
mpp_err("%p decode_get_frame failed ret %d\n", ret, ctx);
break;
}
if (frame) {
if (mpp_frame_get_info_change(frame)) {
RK_U32 width = mpp_frame_get_width(frame);
RK_U32 height = mpp_frame_get_height(frame);
RK_U32 hor_stride = mpp_frame_get_hor_stride(frame);
RK_U32 ver_stride = mpp_frame_get_ver_stride(frame);
RK_U32 buf_size = mpp_frame_get_buf_size(frame);
MppBufferGroup grp = NULL;
mpp_log_q(quiet, "%p decode_get_frame get info changed found\n", ctx);
mpp_log_q(quiet, "%p decoder require buffer w:h [%d:%d] stride [%d:%d] buf_size %d",
ctx, width, height, hor_stride, ver_stride, buf_size);
if (MPP_FRAME_FMT_IS_FBC(cmd->format)) {
MppFrame frm = NULL;
mpp_frame_init(&frm);
mpp_frame_set_width(frm, width);
mpp_frame_set_height(frm, height);
mpp_frame_set_fmt(frm, cmd->format);
ret = mpi->control(ctx, MPP_DEC_SET_FRAME_INFO, frm);
mpp_frame_deinit(&frm);
if (ret) {
mpp_err("set fbc frame info failed\n");
break;
}
}
grp = dec_buf_mgr_setup(data->buf_mgr, buf_size, 24, cmd->buf_mode);
/* Set buffer to mpp decoder */
ret = mpi->control(ctx, MPP_DEC_SET_EXT_BUF_GROUP, grp);
if (ret) {
mpp_err("%p set buffer group failed ret %d\n", ctx, ret);
break;
}
data->frm_grp = grp;
/*
* All buffer group config done. Set info change ready to let
* decoder continue decoding
*/
ret = mpi->control(ctx, MPP_DEC_SET_INFO_CHANGE_READY, NULL);
if (ret) {
mpp_err("%p info change ready failed ret %d\n", ctx, ret);
break;
}
} else {
char log_buf[256];
RK_S32 log_size = sizeof(log_buf) - 1;
RK_S32 log_len = 0;
RK_U32 err_info = mpp_frame_get_errinfo(frame);
RK_U32 discard = mpp_frame_get_discard(frame);
if (!data->first_frm)
data->first_frm = mpp_time();
log_len += snprintf(log_buf + log_len, log_size - log_len,
"decode get frame %d", data->frame_count);
if (mpp_frame_has_meta(frame)) {
MppMeta meta = mpp_frame_get_meta(frame);
RK_S32 temporal_id = 0;
mpp_meta_get_s32(meta, KEY_TEMPORAL_ID, &temporal_id);
log_len += snprintf(log_buf + log_len, log_size - log_len,
" tid %d", temporal_id);
}
if (err_info || discard) {
log_len += snprintf(log_buf + log_len, log_size - log_len,
" err %x discard %x", err_info, discard);
}
mpp_log_q(quiet, "%p %s\n", ctx, log_buf);
data->frame_count++;
if (data->fp_output && !err_info)
dump_mpp_frame_to_file(frame, data->fp_output);
if (data->fp_verify) {
calc_frm_crc(frame, checkcrc);
write_frm_crc(data->fp_verify, checkcrc);
}
fps_calc_inc(cmd->fps);
}
frm_eos = mpp_frame_get_eos(frame);
mpp_frame_deinit(&frame);
get_frm = 1;
}
// try get runtime frame memory usage
if (data->frm_grp) {
size_t usage = mpp_buffer_group_usage(data->frm_grp);
if (usage > data->max_usage)
data->max_usage = usage;
}
// if last packet is send but last frame is not found continue
if (pkt_eos && pkt_done && !frm_eos) {
msleep(1);
continue;
}
if (frm_eos) {
mpp_log_q(quiet, "%p found last packet\n", ctx);
break;
}
if ((data->frame_num > 0 && (data->frame_count >= data->frame_num)) ||
((data->frame_num == 0) && frm_eos))
break;
if (get_frm)
continue;
break;
} while (1);
if ((data->frame_num > 0 && (data->frame_count >= data->frame_num)) ||
((data->frame_num == 0) && frm_eos)) {
data->loop_end = 1;
break;
}
if (pkt_done)
break;
/*
* why sleep here:
* mpi->decode_put_packet will failed when packet in internal queue is
* full,waiting the package is consumed .Usually hardware decode one
* frame which resolution is 1080p needs 2 ms,so here we sleep 1ms
* * is enough.
*/
msleep(1);
} while (1);
return ret;
}
static int dec_advanced(MpiDecLoopData *data)
{
MPP_RET ret = MPP_OK;
MpiDecTestCmd *cmd = data->cmd;
MppCtx ctx = data->ctx;
MppApi *mpi = data->mpi;
MppPacket packet = NULL;
MppPacket packet_ret = NULL;
MppFrame frame = data->frame;
MppFrame frame_ret = NULL;
MppMeta meta = NULL;
RK_U32 quiet = data->quiet;
FileBufSlot *slot = NULL;
FrmCrc *checkcrc = &data->checkcrc;
ret = reader_index_read(cmd->reader, 0, &slot);
mpp_assert(ret == MPP_OK);
mpp_assert(slot);
mpp_packet_init_with_buffer(&packet, slot->buf);
// setup eos flag
if (slot->eos)
mpp_packet_set_eos(packet);
/* use the MppFrame with prealloced buffer and do not release */
meta = mpp_packet_get_meta(packet);
if (meta)
mpp_meta_set_frame(meta, KEY_OUTPUT_FRAME, frame);
ret = mpi->decode_put_packet(ctx, packet);
if (ret) {
mpp_err("%p mpp decode put packet failed ret %d\n", ctx, ret);
data->loop_end = 1;
goto DONE;
}
if (!data->first_pkt)
data->first_pkt = mpp_time();
ret = mpi->decode_get_frame(ctx, &frame_ret);
if (ret || !frame_ret) {
mpp_err("%p mpp decode get frame failed ret %d frame %p\n", ctx, ret, frame_ret);
data->loop_end = 1;
goto DONE;
}
if (!data->first_frm)
data->first_frm = mpp_time();
if (frame_ret != frame)
mpp_err_f("mismatch frame %p -> %p\n", frame_ret, frame);
/* write frame to file here */
if (data->fp_output)
dump_mpp_frame_to_file(frame_ret, data->fp_output);
if (data->fp_verify) {
calc_frm_crc(frame_ret, checkcrc);
write_frm_crc(data->fp_verify, checkcrc);
}
mpp_log_q(quiet, "%p decoded frame %d\n", ctx, data->frame_count);
data->frame_count++;
if (mpp_frame_get_eos(frame_ret))
mpp_log_q(quiet, "%p found eos frame\n", ctx);
fps_calc_inc(cmd->fps);
meta = mpp_frame_get_meta(frame);
if (meta) {
ret = mpp_meta_get_packet(meta, KEY_INPUT_PACKET, &packet_ret);
if (ret || !packet_ret) {
mpp_err("%p mpp meta get packet failed ret %d\n", ctx, ret);
goto DONE;
}
if (packet_ret != packet)
mpp_err_f("mismatch packet %p -> %p\n", packet, packet_ret);
}
if (data->frame_num > 0) {
if (data->frame_count >= data->frame_num)
data->loop_end = 1;
} else if (data->frame_num == 0) {
if (slot->eos)
data->loop_end = 1;
}
DONE:
mpp_packet_deinit(&packet);
return ret;
}
void *thread_decode(void *arg)
{
MpiDecLoopData *data = (MpiDecLoopData *)arg;
MpiDecTestCmd *cmd = data->cmd;
MppCtx ctx = data->ctx;
MppApi *mpi = data->mpi;
RK_S64 t_s, t_e;
memset(&data->checkcrc, 0, sizeof(data->checkcrc));
data->checkcrc.luma.sum = mpp_malloc(RK_ULONG, 512);
data->checkcrc.chroma.sum = mpp_malloc(RK_ULONG, 512);
t_s = mpp_time();
if (cmd->simple) {
while (!data->loop_end)
dec_simple(data);
} else {
/* NOTE: change output format before jpeg decoding */
if (MPP_FRAME_FMT_IS_YUV(cmd->format) || MPP_FRAME_FMT_IS_RGB(cmd->format)) {
MPP_RET ret = mpi->control(ctx, MPP_DEC_SET_OUTPUT_FORMAT, &cmd->format);
if (ret) {
mpp_err("Failed to set output format 0x%x\n", cmd->format);
return NULL;
}
}
while (!data->loop_end)
dec_advanced(data);
}
t_e = mpp_time();
data->elapsed_time = t_e - t_s;
data->frame_rate = (float)data->frame_count * 1000000 / data->elapsed_time;
data->delay = data->first_frm - data->first_pkt;
mpp_log("decode %d frames time %lld ms delay %3d ms fps %3.2f\n",
data->frame_count, (RK_S64)(data->elapsed_time / 1000),
(RK_S32)(data->delay / 1000), data->frame_rate);
MPP_FREE(data->checkcrc.luma.sum);
MPP_FREE(data->checkcrc.chroma.sum);
return NULL;
}
int dec_decode(MpiDecTestCmd *cmd)
{
// base flow context
MppCtx ctx = NULL;
MppApi *mpi = NULL;
// input / output
MppPacket packet = NULL;
MppFrame frame = NULL;
// paramter for resource malloc
RK_U32 width = cmd->width;
RK_U32 height = cmd->height;
MppCodingType type = cmd->type;
// config for runtime mode
MppDecCfg cfg = NULL;
RK_U32 need_split = 1;
// resources
MppBuffer frm_buf = NULL;
pthread_t thd;
pthread_attr_t attr;
MpiDecLoopData data;
MPP_RET ret = MPP_OK;
mpp_log("mpi_dec_test start\n");
memset(&data, 0, sizeof(data));
pthread_attr_init(&attr);
cmd->simple = (cmd->type != MPP_VIDEO_CodingMJPEG) ? (1) : (0);
if (cmd->have_output) {
data.fp_output = fopen(cmd->file_output, "w+b");
if (NULL == data.fp_output) {
mpp_err("failed to open output file %s\n", cmd->file_output);
goto MPP_TEST_OUT;
}
}
if (cmd->file_slt) {
data.fp_verify = fopen(cmd->file_slt, "wt");
if (!data.fp_verify)
mpp_err("failed to open verify file %s\n", cmd->file_slt);
}
ret = dec_buf_mgr_init(&data.buf_mgr);
if (ret) {
mpp_err("dec_buf_mgr_init failed\n");
goto MPP_TEST_OUT;
}
if (cmd->simple) {
ret = mpp_packet_init(&packet, NULL, 0);
if (ret) {
mpp_err("mpp_packet_init failed\n");
goto MPP_TEST_OUT;
}
} else {
RK_U32 hor_stride = MPP_ALIGN(width, 16);
RK_U32 ver_stride = MPP_ALIGN(height, 16);
ret = mpp_frame_init(&frame); /* output frame */
if (ret) {
mpp_err("mpp_frame_init failed\n");
goto MPP_TEST_OUT;
}
data.frm_grp = dec_buf_mgr_setup(data.buf_mgr, hor_stride * ver_stride * 4, 4, cmd->buf_mode);
if (!data.frm_grp) {
mpp_err("failed to get buffer group for input frame ret %d\n", ret);
ret = MPP_NOK;
goto MPP_TEST_OUT;
}
/*
* NOTE: For jpeg could have YUV420 and YUV422 the buffer should be
* larger for output. And the buffer dimension should align to 16.
* YUV420 buffer is 3/2 times of w*h.
* YUV422 buffer is 2 times of w*h.
* So create larger buffer with 2 times w*h.
*/
ret = mpp_buffer_get(data.frm_grp, &frm_buf, hor_stride * ver_stride * 4);
if (ret) {
mpp_err("failed to get buffer for input frame ret %d\n", ret);
goto MPP_TEST_OUT;
}
mpp_frame_set_buffer(frame, frm_buf);
}
// decoder demo
ret = mpp_create(&ctx, &mpi);
if (ret) {
mpp_err("mpp_create failed\n");
goto MPP_TEST_OUT;
}
mpp_log("%p mpi_dec_test decoder test start w %d h %d type %d\n",
ctx, width, height, type);
ret = mpp_init(ctx, MPP_CTX_DEC, type);
if (ret) {
mpp_err("%p mpp_init failed\n", ctx);
goto MPP_TEST_OUT;
}
mpp_dec_cfg_init(&cfg);
/* get default config from decoder context */
ret = mpi->control(ctx, MPP_DEC_GET_CFG, cfg);
if (ret) {
mpp_err("%p failed to get decoder cfg ret %d\n", ctx, ret);
goto MPP_TEST_OUT;
}
/*
* split_parse is to enable mpp internal frame spliter when the input
* packet is not aplited into frames.
*/
ret = mpp_dec_cfg_set_u32(cfg, "base:split_parse", need_split);
if (ret) {
mpp_err("%p failed to set split_parse ret %d\n", ctx, ret);
goto MPP_TEST_OUT;
}
ret = mpi->control(ctx, MPP_DEC_SET_CFG, cfg);
if (ret) {
mpp_err("%p failed to set cfg %p ret %d\n", ctx, cfg, ret);
goto MPP_TEST_OUT;
}
data.cmd = cmd;
data.ctx = ctx;
data.mpi = mpi;
data.loop_end = 0;
data.packet = packet;
data.frame = frame;
data.frame_count = 0;
data.frame_num = cmd->frame_num;
data.quiet = cmd->quiet;
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
ret = pthread_create(&thd, &attr, thread_decode, &data);
if (ret) {
mpp_err("failed to create thread for input ret %d\n", ret);
goto MPP_TEST_OUT;
}
if (cmd->frame_num < 0) {
// wait for input then quit decoding
mpp_log("*******************************************\n");
mpp_log("**** Press Enter to stop loop decoding ****\n");
mpp_log("*******************************************\n");
getc(stdin);
data.loop_end = 1;
}
pthread_join(thd, NULL);
cmd->max_usage = data.max_usage;
ret = mpi->reset(ctx);
if (ret) {
mpp_err("%p mpi->reset failed\n", ctx);
goto MPP_TEST_OUT;
}
MPP_TEST_OUT:
if (data.packet) {
mpp_packet_deinit(&data.packet);
data.packet = NULL;
}
if (frame) {
mpp_frame_deinit(&frame);
frame = NULL;
}
if (ctx) {
mpp_destroy(ctx);
ctx = NULL;
}
if (!cmd->simple) {
if (frm_buf) {
mpp_buffer_put(frm_buf);
frm_buf = NULL;
}
}
data.frm_grp = NULL;
if (data.buf_mgr) {
dec_buf_mgr_deinit(data.buf_mgr);
data.buf_mgr = NULL;
}
if (data.fp_output) {
fclose(data.fp_output);
data.fp_output = NULL;
}
if (data.fp_verify) {
fclose(data.fp_verify);
data.fp_verify = NULL;
}
if (cfg) {
mpp_dec_cfg_deinit(cfg);
cfg = NULL;
}
pthread_attr_destroy(&attr);
return ret;
}
int main(int argc, char **argv)
{
RK_S32 ret = 0;
MpiDecTestCmd cmd_ctx;
MpiDecTestCmd* cmd = &cmd_ctx;
memset((void*)cmd, 0, sizeof(*cmd));
cmd->format = MPP_FMT_BUTT;
cmd->pkt_size = MPI_DEC_STREAM_SIZE;
// parse the cmd option
ret = mpi_dec_test_cmd_init(cmd, argc, argv);
if (ret)
goto RET;
mpi_dec_test_cmd_options(cmd);
ret = dec_decode(cmd);
if (MPP_OK == ret)
mpp_log("test success max memory %.2f MB\n", cmd->max_usage / (float)(1 << 20));
else
mpp_err("test failed ret %d\n", ret);
RET:
mpi_dec_test_cmd_deinit(cmd);
return ret;
}
gcc mpp_dec_test.c -o mpp_dec_test `pkg-config --cflags --libs rockchip_mpp rockchip_vpu` -lutils
tail -f /var/log/syslog //开启新终端,监控输出
./mpp_dec_test -i /media/200frames_count.h264 -t 7 -n 200 -o ./decode.raw -w 640 -h 480
使用如下步骤安装Qt
sudo apt-get install build-essential
sudo apt-get install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools
sudo apt-get install qtcreator
sudo apt-get install qt5*
首先创建Qt项目,使用qmake,创建完后打开.pro文件加入下图所示opencv库的路径
INCLUDEPATH += /ur/local/include/ \
/usr/local/include/opencv/ \
/usr/local/innclude/opencv2
LIBS += /usr/local/lib/
RK3588 内置 NPU 模块, 处理性能最高可达6TOPS。使用该NPU需要下载RKNN SDK。RKNN SDK包括两套源码,一套是RKNN-Toolkit2,是用来将onnx模型转换为rknn模型。另一套为 RKNPU2。,是用来在板端进行推理。下面分别介绍这两套源码。
安装RKNN-Toolkit2有两种方法,通过pip install安装或者通过Docker镜像安装。
sudo apt-get install python3 python3-dev python3-pip
sudo apt-get install libxslt1-dev zlib1g-dev libglib2.0 libsm6 libgl1-mesa-glx libprotobuf-dev gcc
RKNN-Toolkit2 工具git clone https://github.com/airockchip/rknn-toolkit2.git
cd rknn-toolkit2
pip3 install -r doc/requirements_cp36-1.5.2.txt
pip3 install packages/rknn_toolkit2-1.5.2+b642f30c-cp38-cp38-linux_x86_64.whl
目前安装的onnx会是最新的1.19.1版本,该版本会报错:AttributeError: module 'onnx' has no attribute 'mapping'
pip3 install onnx==1.18.0 onnxruntime==1.18.0
# 若没有出现错误,说明安装成功。同时按住Ctrl+D退出Python3。
python3
from rknn.api import RKNN
cd examples/onnx/yolov5
python3 test.py
转换模型和推理脚本 test.py 运行成功后,转换后的模型默认保存路径为 examples/onnx/yolov5/yolov5s_relu.rknn , 推理的图片结果保存在 examples/onnx/yolov5/result.jpg 。

Docker 环境。curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
sudo apt update
apt-cache policy docker-ce
sudo apt install docker-ce
sudo groupadd docker
sudo usermod -aG docker $USER
sudo reboot
sudo systemctl status docker
RKNN-Toolkit2 工具git clone https://github.com/airockchip/rknn-toolkit2.git
cd rknn-toolkit2
Dockercd docker/docker_full/
# 加载镜像
docker load --input rknn-toolkit2-1.5.2-cp36-docker.tar.gz
# 能查询到 REPOSITORY为 rknn-toolkit2,TAG为1.x.x-cp36则表示加载成功。
docker images
# 运行docker容器, 其中~/share/test/rknn-toolkit2-1.5.2/examples/onnx/yolov5为笔者的路径,读者需要修改为自己的路径。
docker run -t -i --privileged -v /dev/bus/usb:/dev/bus/usb -v ~/share/test/rknn-toolkit2-1.5.2/examples/onnx/yolov5:/rknn_yolov5_demo rknn-toolkit2:1.5.2-cp36 /bin/bash
cd rknn_yolov5_demo
# 转换yolov5s_relu.onnx 为 rknn 模型并行推理图片。
python3 ./test.py
# 退出 Docker
exit
转换模型和推理脚本 test.py 运行成功后,转换后的模型默认保存路径为 examples/onnx/yolov5/yolov5s_relu.rknn , 推理的图片结果保存在 examples/onnx/yolov5/result.jpg 。

前面两种方式转换和推理模型均运行在PC端的模拟器环境中。本段是在上面两种的基础上,将推理环境修改为板端进行推理。
Ubuntu 系统。需要安装 ADB 命令。读者的PC端是其他系统的话,请自行在网上搜索安装adb的方法。sudo apt install adb
将开发板的 type-c 口连接到电脑 usb 口,在 PC端的Ubuntu系统 中输入以下命令,查看是否正常连接到开发板。
adb shell
exit
adb shell pgrep rknn_server
如果没有进程号输出,表示rknn服务未运行。需要输入以下命令中其中一条即可。
adb shell
# 重启rknn服务
restart_rknn.sh
# 或者启动rknn服务
start_rknn.sh
exit
adb devices
这里笔者的开发板adb的id号是ca32be3b402e6579。
打开 rknn-toolkit2/examples/onnx/yolov5/test.py
# 修改脚本target和device_id。 修改对应平台类型值(”rk3566”、”rk3568”、”rk3588”、”rv1103”、”rv1106”、”rk3562”)和设备ID,保存后再执行脚本生成适用于板子的模型并进行推理图片。
# 找到以下语句
rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform='rk3566')
...
ret = rknn.init_runtime()
# 修改为
rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform='rk3588')
...
ret = rknn.init_runtime(target='rk3588', device_id='ca32be3b402e6579')
git clone https://github.com/rockchip-linux/rknpu2.git