FreeRTOS提供有不同的组件为用户提供不同的功能,本文移植其中的FreeRTOS-CLI命令行工具到NUCELO-WBA65RI开发板上。
1、STM32CubeMX配置
参考【NUCLEO-WBA65RI硬件资源、LED和串口打印】(https://shequ.stmicroelectronics.cn/thread-792019-1-1.html)
完成USART1和LED引脚的外设配置,使能USART1的中断。

在"Middleware and Software Packs"中选择X-Cube-FreeRTOS组件。

在其中添加用于处理命令行的任务。

配置完成后,保存生成代码。
2、移植FreeRTOS_CLI
在FreeRTOS仓库的FreeRTOS-Plus/Source/FreeRTOS-Plus-CLI
路径中有FreeRTOS_CLI的源代码。

在工程目录中添加Drivers文件夹用于存放FreeRTOS CLI和cli应用的代码,文件夹的结构如下图所示。

在FreeRTOSConfig.h
中添加以下宏定义
#define configCOMMAND_INT_MAX_OUTPUT_SIZE 200
cli_app.h中声明几个用户应用的函数以及命令行处理函数,并在“app_freertos.c”中添加该头文件的引用
#ifndef INC_CLI_APP_H_
#define INC_CLI_APP_H_
#include "stdint.h"
void processRxedChar(uint8_t rxChar);
void handleNewline(const char *const pcInputString, char *cOutputBuffer, uint8_t *cInputIndex);
void handleCharacterInput(uint8_t *cInputIndex, char *pcInputString);
void vRegisterCLICommands(void);
void vCommandConsoleTask(void *pvParameters);
#endif /* INC_CLI_APP_H_ */
cli_app.c中实现FreeRTOS_CLI移植的主要工作,添加必要的头文件
#include "main.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "FreeRTOS_CLI.h"
#include "cli_app.h"
#include "stdbool.h"
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
#define MAX_INPUT_LENGTH 50
#define USING_VS_CODE_TERMINAL 0
#define USING_OTHER_TERMINAL 1 // e.g. Putty, TerraTerm
定义用于缓存数据的变量以及命令行常量
uint8_t cli_recv;
char cOutputBuffer[configCOMMAND_INT_MAX_OUTPUT_SIZE], pcInputString[MAX_INPUT_LENGTH];
int8_t cRxedChar;
const char * cli_prompt = "\r\ncli> ";
/* CLI escape sequences*/
uint8_t backspace[] = "\b \b";
uint8_t backspace_tt[] = " \b";
重定义_write函数,用于printf函数重定向
int _write(int file, char *data, int len)
{
UNUSED(file);
// Transmit data using UART2
for (int i = 0; i < len; i++)
{
// Send the character
USART1->TDR = (uint16_t)data[i];
// Wait for the transmit buffer to be empty
while (!(USART1->ISR & USART_ISR_TXE));
}
return len;
}
定义CLI命令函数和命令数组以及相关的注册函数,实现控制台清屏、加法以及控制开发板LED灯亮灭的功能。
//*****************************************************************************
BaseType_t cmd_add(char *pcWriteBuffer, size_t xWriteBufferLen,
const char *pcCommandString)
{
char *pcParameter1, *pcParameter2;
BaseType_t xParameter1StringLength, xParameter2StringLength;
/* Obtain the name of the source file, and the length of its name, from
the command string. The name of the source file is the first parameter. */
pcParameter1 = FreeRTOS_CLIGetParameter
(
/* The command string itself. */
pcCommandString,
/* Return the first parameter. */
1,
/* Store the parameter string length. */
&xParameter1StringLength
);
pcParameter2 = FreeRTOS_CLIGetParameter
(
/* The command string itself. */
pcCommandString,
/* Return the first parameter. */
2,
/* Store the parameter string length. */
&xParameter2StringLength
);
// convert the string to a number
int32_t xValue1 = strtol(pcParameter1, NULL, 10);
int32_t xValue2 = strtol(pcParameter2, NULL, 10);
// add the two numbers
int32_t xResultValue = xValue1 + xValue2;
// convert the result to a string
char cResultString[10];
itoa(xResultValue, cResultString, 10);
// copy the result to the write buffer
strcpy(pcWriteBuffer, cResultString);
return pdFALSE;
}
//*****************************************************************************
BaseType_t cmd_toggle(char *pcWriteBuffer, size_t xWriteBufferLen,
const char *pcCommandString)
{
(void)pcCommandString;
(void)xWriteBufferLen;
memset(pcWriteBuffer, 0x00, xWriteBufferLen);
HAL_GPIO_TogglePin(LD1_GPIO_PORT, LD1_PIN);
HAL_GPIO_TogglePin(LD2_GPIO_PORT, LD2_PIN);
HAL_GPIO_TogglePin(LD3_GPIO_PORT, LD3_PIN);
return pdFALSE;
}
const CLI_Command_Definition_t xCommandList[] = {
{
.pcCommand = "cls", /* The command string to type. */
.pcHelpString = "cls:\r\n Clears screen\r\n\r\n",
.pxCommandInterpreter = cmd_clearScreen, /* The function to run. */
.cExpectedNumberOfParameters = 0 /* No parameters are expected. */
},
{
.pcCommand = "add", /* The command string to type. */
.pcHelpString = "add a b:\r\n add two numbers\r\n\r\n",
.pxCommandInterpreter = cmd_add, /* The function to run. */
.cExpectedNumberOfParameters = 2 /* 2 parameters are expected. */
},
{
.pcCommand = "toggle", /* The command string to type. */
.pcHelpString = "toggle :\r\n Toggle All Leds\r\n\r\n",
.pxCommandInterpreter = cmd_toggle, /* The function to run. */
.cExpectedNumberOfParameters = 0 /* No parameters are expected. */
},
{
.pcCommand = NULL /* simply used as delimeter for end of array*/
}
};
void vRegisterCLICommands(void){
//itterate thourgh the list of commands and register them
for (int i = 0; xCommandList[i].pcCommand != NULL; i++)
{
FreeRTOS_CLIRegisterCommand(&xCommandList[i]);
}
}
封装prinf函数,定义处理输入字符串中的换行符、空格以及其他字符的处理函数
/*************************************************************************************************/
void cliWrite(const char *str)
{
printf("%s", str);
// flush stdout
fflush(stdout);
}
/*************************************************************************************************/
void handleNewline(const char *const pcInputString, char *cOutputBuffer, uint8_t *cInputIndex)
{
cliWrite("\r\n");
BaseType_t xMoreDataToFollow;
do
{
xMoreDataToFollow = FreeRTOS_CLIProcessCommand(pcInputString, cOutputBuffer, configCOMMAND_INT_MAX_OUTPUT_SIZE);
cliWrite(cOutputBuffer);
} while (xMoreDataToFollow != pdFALSE);
cliWrite(cli_prompt);
*cInputIndex = 0;
memset((void*)pcInputString, 0x00, MAX_INPUT_LENGTH);
}
/*************************************************************************************************/
void handleBackspace(uint8_t *cInputIndex, char *pcInputString)
{
if (*cInputIndex > 0)
{
(*cInputIndex)--;
pcInputString[*cInputIndex] = '\0';
#if USING_VS_CODE_TERMINAL
cliWrite((char *)backspace);
#elif USING_OTHER_TERMINAL
cliWrite((char *)backspace_tt);
#endif
}
else
{
#if USING_OTHER_TERMINAL
uint8_t right[] = "\x1b\x5b\x43";
cliWrite((char *)right);
#endif
}
}
/*************************************************************************************************/
void handleCharacterInput(uint8_t *cInputIndex, char *pcInputString)
{
if (cRxedChar == '\r')
{
return;
}
else if (cRxedChar == (uint8_t)0x08 || cRxedChar == (uint8_t)0x7F)
{
handleBackspace(cInputIndex, pcInputString);
}
else
{
if (*cInputIndex < MAX_INPUT_LENGTH)
{
pcInputString[*cInputIndex] = cRxedChar;
(*cInputIndex)++;
}
}
}
实现vCommandConsoleTask
函数,实现对命令行解析和执行的功能
void vCommandConsoleTask(void *pvParameters)
{
uint8_t cInputIndex = 0; // simply used to keep track of the index of the input string
uint32_t receivedValue; // used to store the received value from the notification
//printf("vCommandConsole print\r\n");
UNUSED(pvParameters);
HAL_UART_Receive_IT(&huart1,&cli_recv, 1);
vRegisterCLICommands();
for (;;)
{
xTaskNotifyWait(pdFALSE, // Don't clear bits on entry
0, // Clear all bits on exit
&receivedValue, // Receives the notification value
portMAX_DELAY); // Wait indefinitely
//echo recevied char
cRxedChar = receivedValue & 0xFF;
cliWrite((char *)&cRxedChar);
if (cRxedChar == '\r' || cRxedChar == '\n')
{
// user pressed enter, process the command
handleNewline(pcInputString, cOutputBuffer, &cInputIndex);
}
else
{
// user pressed a character add it to the input string
handleCharacterInput(&cInputIndex, pcInputString);
}
}
}
在stm32wbaxx_it
中添加串口接收处理函数
#include "cmsis_os.h"
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
//get ready to receive another char
HAL_UART_Receive_IT(&huart1, &cli_recv, 1);
//send the char to the command line task
xTaskNotifyFromISR(cmdLineTaskHandle, (uint32_t)cli_recv, eSetValueWithOverwrite , NULL);
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_UART_RxCpltCallback could be implemented in the user file
*/
}
在"main.h"中添加用于交换数据的外部变量声明
#include "cmsis_os.h"
extern osThreadId_t cmdLineTaskHandle;
extern uint8_t cli_recv;
完成上述代码添加后编译下载到开发板,程序的运行效果如下。


3、总结
CLI命令行功能提供通过串口控制台调试开发板的功能,作为嵌入式开发的一种辅助手段。