eefocus_3891719 发表于 2024-11-13 16:47:40

【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, &paramLedIdLength);
        paramLedStatus = FreeRTOS_CLIGetParameter(pcCommandString, 2, &paramLedStatusLength);

        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, &paramLedIdLength);

        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]
查看完整版本: 【Avnet | NXP FRDM-MCXN947试用活动】移植FreeRTOS-CLI 新增命令控...