This document is divided into two parts: the first part is introducing the implementation of the UART console, which requires only two functions to complete the UART console printing function. The second part is the implementation of porting FinSH components, to implement the input command debugging system in the console, and its implementation is based on the UART console implementation of the first part, only need to add FinSH component source code and docking a system function.
By adding the UART console printing feature to the RT-Thread Nano, you can print information in your code using the print function rt_kprintf() provided by RT-Thread to get custom print information, easy to locate code bugs and get to know the system operating status, and more. Implementing console printing (which requires to make sure that the RT_USING_CONSOLE
macro definition has been enabled in rtconfig.h), the basic required hardware initialization and the functions for docking a system output character will be introduced in this article.
Using a serial port to dock the printer of the console, you'll first need to initialize the serial ports, such as pins, baud rates, and so on. uart_init () needs to be call in the rt_hw_board_init()
function in board.c.
/* Implementation 1: Initialize the serial port */
static int uart_init(void);
Sample Code: The following is the sample code of a HAL library-based STM32F103 serial driver that completes of adding the console:
static UART_HandleTypeDef UartHandle;
static int uart_init(void)
{
/* Initialize serial parameters such as baud rate, stop bit, and so on */
UartHandle.Instance = USART1;
UartHandle.Init.BaudRate = 115200;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;
UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
/* Initialize serial pins, etc */
if (HAL_UART_Init(&UartHandle) != HAL_OK)
{
while(1);
}
return 0;
}
INIT_BOARD_EXPORT(uart_init);
/* board.c */
void rt_hw_board_init(void)
{
....
uart_init(); // Call serial port initialization function in rt_hw_board_init
....
}
Implementing the finsh component outputs a character, that is output the uart character in this function:
/* Implementation 2: Output a character, system function, function name can not be changed */
void rt_hw_console_output(const char *str);
Note
Prints already in the RT-Thread system all end with \n
, not \r\n
, so when the character is output, you need to output \r
before output \n
, complete the Enter and Newline, otherwise the information printed by the system will only be Newline.
Example code: The following is an rt_hw_console_output() function based on the STM32F103 HAL serial drive docking, implementing console character output.
void rt_hw_console_output(const char *str)
{
rt_size_t i = 0, size = 0;
char a = '\r';
__HAL_UNLOCK(&UartHandle);
size = rt_strlen(str);
for (i = 0; i < size; i++)
{
if (*(str + i) == '\n')
{
HAL_UART_Transmit(&UartHandle, (uint8_t *)&a, 1, 1);
}
HAL_UART_Transmit(&UartHandle, (uint8_t *)(str + i), 1, 1);
}
}
Write code in your app code that includes rt_kprintf () prints, compile and download, and open the serial assistant for validation. The following image is an example effect of looping Hello RT-Thread
every 1 second in the main() function:
RT-Thread FinSH is RT-Thread command-line component (shell) that provides a set of operational interfaces for users to invoke on the command line, primarily for debugging or viewing system information. It can communicate with a PC using serial ports/ Ethernet / USB, etc., and the effect of using the FinSH component basic commands is shown below:
In this article, we communicate with the PC by using serial UART as an input and output port for FinSH, describing how to implement FinSH shell functionality on RT-Thread Nano.
To add a FinSH component to the RT-Thread Nano, the steps to implement FinSH functionality are as follows:
Click Manage Run-Environment:
Check in shell,which automatically add sources code of FinSH components to the project:
Open a cube project, click Additional Software, check RealThread in Pack Vendor to quickly locate the RT-Thread package, and then tick the shell in the RT-Thread package to add the source code of the FinSH component to the project.
Other IDEs add FinSH source code, which requires manual addition of FinSH source code and header file paths into the project, as described in the case of IAR IDE.
finsh
folder under the rtthread-nano/components folder , as shown in the figure:finsh
group and add all the . c File under the finsh
folders to the project, as shown below;finsh
folder (click Project -> Options...
, enter the prompt box, as shown below);#define RT_USING_FINSH
in rtconfig.h so that FinSH takes effect, as shown below.To implement FinSH component functionality: you can print as well as input commands for debugging, the console has already implemented printing, and now you need to dock the console input function in board.c for character input:
/* Implementation 3:FinSH gets a character, a system function, and the function name cannot be changed */
char rt_hw_console_getchar(void);
Example code: The following is an function of rt_hw_console_getchar() based on the STM32F103 HAL serial-driven docking, complete docking the FinSH component, where the characters are queried.
char rt_hw_console_getchar(void)
{
int ch = -1;
if (__HAL_UART_GET_FLAG(&UartHandle, UART_FLAG_RXNE) != RESET)
{
ch = UartHandle.Instance->DR & 0xff;
}
else
{
if(__HAL_UART_GET_FLAG(&UartHandle, UART_FLAG_ORE) != RESET)
{
__HAL_UART_CLEAR_OREFLAG(&UartHandle);
}
rt_thread_mdelay(10);
}
return ch;
}
Compile and download the code, open the serial assistant, you can print and input help command in the serial assistant, enter to see the commands supported by the system:
If it does not run successfully, check that if the docking function is implemented correctly.
The following is based on the STM32F103 HAL serial driver, which implements console output with FinSH Shell, where the acquired characters are interrupted. The principle is that there is an interrupt when uart receives the data, deposits the data in the ringbuffer buffer during the interrupt, then releases the semader, the tshell thread receives the semader, and then reads the data that exists in the ringbuffer.
/* Part 1: ringbuffer implementation part */
#include <rtthread.h>
#include <string.h>
#define rt_ringbuffer_space_len(rb) ((rb)->buffer_size - rt_ringbuffer_data_len(rb))
struct rt_ringbuffer
{
rt_uint8_t *buffer_ptr;
rt_uint16_t read_mirror : 1;
rt_uint16_t read_index : 15;
rt_uint16_t write_mirror : 1;
rt_uint16_t write_index : 15;
rt_int16_t buffer_size;
};
enum rt_ringbuffer_state
{
RT_RINGBUFFER_EMPTY,
RT_RINGBUFFER_FULL,
/* half full is neither full nor empty */
RT_RINGBUFFER_HALFFULL,
};
rt_inline enum rt_ringbuffer_state rt_ringbuffer_status(struct rt_ringbuffer *rb)
{
if (rb->read_index == rb->write_index)
{
if (rb->read_mirror == rb->write_mirror)
return RT_RINGBUFFER_EMPTY;
else
return RT_RINGBUFFER_FULL;
}
return RT_RINGBUFFER_HALFFULL;
}
/**
* get the size of data in rb
*/
rt_size_t rt_ringbuffer_data_len(struct rt_ringbuffer *rb)
{
switch (rt_ringbuffer_status(rb))
{
case RT_RINGBUFFER_EMPTY:
return 0;
case RT_RINGBUFFER_FULL:
return rb->buffer_size;
case RT_RINGBUFFER_HALFFULL:
default:
if (rb->write_index > rb->read_index)
return rb->write_index - rb->read_index;
else
return rb->buffer_size - (rb->read_index - rb->write_index);
};
}
void rt_ringbuffer_init(struct rt_ringbuffer *rb,
rt_uint8_t *pool,
rt_int16_t size)
{
RT_ASSERT(rb != RT_NULL);
RT_ASSERT(size > 0);
/* initialize read and write index */
rb->read_mirror = rb->read_index = 0;
rb->write_mirror = rb->write_index = 0;
/* set buffer pool and size */
rb->buffer_ptr = pool;
rb->buffer_size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);
}
/**
* put a character into ring buffer
*/
rt_size_t rt_ringbuffer_putchar(struct rt_ringbuffer *rb, const rt_uint8_t ch)
{
RT_ASSERT(rb != RT_NULL);
/* whether has enough space */
if (!rt_ringbuffer_space_len(rb))
return 0;
rb->buffer_ptr[rb->write_index] = ch;
/* flip mirror */
if (rb->write_index == rb->buffer_size-1)
{
rb->write_mirror = ~rb->write_mirror;
rb->write_index = 0;
}
else
{
rb->write_index++;
}
return 1;
}
/**
* get a character from a ringbuffer
*/
rt_size_t rt_ringbuffer_getchar(struct rt_ringbuffer *rb, rt_uint8_t *ch)
{
RT_ASSERT(rb != RT_NULL);
/* ringbuffer is empty */
if (!rt_ringbuffer_data_len(rb))
return 0;
/* put character */
*ch = rb->buffer_ptr[rb->read_index];
if (rb->read_index == rb->buffer_size-1)
{
rb->read_mirror = ~rb->read_mirror;
rb->read_index = 0;
}
else
{
rb->read_index++;
}
return 1;
}
/* Part 2:finsh porting*/
#define UART_RX_BUF_LEN 16
rt_uint8_t uart_rx_buf[UART_RX_BUF_LEN] = {0};
struct rt_ringbuffer uart_rxcb; /* Define ringbuffer cb */
static UART_HandleTypeDef UartHandle;
static struct rt_semaphore shell_rx_sem; /* Define a static senum */
/* Initialize the serial port, interrupt mode */
static int uart_init(void)
{
/* Initialize the serial port to receive ringbuffer */
rt_ringbuffer_init(&uart_rxcb, uart_rx_buf, UART_RX_BUF_LEN);
/* Initialize the semaphore of the serial port receives data */
rt_sem_init(&(shell_rx_sem), "shell_rx", 0, 0);
/* Initialize serial parameters such as baud rate, stop bit, and so on */
UartHandle.Instance = USART2;
UartHandle.Init.BaudRate = 115200;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;
UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
/* Initialize serial pins, etc */
if (HAL_UART_Init(&UartHandle) != HAL_OK)
{
while (1);
}
/* Interrupt Configuration */
__HAL_UART_ENABLE_IT(&UartHandle, UART_IT_RXNE);
HAL_NVIC_EnableIRQ(USART2_IRQn);
HAL_NVIC_SetPriority(USART2_IRQn, 3, 3);
return 0;
}
INIT_BOARD_EXPORT(uart_init);
/* Port the console, implement the console output, docking rt_hw_console_output */
void rt_hw_console_output(const char *str)
{
rt_size_t i = 0, size = 0;
char a = '\r';
__HAL_UNLOCK(&UartHandle);
size = rt_strlen(str);
for (i = 0; i < size; i++)
{
if (*(str + i) == '\n')
{
HAL_UART_Transmit(&UartHandle, (uint8_t *)&a, 1, 1);
}
HAL_UART_Transmit(&UartHandle, (uint8_t *)(str + i), 1, 1);
}
}
/* Port FinSH for command-line interaction, add FinSH source code, then dock rt_hw_console_getchar */
/* Interrupt mode */
char rt_hw_console_getchar(void)
{
char ch = 0;
/* Take the data out of ringbuffer */
while (rt_ringbuffer_getchar(&uart_rxcb, (rt_uint8_t *)&ch) != 1)
{
rt_sem_take(&shell_rx_sem, RT_WAITING_FOREVER);
}
return ch;
}
/* uart interrupt */
void USART2_IRQHandler(void)
{
int ch = -1;
rt_base_t level;
/* enter interrupt */
rt_interrupt_enter(); //Be sure to call this pair of functions in the interrupt and enter the interrupt
if ((__HAL_UART_GET_FLAG(&(UartHandle), UART_FLAG_RXNE) != RESET) &&
(__HAL_UART_GET_IT_SOURCE(&(UartHandle), UART_IT_RXNE) != RESET))
{
while (1)
{
ch = -1;
if (__HAL_UART_GET_FLAG(&(UartHandle), UART_FLAG_RXNE) != RESET)
{
ch = UartHandle.Instance->DR & 0xff;
}
if (ch == -1)
{
break;
}
/* Read the data and store it in ringbuffer */
rt_ringbuffer_putchar(&uart_rxcb, ch);
}
rt_sem_release(&shell_rx_sem);
}
/* leave interrupt */
rt_interrupt_leave(); //Be sure to call this pair of functions in the interrupt and leave the interrupt
}
#define USART_TX_Pin GPIO_PIN_2
#define USART_RX_Pin GPIO_PIN_3
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (huart->Instance == USART2)
{
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART2 GPIO Configuration
PA2 ------> USART2_TX
PA3 ------> USART2_RX
*/
GPIO_InitStruct.Pin = USART_TX_Pin | USART_RX_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}