1. 小核运行Freertos¶
1.1. C906小核¶
LubanCat-P1基于Sophgo SG2000 SoC,多核架构信息:
一个T-Head C906 64-bit RISC-V CPU核(1GHz)
一个ARM Cortex-A53 CPU核(1GHz)
一个T-Head C906 64位RISC-V核(700Mhz)
一个8051 8-bit低功耗mcu核(25-300MHz)
C906 riscv大核和A53核可以运行Linux系统,C906 riscv小核可以运行Freertos,8051核可以运行裸机。
默认分配给小核的运行内存为768kb,可修改SDK进行调整。
1.2. Freertos简介¶
FreeRTOS 由美国的 Richard Barry 于 2003 年发布,Richard Barry 是 FreeRTOS 的拥有 者和维护者,在过去的十多年中 FreeRTOS 历经了9个版本,与众多半导体厂商合作密切, 累计开发者数百万,是目前市场占有率最高的RTOS。
更多介绍参考: [野火]《FreeRTOS内核实现与应用开发实战指南》系列
1.3. Freertos运行测试¶
添加或者修改Freertos代码需要修改SDK,然后重新构建系统,对于SDK获取和编译部分,参考镜像构建与部署篇的 LubanCat-SDK 章节, 本文不作过多赘述,默认读者能够编译SDK。
1.3.1. 控制系统LED实验¶
小核Freertos代码控制系统心跳灯代码于2024年8月23日已经添加进SDK,如果使用比这个日期更加新的镜像,则不需要重新编译,可以直接进行测试,以下对野火添加的代码进行讲解。
1.3.1.1. 源代码讲解¶
对于小核rtos控制sys_led灯添加的代码部分可参考SDK提交信息: freertos 添加小核rtos控制sys_led的示例代码
1、添加SYS_CMD_ID
修改rtos_cmdqu.h,需要同时修改freertos源码中和osdrv驱动源码中的rtos_cmdqu.h
freertos/cvitek/driver/rtos_cmdqu/include/rtos_cmdqu.h
osdrv/interdrv/v2/rtos_cmdqu/rtos_cmdqu.h
值得注意的是新添加的CMD_ID需要在SYS_CMD_INFO_LIMIT上面,不然会报错。
2、添加控制led代码
freertos/cvitek/task/comm/include/lubancat_led_test.h
freertos/cvitek/task/comm/src/riscv64/lubancat_led_test.c
其中lubancat_led_test.h内容如下:
1 2 3 4 5 6 7 | #include <stdio.h>
#define GPIO1 0x03021000
#define GPIO_SWPORTA_DR 0x000
#define GPIO_SWPORTA_DDR 0x004
void lubancat_led_control(int status);
|
系统心跳灯为XGPIOB[11],对于GPIO1 11,以上定义了GPIO1的基地址和GPIO_SWPORTA_DR(电平状态控制寄存器)、GPIO_SWPORTA_DDR(输入输出模式控制寄存器)的偏移地址。
可以查阅sg2000_trm_cn.pdf手册找到相关地址定义,手册在资料网盘/5-数据手册目录中。
其中lubancat_led_test.c内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include "lubancat_led_test.h"
void lubancat_led_control(int status)
{
/* 设置为输出*/
*(uint32_t*)(GPIO1 | GPIO_SWPORTA_DDR) = 1 << 11;
/* 读取当前寄存器值 */
uint32_t current_value = *(uint32_t*)(GPIO1 | GPIO_SWPORTA_DR);
if (status) {
/* 设置第11位为1 */
*(uint32_t*)(GPIO1 | GPIO_SWPORTA_DR) = current_value | (1 << 11);
printf("led light off\n");
} else {
/* 清除第11位 */
*(uint32_t*)(GPIO1 | GPIO_SWPORTA_DR) = current_value & ~(1 << 11);
printf("led light on\n");
}
}
|
以上代码比较简单,先将GPIO1 11引脚设置为输出,然后根据输入参数控制引脚电平。
3、添加任务
freertos/cvitek/task/comm/src/riscv64/comm_main.c
freertos的入口函数为main_cvirtos(),执行注册中断服务,创建FreeRTOS任务以及启动FreeRTOS调度器。
截取main_create_tasks函数部分内容如下:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | void main_create_tasks(void)
{
u8 i = 0;
#define TASK_INIT(_idx) \
do { \
gTaskCtx[_idx].queHandle = xQueueCreate(gTaskCtx[_idx].queLength, sizeof(cmdqu_t)); \
if (gTaskCtx[_idx].queHandle != NULL && gTaskCtx[_idx].runTask != NULL) { \
xTaskCreate(gTaskCtx[_idx].runTask, gTaskCtx[_idx].name, gTaskCtx[_idx].stack_size, \
NULL, gTaskCtx[_idx].priority, NULL); \
} \
} while(0)
for (; i < ARRAY_SIZE(gTaskCtx); i++) {
TASK_INIT(i);
}
}
TASK_CTX_S gTaskCtx[E_QUEUE_MAX] = {
{
.name = "ISP",
.stack_size = configMINIMAL_STACK_SIZE * 8,
.priority = tskIDLE_PRIORITY + 3,
.runTask = NULL,
.queLength = 1,
.queHandle = NULL,
},
........
{
.name = "CMDQU",
.stack_size = configMINIMAL_STACK_SIZE,
.priority = tskIDLE_PRIORITY + 5,
.runTask = prvCmdQuRunTask,
.queLength = 30,
.queHandle = NULL,
},
{
.name = "AUDIO",
.stack_size = configMINIMAL_STACK_SIZE*15,
.priority = tskIDLE_PRIORITY + 3,
.runTask = prvAudioRunTask,
.queLength = 10,
.queHandle = NULL,
},
};
|
使用宏TASK_INIT来初始化每个任务的队列和创建任务,如果队列创建成功且任务函数 (runTask) 存在,任务就会被创建。
TASK_CTX_S gTaskCtx[E_QUEUE_MAX]:定义了一个任务上下文数组 gTaskCtx,每个元素包含任务的相关信息, 比如名称、栈大小、优先级、任务函数、队列长度和队列句柄。
name:任务名称。
stack_size:任务栈大小。
priority:任务优先级。
runTask:任务函数指针。
queLength:任务使用的队列长度。
queHandle:任务的队列句柄。
例如,”CMDQU” 任务的队列长度为30,优先级为tskIDLE_PRIORITY + 5,任务函数是prvCmdQuRunTask,栈大小是基本栈大小的1倍。
我们添加的控制代码可以添加到”CMDQU”任务的prvCmdQuRunTask任务函数中,部分内容如下:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | /* lubancat led test */
#include "lubancat_led_test.h"
void prvCmdQuRunTask(void *pvParameters)
{
.....
cvi_spinlock_init();
printf("prvCmdQuRunTask run\n");
.....
for (;;) {
xQueueReceive(gTaskCtx[E_QUEUE_CMDQU].queHandle, &rtos_cmdq, portMAX_DELAY);
switch (rtos_cmdq.cmd_id) {
case SYS_CMD_INFO_STOP_ISR_DONE:
// stop interrupt in order to avoid losing frame
if (rtos_cmdq.ip_id == IP_VI) {
stop_ip |= STOP_CMD_DONE_VI;
rtos_cmdq.ip_id = IP_VCODEC;
rtos_cmdq.cmd_id = SYS_CMD_INFO_STOP_ISR;
xQueueSend(gTaskCtx[E_QUEUE_VCODEC].queHandle, &rtos_cmdq, 0U);
break;
}
if (rtos_cmdq.ip_id == IP_VCODEC)
stop_ip |= STOP_CMD_DONE_VCODE;
if (stop_ip != STOP_CMD_DONE_ALL)
break;
else {
// all isr of ip is disabled, and send msg back to linux
rtos_cmdq.ip_id = IP_SYSTEM;
}
case SYS_CMD_LUBANCAT_LED:
rtos_cmdq.cmd_id = SYS_CMD_LUBANCAT_LED;
printf("recv cmd(%d) from C906B, param_ptr [0x%x]\n", rtos_cmdq.cmd_id, rtos_cmdq.param_ptr);
if (rtos_cmdq.param_ptr == LUBANCAT_LED_ON) {
lubancat_led_control(0);
} else {
lubancat_led_control(1);
}
rtos_cmdq.param_ptr = LUBANCAT_LED_DONE;
rtos_cmdq.resv.valid.rtos_valid = 1;
rtos_cmdq.resv.valid.linux_valid = 0;
printf("recv cmd(%d) from C906B...send [0x%x] to C906B\n", rtos_cmdq.cmd_id, rtos_cmdq.param_ptr);
goto send_label;
break;
case SYS_CMD_INFO_LINUX:
default:
|
代码讲解如下:
任务开始时初始化了一个自旋锁,并打印一条启动信息。
任务从队列中接收命令(xQueueReceive),并根据rtos_cmdq.cmd_id执行相应的操作。
其中,cmd_id为SYS_CMD_LUBANCAT_LED就是添加控制LED的代码:
设置rtos_cmdq.cmd_id为SYS_CMD_LUBANCAT_LED。
输出收到的命令和参数信息。
根据 rtos_cmdq.param_ptr的值调用lubancat_led_control()函数来控制LED的亮灭。
将rtos_cmdq.param_ptr设置为LUBANCAT_LED_DONE,表示命令已处理完成。
1.3.1.2. 测试代码讲解¶
创建freertos_led.c,添加以下内容:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | #include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
enum SYSTEM_CMD_TYPE {
CMDQU_SEND = 1,
CMDQU_SEND_WAIT,
CMDQU_SEND_WAKEUP,
};
#define RTOS_CMDQU_DEV_NAME "/dev/cvi-rtos-cmdqu"
#define RTOS_CMDQU_SEND _IOW('r', CMDQU_SEND, unsigned long)
#define RTOS_CMDQU_SEND_WAIT _IOW('r', CMDQU_SEND_WAIT, unsigned long)
#define RTOS_CMDQU_SEND_WAKEUP _IOW('r', CMDQU_SEND_WAKEUP, unsigned long)
enum IP_TYPE {
IP_ISP = 0,
IP_VCODEC,
IP_VIP,
IP_VI,
IP_RGN,
IP_AUDIO,
IP_SYSTEM,
IP_CAMERA,
IP_LIMIT,
};
enum SYS_CMD_ID {
SYS_CMD_INFO_TRANS = 0x50,
SYS_CMD_INFO_LINUX_INIT_DONE,
SYS_CMD_INFO_RTOS_INIT_DONE,
SYS_CMD_INFO_STOP_ISR,
SYS_CMD_INFO_STOP_ISR_DONE,
SYS_CMD_INFO_LINUX,
SYS_CMD_INFO_RTOS,
SYS_CMD_SYNC_TIME,
SYS_CMD_INFO_DUMP_MSG,
SYS_CMD_INFO_DUMP_EN,
SYS_CMD_INFO_DUMP_DIS,
SYS_CMD_INFO_DUMP_JPG,
SYS_CMD_INFO_TRACE_SNAPSHOT_START,
SYS_CMD_INFO_TRACE_SNAPSHOT_STOP,
SYS_CMD_INFO_TRACE_STREAM_START,
SYS_CMD_INFO_TRACE_STREAM_STOP,
SYS_CMD_LUBANCAT_LED,
SYS_CMD_INFO_LIMIT,
};
enum LUBANCAT_LED_STATUS {
LUBANCAT_LED_ON = 0x00,
LUBANCAT_LED_OFF,
LUBANCAT_LED_DONE,
};
struct valid_t {
unsigned char linux_valid;
unsigned char rtos_valid;
} __attribute__((packed));
typedef union resv_t {
struct valid_t valid;
unsigned short mstime; // 0 : noblock, -1 : block infinite
} resv_t;
typedef struct cmdqu_t cmdqu_t;
/* cmdqu size should be 8 bytes because of mailbox buffer size */
struct cmdqu_t {
unsigned char ip_id;
unsigned char cmd_id : 7;
unsigned char block : 1;
union resv_t resv;
unsigned int param_ptr;
} __attribute__((packed)) __attribute__((aligned(0x8)));
int main()
{
int ret = 0;
int fd = open(RTOS_CMDQU_DEV_NAME, O_RDWR);
if(fd <= 0)
{
printf("open failed! fd = %d\n", fd);
return 0;
}
struct cmdqu_t cmd = {0};
cmd.ip_id = IP_SYSTEM;
cmd.cmd_id = SYS_CMD_LUBANCAT_LED;
cmd.resv.mstime = 100;
cmd.block = 1;
cmd.param_ptr = LUBANCAT_LED_OFF;
ret = ioctl(fd , RTOS_CMDQU_SEND, &cmd);
if(ret < 0)
{
printf("ioctl error!\n");
close(fd);
}
printf("C906B: cmd.param_ptr = 0x%x\n", cmd.param_ptr);
sleep(1);
cmd.ip_id = IP_SYSTEM;
cmd.cmd_id = SYS_CMD_LUBANCAT_LED;
cmd.resv.mstime = 100;
cmd.block = 1;
cmd.param_ptr = LUBANCAT_LED_ON;
ret = ioctl(fd , RTOS_CMDQU_SEND, &cmd);
if(ret < 0)
{
printf("ioctl error!\n");
close(fd);
}
printf("C906B: cmd.param_ptr = 0x%x\n", cmd.param_ptr);
sleep(2);
cmd.ip_id = IP_SYSTEM;
cmd.cmd_id = SYS_CMD_LUBANCAT_LED;
cmd.resv.mstime = 100;
cmd.block = 1;
cmd.param_ptr = LUBANCAT_LED_OFF;
ret = ioctl(fd , RTOS_CMDQU_SEND, &cmd);
if(ret < 0)
{
printf("ioctl error!\n");
close(fd);
}
printf("C906B: cmd.param_ptr = 0x%x\n", cmd.param_ptr);
sleep(1);
close(fd);
return 0;
}
|
以上通过设备文件/dev/cvi-rtos-cmdqu向命令队列发送命令,进行通信,下面是对各个部分的解释:
1、命令队列相关定义
1 2 3 4 5 6 7 8 9 10 | enum SYSTEM_CMD_TYPE {
CMDQU_SEND = 1,
CMDQU_SEND_WAIT,
CMDQU_SEND_WAKEUP,
};
#define RTOS_CMDQU_DEV_NAME "/dev/cvi-rtos-cmdqu"
#define RTOS_CMDQU_SEND _IOW('r', CMDQU_SEND, unsigned long)
#define RTOS_CMDQU_SEND_WAIT _IOW('r', CMDQU_SEND_WAIT, unsigned long)
#define RTOS_CMDQU_SEND_WAKEUP _IOW('r', CMDQU_SEND_WAKEUP, unsigned long)
|
SYSTEM_CMD_TYPE枚举定义了三种命令类型:CMDQU_SEND,CMDQU_SEND_WAIT,CMDQU_SEND_WAKEUP。
RTOS_CMDQU_DEV_NAME是设备文件的路径。
_IOW宏用于定义写命令。
2、 IP 类型和系统命令定义
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 32 33 34 35 36 37 38 | enum IP_TYPE {
IP_ISP = 0,
IP_VCODEC,
IP_VIP,
IP_VI,
IP_RGN,
IP_AUDIO,
IP_SYSTEM,
IP_CAMERA,
IP_LIMIT,
};
enum SYS_CMD_ID {
SYS_CMD_INFO_TRANS = 0x50,
SYS_CMD_INFO_LINUX_INIT_DONE,
SYS_CMD_INFO_RTOS_INIT_DONE,
SYS_CMD_INFO_STOP_ISR,
SYS_CMD_INFO_STOP_ISR_DONE,
SYS_CMD_INFO_LINUX,
SYS_CMD_INFO_RTOS,
SYS_CMD_SYNC_TIME,
SYS_CMD_INFO_DUMP_MSG,
SYS_CMD_INFO_DUMP_EN,
SYS_CMD_INFO_DUMP_DIS,
SYS_CMD_INFO_DUMP_JPG,
SYS_CMD_INFO_TRACE_SNAPSHOT_START,
SYS_CMD_INFO_TRACE_SNAPSHOT_STOP,
SYS_CMD_INFO_TRACE_STREAM_START,
SYS_CMD_INFO_TRACE_STREAM_STOP,
SYS_CMD_LUBANCAT_LED,
SYS_CMD_INFO_LIMIT,
};
enum LUBANCAT_LED_STATUS {
LUBANCAT_LED_ON = 0x00,
LUBANCAT_LED_OFF,
LUBANCAT_LED_DONE,
};
|
IP_TYPE枚举定义了不同的IP类型,例如 IP_SYSTEM。
SYS_CMD_ID枚举定义了各种系统命令,例如 SYS_CMD_LUBANCAT_LED。
LUBANCAT_LED_STATUS枚举定义了控制LED的状态值。
3、数据结构定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | struct valid_t {
unsigned char linux_valid;
unsigned char rtos_valid;
} __attribute__((packed));
typedef union resv_t {
struct valid_t valid;
unsigned short mstime; // 0 : noblock, -1 : block infinite
} resv_t;
typedef struct cmdqu_t cmdqu_t;
/* cmdqu size should be 8 bytes because of mailbox buffer size */
struct cmdqu_t {
unsigned char ip_id;
unsigned char cmd_id : 7;
unsigned char block : 1;
union resv_t resv;
unsigned int param_ptr;
} __attribute__((packed)) __attribute__((aligned(0x8)));
|
valid_t 结构体用于存储Linux和RTOS的有效性标志。
resv_t 联合体可以存储valid_t结构体或 mstime(用于表示阻塞时间)。
cmdqu_t 结构体表示一个命令队列条目,包括IP ID、命令 ID、阻塞标志、保留字段以及参数指针。
4、主程序
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | int main()
{
int ret = 0;
int fd = open(RTOS_CMDQU_DEV_NAME, O_RDWR);
if(fd <= 0)
{
printf("open failed! fd = %d\n", fd);
return 0;
}
struct cmdqu_t cmd = {0};
cmd.ip_id = IP_SYSTEM;
cmd.cmd_id = SYS_CMD_LUBANCAT_LED;
cmd.resv.mstime = 100;
cmd.block = 1;
cmd.param_ptr = LUBANCAT_LED_OFF;
ret = ioctl(fd , RTOS_CMDQU_SEND, &cmd);
if(ret < 0)
{
printf("ioctl error!\n");
close(fd);
}
printf("C906B: cmd.param_ptr = 0x%x\n", cmd.param_ptr);
sleep(1);
cmd.ip_id = IP_SYSTEM;
cmd.cmd_id = SYS_CMD_LUBANCAT_LED;
cmd.resv.mstime = 100;
cmd.block = 1;
cmd.param_ptr = LUBANCAT_LED_ON;
ret = ioctl(fd , RTOS_CMDQU_SEND, &cmd);
if(ret < 0)
{
printf("ioctl error!\n");
close(fd);
}
printf("C906B: cmd.param_ptr = 0x%x\n", cmd.param_ptr);
sleep(2);
cmd.ip_id = IP_SYSTEM;
cmd.cmd_id = SYS_CMD_LUBANCAT_LED;
cmd.resv.mstime = 100;
cmd.block = 1;
cmd.param_ptr = LUBANCAT_LED_OFF;
ret = ioctl(fd , RTOS_CMDQU_SEND, &cmd);
if(ret < 0)
{
printf("ioctl error!\n");
close(fd);
}
printf("C906B: cmd.param_ptr = 0x%x\n", cmd.param_ptr);
sleep(1);
close(fd);
return 0;
}
|
程序通过open打开设备文件,之后使用ioctl系统调用向设备发送命令,控制LED的开关状态。
初始化命令结构体并发送命令:初始化cmdqu_t结构体,设置IP ID、命令 ID、阻塞时间和参数指针。
1.3.1.3. 运行测试¶
1、由于Linux驱动层已经占用sys_led,需要先释放占用,执行以下命令:
1 2 3 4 5 | #释放占用
sudo sh -c "echo 0 > /sys/class/leds/sys_status_led/brightness"
#如果需要恢复则执行
sudo sh -c "echo heartbeat > /sys/class/leds/sys_status_led/trigger"
|
2、编译程序:
1 2 3 4 5 | #板卡本地编译
gcc freertos_led.c -o freertos_led
#交叉编译
host-tools/gcc/riscv64-linux-musl-x86_64/bin/riscv64-unknown-linux-musl-gcc freertos_led.c -o freertos_led -march=rv64imafdcvxthead -mcmodel=medany -mabi=lp64d -static
|
3、运行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #运行
sudo ./freertos_led
#输出信息如下
RT: [360.746469]recv cmd(96) from C906B, param_ptr [0x00000001]
RT: [360.752129]led light off
RT: [360.754905]recv cmd(96) from C906B...send [0x00000002] to C906B
C906B: cmd.param_ptr = 0x1
RT: [361.746928]recv cmd(96) from C906B, param_ptr [0x00000000]
RT: [361.752587]led light on
RT: [361.755273]recv cmd(96) from C906B...send [0x00000002] to C906B
C906B: cmd.param_ptr = 0x0
RT: [363.747087]recv cmd(96) from C906B, param_ptr [0x00000001]
RT: [363.752746]led light off
RT: [363.755522]recv cmd(96) from C906B...send [0x00000002] to C906B
C906B: cmd.param_ptr = 0x1
|
RT:开头的就是Freertos发送过来的信息,打印了cmd_id、param_ptr以及当前灯状态信息。
4、实验现象
系统sys_led先熄灭1s再点亮2s最后再熄灭。