【Avnet | NXP FRDM-MCXN947试用活动】移植FreeRTOS-CLI 新增命令控...
# 背景此次任务通过串口命令行控制 RGB LED,相比较与上一次任务通过单个字符控制增加了 FreeRTOS-CLI 组件,支持更复杂的、带参数的命令。
1. 搭建 VSCode 开发环境;
2. 添加 FreeRTOS 组件,创建任务;
3. 添加 FreeRTOS-CLI 组件,打通适配层
4. 添加 FreeRTOS-CLI 自定义命令,控制 RGB LED;
# 搭建 VS Code 开发环境
无论是使用 MCUXpresso IDE 还是 VS Code 开发环境,都必须要:
1. 安装 MCUXpresso IDE,因为 IDE 里有NXP支持的 GCC 工具链;
2. 下载 **mcux_sdk_frdm_mcxn947** SDK;
3. VS Code 安装插件 **MCUXPresso for VS Code**;
4. VS Code 配置插件 **MCUXpresso for VS Code**
前几个步骤都好说,这里简短演示下 VS Code 配置插件 **MCUXpresso for VS Code**。
## 配置 MCUXpresso for VS Code
!(https://www.eefocus.com/forum/data/attachment/forum/202411/13/163046q40ww40nx0e80ps6.png)
1. 在 VS Code 侧边栏单击 MCUXpresso 图标展开右侧视图;
2. 单击(2)处展开右侧视图。这里建议单击 **Import Example from Repository**,因为它比上面的 **Import Repository** 有更多的配置选型,可以直接从这里创建示例工程;
3. 单击(3)处选择本地的 SDK 路径,例如这里选择已经下载并解压缩的 **mcux_sdk_frdm_mcxn947**;
4. 单击(4)处选择 **MCUXpresso IDE** 的 GCC 工具链;
5. 单击(5)处选择开发板,一个SDK可以支持同类型的几个开发板,根据需要选择对应的开发板;
6. 单击(6)处选择示例工程模版,也可以输入关键词搜索;
7. 编辑(7)处输入框输入新建的工程名字;
8. 单击(8)处选择工程保存路径;
9. 最后点击 **Create** 即可以创建工程。
## 工程结构
- **.vscode/** 包含一些配置选项、调试启动文件;
- **__repo__/** 是一个链接文件,执行 SDK 所在文件夹;
- **app/** 包含应用代码;
- **armgcc** 包含 CMakeLists.txt 目录程序以及一些 bat、shell 编译脚本,如果新增了源文件和头文件,需要修改此处的 CMakeLists.txt 文件;
- **board/** 包含管脚、时钟、外设初始化代码,是 **MCUXpresso Config Tools** 自动生成的文件夹;
- **iar/** 是 IAR IDE 工程文件和链接脚本;
- **mdk/** 是 MDK IDE 工程文件和链接脚本;
- 后缀名为 `*.mex` 是 **MCUXpresso Config Tools** 的输入文件;
- **readme.md** 是示例工程的说明文档;
## 编译、下载、调试
采用 CMake + GCC 编译此工程,图简便的话直接点击 MCUXpresso for VS Code 中的图标,如下图所示。
!(https://www.eefocus.com/forum/data/attachment/forum/202411/13/163122hzxyl7lyfdiua7yg.png)
# 添加 FreeRTOS 组件
尝试过 MCUXpresso IDE 添加 FreeRTOS 组件,虽然把源码拷贝过来添加到工程里,但是 **port** 层的源文件和头文件缺失了,需要从例程拷贝复制,太麻烦了。
而 VS Code 中添加组件的方式特别简单,如下添加 FreeRTOS 组件,简直不要太爽了。
1. 鼠标右键单击工程名;
2. 在弹出的菜单中选择(2)配置工程;
3. 然后选择(3)管理组件;
4. 在(4)处编辑框输入 **kernel** 过滤组件;
5. 在(5)处选择合适的 FreeRTOS 类型;
6. 在(6)处点击确认即可;
!(https://www.eefocus.com/forum/data/attachment/forum/202411/13/163143j6w4d6wcwowlzgn1.png)
!(https://www.eefocus.com/forum/data/attachment/forum/202411/13/163156vzh64826865jz2o5.png)
添加 FreeRTOS 所做的更改体现在 **armgcc/config.cmake** 文件,如下图所示文件中增加了几处和 **freertos** 相关的配置选项。当然 FreeRTOS 源码不会拷贝过来,它依然存在于 SDK 路径中,但是需要拷贝一份 **FreeRTOSConfig.h** 过来,自行修改其中的参数。
!(https://www.eefocus.com/forum/data/attachment/forum/202411/13/163208h9tpl1lcu9tcc8pc.png)
## 新建 FreeRTOS 任务
新建一个最简单的 FreeRTOS 任务,每隔两秒钟打印一次信息。
```c
int main(void)
{
// 管脚复用和配置
BOARD_InitBootClocks();
BOARD_InitBootPeripherals();
BOARD_InitBootPins();
BOARD_InitSWD_DEBUGPins();
// 调试串口打印日志
BOARD_InitDebugConsole();
PRINTF("\r\n");
PRINTF("\r\n Build: %s %s\r\n\r\n", __DATE__, __TIME__);
if (xTaskCreate(zygote_task, "zygote_task", ZYGOTE_TASK_STACK_SIZE, NULL, ZYGOTE_TASK_PRIORITY, NULL) !=
pdPASS)
{
PRINTF("Task creation failed!.\r\n");
while (1)
;
}
vTaskStartScheduler();
for (;;)
;
}
static void zygote_task(void *pvParameters)
{
uint32_t zygote_loop_cnt = 0;
for (;;) {
zygote_loop_cnt++;
PRINTF("zygote loop cnt: %u \r\n", zygote_loop_cnt);
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
```
!(https://www.eefocus.com/forum/data/attachment/forum/202411/13/163237rbmtlgqggmql1mns.png)
# FreeRTOS-CLI 组件
## 组件介绍
FreeRTOS-CLI 是 FreeRTOS 官方的组件,支持注册多参数命令,命令接口可以是串口、网络套接字等。
当前使用的版本是 **FreeRTOS+CLI V1.0.4**,适配层使用串口,注册两个多参数的命令,控制开发板上的 RGB LED 亮灭。
## 添加 FreeRTOS-CLI 组件
在源码顶层目录新建 **3rdparty** 目录并拷贝 **FreeRTOS_Plus_CLI** 组件到此,目录结构如下:
```shell
FreeRTOS_Plus_CLI/
port/
serial.c
serial.h
src/
FreeRTOS_CLI.c
FreeRTOS_CLI.h
```
我们只需要关心 **port/** 目录即可,适配UART层在这里。
## 适配层
重点在以下几个函数的适配:
- **xSerialPortInitMinimal()**
- **xSerialPortInit()**
- **vSerialPutString()**
- **xSerialGetChar()**
- **xSerialPutChar()**
因为管脚初始化已经由MCUXpresso Config Tools 图形化配置完成,通过Debug UART 进行输入输出,所以前两个串口初始化函数可以留空,重点在于 `xSerialGetChar()` 和 `xSerialPutChar()` 的实现,这里简单实现一下,通过 Debug UART 进行输入输出即可。
```c
signed portBASE_TYPE xSerialGetChar(xComPortHandle pxPort,
signed char *pcRxedChar,
TickType_t xBlockTime)
{
#ifndef DEBUG_CONSOLE_TRANSFER_NON_BLOCKING
*pcRxedChar = GETCHAR();
return pdPASS;
#else
char data = 0;
while (xBlockTime-- > 0) {
if (kStatus_Success == DbgConsole_TryGetchar(&data)) {
*pcRxedChar = data;
return pdPASS;
} else {
vTaskDelay(pdMS_TO_TICKS(1));
}
}
return pdFAIL;
#endif
}
signed portBASE_TYPE xSerialPutChar(xComPortHandle pxPort, signed char cOutChar,
TickType_t xBlockTime)
{
signed portBASE_TYPE ch = 0;
ch = PUTCHAR(cOutChar);
return ch;
}
```
# RGB LED 控制命令
为了点亮、熄灭 RGB LED,需要实现如下这样的命令:
- **ledset r on** 点亮红色LED,同理 **ledset g/b on** 点亮绿色、蓝色LED;
- **ledset r off** 熄灭红色LED,同理 **ledset g/b off** 熄灭绿色、蓝色LED;
- **ledget r** 获取红色LED状态,如 **LEDR: OFF** 表示熄灭, **LEDR: ON** 表示点亮;
## 点亮、熄灭 LED 命令的实现
```c
// TODO: ledset r/g/b on/off
// 作用:设置灯的状态
// 命令: ledset
// 参数1:编号,这里以 r/g/b 缩写分别表示 "RED/GREE/BLUE" 三个灯
// 参数2:开关,这里以字符串 on/off 分别表示 "开灯/关灯"
/**
* @brief
*
* @param pcWriteBuffer
* @param xWriteBufferLen
* @param pcCommandString
* @return BaseType_t
*/
static BaseType_t prvLedSetCommand( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString )
{
configASSERT(pcWriteBuffer);
/* param1: r/g/b */
const char *paramLedId = NULL;
BaseType_t paramLedIdLength = 0;
led_id_e mLedId = LED_ID_INVALID;
/* param2: on/off */
const char *paramLedStatus = NULL;
BaseType_t paramLedStatusLength = 0;
led_status_e mLedStatus;
// 首先清除输出缓冲区旧的内容
memset(pcWriteBuffer, 0, xWriteBufferLen);
// TODO: 根据两个参数打印返回的字符串
paramLedId = FreeRTOS_CLIGetParameter(pcCommandString, 1, ¶mLedIdLength);
paramLedStatus = FreeRTOS_CLIGetParameter(pcCommandString, 2, ¶mLedStatusLength);
if (strncmp("r", paramLedId, 1) == 0) {
mLedId = LED_ID_RED;
} else if (strncmp("g", paramLedId, 1) == 0) {
mLedId = LED_ID_GREEN;
} else if (strncmp("b", paramLedId, 1) == 0) {
mLedId = LED_ID_BLUE;
} else {
mLedId = LED_ID_INVALID;
}
if (strncmp("on", paramLedStatus, 2) == 0) {
mLedStatus = LED_ON;
} else if (strncmp("off", paramLedStatus, 3) == 0) {
mLedStatus = LED_OFF;
}
led_set_status(mLedId, mLedStatus);
/* There is no more data to return after this single string, so return pdFALSE. */
return pdFALSE;
}
```
## 获取 LED 状态命令的实现
```c
// TODO: ledget r/g/b
// 作用:获取灯的状态
// 命令: ledget
// 参数1:编号
/**
* @brief
*
* @param pcWriteBuffer
* @param xWriteBufferLen
* @param pcCommandString
* @return BaseType_t
*/
static BaseType_t prvLedGetCommand( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString )
{
configASSERT(pcWriteBuffer);
/* param1: r/g/b */
const char *paramLedId = NULL;
BaseType_t paramLedIdLength = 0;
led_id_e mLedId = LED_ID_INVALID;
led_status_e mLedStatus;
// 首先清除输出缓冲区旧的内容
memset(pcWriteBuffer, 0, xWriteBufferLen);
paramLedId = FreeRTOS_CLIGetParameter(pcCommandString, 1, ¶mLedIdLength);
if (strncmp("r", paramLedId, 1) == 0) {
mLedId = LED_ID_RED;
} else if (strncmp("g", paramLedId, 1) == 0) {
mLedId = LED_ID_GREEN;
} else if (strncmp("b", paramLedId, 1) == 0) {
mLedId = LED_ID_BLUE;
} else {
mLedId = LED_ID_INVALID;
}
/* 获取灯的状态 */
mLedStatus = led_get_status(mLedId);
/* 输出灯的状态,输出到 pcWriteBuffer 缓冲区中 */
sprintf(pcWriteBuffer, "%s: %s\r\n", led_helper_id_to_string(mLedId), led_helper_status_to_string(mLedStatus));
/* There is no more data to return after this single string, so return pdFALSE. */
return pdFALSE;
}
```
## 注册命令
先定义结构体,把命令字符串和解析函数关联在一起。
```c
/* Structure that defines the "ledset" command line command.This generates
a table that gives information on each task in the system. */
static const CLI_Command_Definition_t xLedSet =
{
"ledset", /* The command string to type. */
"\r\nledset <param1> <param2>:\r\n set r/g/b led status\r\nexample: ledset r on or ledset g off\r\n",
prvLedSetCommand, /* The function to run. */
2 /* 2 parameters are expected. */
};
/* Structure that defines the "ledget" command line command.This generates
a table that gives information on each task in the system. */
static const CLI_Command_Definition_t xLedGet =
{
"ledget", /* The command string to type. */
"\r\nledget <param1>:\r\n get r/g/b led status\r\nexample: ledget r or ledget g\r\n",
prvLedGetCommand, /* The function to run. */
1 /* 1 parameters are expected. */
};
```
再在合适的时机注册命令,如下所示:
```c
void vRegisterBspCliCommands(void)
{
/* Register all the command line commands defined immediately above. */
FreeRTOS_CLIRegisterCommand( &xLedSet );
FreeRTOS_CLIRegisterCommand( &xLedGet );
}
```
```c
static void zygote_task(void *pvParameters)
{
uint32_t zygote_loop_cnt = 0;
/* FreeRTOS-CLI 任务创建 */
vUARTCommandConsoleStart();
extern void vRegisterSampleCLICommands(void);
vRegisterSampleCLICommands();
vRegisterBspCliCommands();
for (;;) {
zygote_loop_cnt++;
PRINTF("zygote loop cnt: %u \r\n", zygote_loop_cnt);
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
```
## 验证
- 发送命令 `ledsetron` 电量红色 LED
- 发送命令 `ledsetroff` 熄灭红色 LED
- 发送命令 `ledget r` 获取红色 LED 点亮状态
- 替换 r/g/b 可以正确执行命令
!(https://www.eefocus.com/forum/data/attachment/forum/202411/13/163257hwb4siiweg0etktw.png)
!(https://www.eefocus.com/forum/data/attachment/forum/202411/13/164721vybqe4uny242n2wq.gif)
页:
[1]