# Porting FreeRTOS + Command Line Interface (FreeRTOS+CLI) to STM32F4
When testing my robot, I need an interface between my PC and the high-level controller of my robot to change the desired robot states, such as resting, standing, trotting, and velocity. In some embed systems, users could change state variables through DEBUG mode of the embed controller; however, for a dynamic system like the robot, this method is impracticable because the breakpoints for debugging the robot stop not only the whole system but the locomotion control-loop. With such a random stop, the locomotion control-loop would be unstable, and then the robot would be out of control. In my robot control systme, I take advantage of FreeRTOS+CLI to change the robot states without stopping the locomotion control-loop. FreeRTOS+CLI creats an independent task, UARTCommandConsoleTask, to process the command line interface (CLI). UARTCommandConsoleTask has lower priority than the locomotion control-loop; The task would be executed after the locomotion control have been done.
## Setting the STM32F4 serial UART for FreeRTOS+CLI
I use a STM32F429 Discovery board as the high-level controller of my robot. To communicate with PC COM port through FreeRTOS+CLI, the STM32F429 chip should have a serail UART module set to 115200 8N1 and none Hardware Control.
```clike=
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
```
Also, the serial COM port in PC needs the same setting.
Serial port setup in Minicom.
```
A - Serial Device : /dev/ttyACM0
B - Lockfile Location : /var/lock
C - Callin Program :
D - Callout Program :
E - Bps/Par/Bits : 115200 8N1
F - Hardware Flow Control : No
```
For less CPU usage in the robot controller, UARTCommandConsoleTask should be executed only after the robot controller has received data from the PC; accordingly, the UART-received interrupt (UART_IT_RXNE) in the robot controller must be set to trigger UARTCommandConsoleTask.
```clike=
HAL_NVIC_SetPriority(USART1_IRQn, 8, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);
```
## Porting FreeRTOS+CLI to STM32F4 for commanding a robot
### Demo files from FreeRTOS
FreeRTOS+CLI provides some demo files for STM32F103: FreeRTOS_CLI.c/.h, Sample-CLI-commands.c, UARTCommandConsole.c, and serial.c/.h.
Sample-CLI-commands.c and UARTCommandConsole.c need some modifications to be fit in with my robot; serial.c/.h, furthermore, must be rewritten with STM32F4 HAL library to be compatible with the STM32F429 Discovery board.
### 1. Define the robot states
The main purpose of FreeRTOS+CLI in my robot is for adjusting robot state variables. The state variables, which describe robot locomotion, are defined in ComdStates.h.
```clike=
typedef struct __ComdStates_TypeDef
{
float vxBaseC_Comd;
float vyBaseC_Comd;
float wyawBaseC_Comd;
float heightB2C;
float rollDiff;
float pitchDiff;
unsigned short int gaitStates;
unsigned short int flagNewComd;
} ComdStates;
```
In main.c, I initiated the ComdStates, robotCommand, as a global variables.
```clike=
ComdStates robotCommand = {
.vxBaseC_Comd = 0,
.vyBaseC_Comd = 0,
.wyawBaseC_Comd = 0,
.heightB2C = 0,
.rollDiff = 0,
.pitchDiff = 0,
.gaitStates = 0,
.flagNewComd = 0
};
```
### 2. Modify the demo files
I modified Sample-CLI-commands.c and UARTCommandConsole.c to add required commands for the robot.
#### In UARTCommandConsole.c
UARTCommandConsoleTask serves as the middle layer between command console and UART driver. To use the UART driver for UARTCommandConsoleTask, I set an external linkage to the UART handle of the robot controller. The command console is implemented in Sample-CLI-commands.c.
```clike=
extern UART_HandleTypeDef huart1;
```
#### In Sample-CLI-commands.c
I added some CLI commands to adjust the robot state variables. The commands are implemented according to [steps](https://www.freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_CLI/FreeRTOS_Plus_Command_Line_Interface.html) provided by FreeRTOS.
*Step1*: Provide a function that implements the command behaviour
I implemented three functions for three kinds of command behaviour: (i)increasing/decreasing forward velocity, (ii)increasing/decreasing height of body, and (iii)changing robot gait.
```clike=
static BaseType_t prv_vxBaseC_Command( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString );
static BaseType_t prv_heightB2C( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString );
static BaseType_t prv_gState( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString );
static BaseType_t prv_vxBaseC_Command( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString )
{
const char *pcParameter;
BaseType_t xParameterStringLength;
/* Remove compile time warnings about unused parameters, and check the
write buffer is not NULL. NOTE - for simplicity, this example assumes the
write buffer length is adequate, so does not check for buffer overflows. */
( void ) pcCommandString;
( void ) xWriteBufferLen;
configASSERT( pcWriteBuffer );
/* Obtain the parameter string. */
pcParameter = FreeRTOS_CLIGetParameter
(
pcCommandString, /* The command string itself. */
1, /* Return the next parameter. */
&xParameterStringLength /* Store the parameter string length. */
);
/* Sanity check something was returned. */
configASSERT( pcParameter );
if( strncmp( pcParameter, "+", strlen("+") ) == 0 )
{
robotCommand.vxBaseC_Comd += 0.1f;
robotCommand.flagNewComd = 1;
sprintf( pcWriteBuffer, "Vx+ => %d cm/s\r\n", ( int )( 100*robotCommand.vxBaseC_Comd ) );
}
else if( strncmp( pcParameter, "-", strlen("-") ) == 0 )
{
robotCommand.vxBaseC_Comd -= 0.1f;
robotCommand.flagNewComd = 1;
sprintf( pcWriteBuffer, "Vx- => %d cm/s\r\n", ( int )( 100*robotCommand.vxBaseC_Comd ) );
}
else
{
sprintf( pcWriteBuffer, "Valid parameters are '+' and '-'.\r\n" );
}
/* This is the only one parameter then there are no more
strings to return after this one, so return pdFALSE. */
return pdFALSE;
}
static BaseType_t prv_heightB2C( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString )
{
const char *pcParameter;
BaseType_t xParameterStringLength;
/* Remove compile time warnings about unused parameters, and check the
write buffer is not NULL. NOTE - for simplicity, this example assumes the
write buffer length is adequate, so does not check for buffer overflows. */
( void ) pcCommandString;
( void ) xWriteBufferLen;
configASSERT( pcWriteBuffer );
/* Obtain the parameter string. */
pcParameter = FreeRTOS_CLIGetParameter
(
pcCommandString, /* The command string itself. */
1, /* Return the next parameter. */
&xParameterStringLength /* Store the parameter string length. */
);
/* Sanity check something was returned. */
configASSERT( pcParameter );
if( strncmp( pcParameter, "+", strlen("+") ) == 0 )
{
robotCommand.heightB2C += 0.05f;
robotCommand.flagNewComd = 1;
sprintf( pcWriteBuffer, "H+ => %d cm\r\n", ( int )( 100*robotCommand.heightB2C ) );
}
else if( strncmp( pcParameter, "-", strlen("-") ) == 0 )
{
robotCommand.heightB2C -= 0.05f;
robotCommand.flagNewComd = 1;
sprintf( pcWriteBuffer, "H- => %d cm\r\n", ( int )( 100*robotCommand.heightB2C ) );
}
else
{
sprintf( pcWriteBuffer, "Valid parameters are '+' and '-'.\r\n" );
}
/* This is the only one parameter then there are no more
strings to return after this one, so return pdFALSE. */
return pdFALSE;
}
static BaseType_t prv_gState( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString )
{
const char *pcParameter;
BaseType_t xParameterStringLength;
/* Remove compile time warnings about unused parameters, and check the
write buffer is not NULL. NOTE - for simplicity, this example assumes the
write buffer length is adequate, so does not check for buffer overflows. */
( void ) pcCommandString;
( void ) xWriteBufferLen;
configASSERT( pcWriteBuffer );
/* Obtain the parameter string. */
pcParameter = FreeRTOS_CLIGetParameter
(
pcCommandString, /* The command string itself. */
1, /* Return the next parameter. */
&xParameterStringLength /* Store the parameter string length. */
);
/* Sanity check something was returned. */
configASSERT( pcParameter );
if( strncmp( pcParameter, "Rst", strlen("Rst") ) == 0 )
{
robotCommand.gaitStates = 0;
robotCommand.flagNewComd = 1;
sprintf( pcWriteBuffer, "GaitState to Rest\r\n" );
}
else if( strncmp( pcParameter, "Std", strlen("Std") ) == 0 )
{
robotCommand.gaitStates = 1;
robotCommand.flagNewComd = 1;
sprintf( pcWriteBuffer, "GaitState to Stand\r\n" );
}
else if ( strncmp( pcParameter, "Tor", strlen("Tor") ) == 0 )
{
robotCommand.gaitStates = 2;
robotCommand.flagNewComd = 1;
sprintf( pcWriteBuffer, "GaitState to Tort\r\n" );
}
else
{
sprintf( pcWriteBuffer, "Valid parameters are 'Rst', 'Std', and 'Tor'.\r\n" );
}
/* This is the only one parameter then there are no more
strings to return after this one, so return pdFALSE. */
return pdFALSE;
}
```
*Step2*: Map the command to the function that implements its behaviour
```clike=
static const CLI_Command_Definition_t xVX =
{
"VX",
"\r\nVX [+|-]:\r\n VelocityX_BaseC +/- 10 cm/s\r\n",
prv_vxBaseC_Command, /* The function to run. */
1 /* One parameter is expected, which can take any value. */
};
static const CLI_Command_Definition_t xH =
{
"H",
"\r\nH [+|-]:\r\n Height_of_CoM_B2C +/- 5 cm\r\n",
prv_heightB2C, /* The function to run. */
1 /* TOne parameter is expected, which can take any value. */
};
static const CLI_Command_Definition_t xgState =
{
"gState",
"\r\ngState [Rst|Std|Tor]:\r\n GaitState : Rest/Stand/Torting\r\n",
prv_gState, /* The function to run. */
1 /* One parameter is expected, which can take any value. */
};
```
*Step3*: Register the commands with FreeRTOS+CLI
```clike=
void vRegisterSampleCLICommands( void )
{
/* Register all the command line commands defined immediately above. */
FreeRTOS_CLIRegisterCommand( &xVX );
FreeRTOS_CLIRegisterCommand( &xH );
FreeRTOS_CLIRegisterCommand( &xgState );
}
```
#### In main.c
I added two functions to creat UARTCommandConsoleTask and register the commands.
```clike=
vUARTCommandConsoleStart( 1000, osPriorityBelowNormal - 5 );
vRegisterSampleCLICommands();
```
### 3. Rewrite the UART interrupt service routine (ISR) in the serial port driver (serial.c/.h) provided by FreeRTOS+CLI.
Since the FreeRTOS+CLI demo files are for STM32F103, I have to rewrite some parts of serial.c to port the FreeRTOS+CLI demo to STM32F429. In serial.c, transmitting and receiving of UART data is implemented in a UART ISR; therefore, I rewrited the UART ISR, vUARTInterruptHandler, to make the FreeRTOS+CLI demo compatible with STM32F429. The rewrited vUARTInterruptHandler is based on HAL_UART_IRQHandler in the STM32F4 HAL library. HAL_UART_IRQHandler is a full-function ISR for UART communication, including run-time failure detection and corresponding callback functions; the callback functions, however, slow down the ISR. Hence, I removed the callback functions in vUARTInterruptHandler.
```clike=
#include "stm32f4xx_hal.h"
void vUARTInterruptHandler( UART_HandleTypeDef *huart )
{
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
char cChar;
uint32_t isrflags = READ_REG(huart->Instance->SR);
uint32_t cr1its = READ_REG(huart->Instance->CR1);
uint32_t cr3its = READ_REG(huart->Instance->CR3);
uint32_t errorflags = 0x00U;
/* If no error occurs */
errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
if (errorflags == RESET)
{
/* UART in mode Receiver -------------------------------------------------*/
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
HAL_UART_Receive( huart, (uint8_t *)&cChar, 1, 0xFFFF );
xQueueSendFromISR( xRxedChars, &cChar, &xHigherPriorityTaskWoken );
return;
}
}
/* If some errors occur */
if ((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)))
{
/* UART parity error interrupt occurred ----------------------------------*/
if (((isrflags & USART_SR_PE) != RESET) && ((cr1its & USART_CR1_PEIE) != RESET))
{
huart->ErrorCode |= HAL_UART_ERROR_PE;
}
/* UART noise error interrupt occurred -----------------------------------*/
if (((isrflags & USART_SR_NE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
{
huart->ErrorCode |= HAL_UART_ERROR_NE;
}
/* UART frame error interrupt occurred -----------------------------------*/
if (((isrflags & USART_SR_FE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
{
huart->ErrorCode |= HAL_UART_ERROR_FE;
}
/* UART Over-Run interrupt occurred --------------------------------------*/
if (((isrflags & USART_SR_ORE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
{
huart->ErrorCode |= HAL_UART_ERROR_ORE;
}
return;
} /* End if some error occurs */
/* UART in mode Transmitter ------------------------------------------------*/
if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
/* The interrupt was caused by the THR becoming empty. Are there any
more characters to transmit? */
if( xQueueReceiveFromISR( xCharsForTx, &cChar, &xHigherPriorityTaskWoken ) == pdTRUE )
{
/* A character was retrieved from the queue so can be sent to the
THR now. */
HAL_UART_Transmit( huart, (uint8_t *)&cChar, 1, 0xFFFF );
}
else
{
__HAL_UART_DISABLE_IT(huart, UART_IT_TXE);
}
return;
}
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
}
```
In stm32f4xx_it.c
I put the rewrited vUARTInterruptHandler into USART1_IRQHandler to replace the original HAL_UART_IRQHandler offered by the STM32F4 HAL library.
```clike=
void USART1_IRQHandler(void)
{
vUARTInterruptHandler(&huart1);
}
```
## Testing the CLI
The input commands for CLI testing and the corresponding results are shown below.
Command: gState Std
Command: gState Rst
Command: VX +
Command: VX +
Command: VX -

