How to implement a nonblocking scanf function?
1. Introduction
This article shows how to implement and redirect the scanf function to the UART, in a nonblocking way. Therefore, was chosen that this function is working through tasks on FreeRTOS™ and so the main code is located at the FreeRTOS™ application file. The target for this article is the STM32G071 Nucleo board but it can be easily tailored to other STM32’s boards.
2. Hardware:
3. Software:
- STM32CubeIDE v1.10.1 or later
- Tera Term or similar
4. Configuration:
The first step is to create a new project on STM32CubeIDE for the board. Create a project to the NUCLEO-G071RB and name it. Opening the *.ioc file of the project, go to the Pinout & Configuration tab > System Core > SYS and check the Serial Wire and System Wake-Up 1 box, also put the Timebase Source as the higher timer allowed.The next step is to configure the User Button (PB13) as an External Interruption since it will be the trigger to call the scanf function when it is necessary. After configuring that, go to System Core > GPIO and put External Interrupt Mode with Falling edge trigger detection on GPIO mode, label it and check the NVIC box “EXTI line 4 to 5 interrupts”.Now it is time to configure the UART where the scanf function will be redirect to. On Connectivity > USART2, select the asynchronous mode, set the Basic Parameter in the Parameter Settings as follows on the image below and check the NVIC box for the global interrupt of USART2.Finally, we need to configure the FreeRTOS parameters. On the Middleware tab > FreeRTOS™, choose the CMSIS_V1 interface as mode, go to Config parameters and change the following parameters:
- MINIMAL_STACK_SIZE = 255 Words
- MAX_TASK_NAME_LEN = 255
- On Memory management settings:
- TOTAL_HEAP_SIZE = 12000 Bytes
- Memory Management scheme = heap_4
This application consists of two tasks: one that contains the scanf function and the other one controls its call. The controller one (MainTask) will have higher priority than the controlled one (ScanfTask). On Tasks and Queues, add the tasks and configure them as the figure below:We will also create a queue to receive and store the input data. On the same tab, add a new queue and configure it as follows:At last, there will be used two semaphores: one to enable the execution of the scanf function task and the other to enable the return value of the queue by printing the input data received so far. On Timers and Semaphores tab, add two semaphores and name them.Now, the last pending configuration to generate the code is to generate the peripheral initialization with the ‘.c/.h’ files separated. Going to the Project Manager section, on the Code Generator Tab, check the first box of the Generated files region.With this last step, all the necessary configuration is done. Now it is time to code!
5. Code Development:
All the functionality of the scanf function is possible thanks to the priority relation between the two declared tasks. The image below shows the application diagram and relationship between tasks.Basically, the MainTask (“TASK1”) will control the execution of the ScanfTask (“TASK2”) using the CallScanfTask semaphore (“BUTTON_SEMAPHORE”) that is released when the user button is pressed. When the button is pressed, there is the possibility to type anything through UART, which will be stored in the queue created. Finally, the final string contained in the queue is released and printed when the enter tab is pressed, which release the ReleaseQueue semaphore (“SCANF_SEMAPHORE”), that is located inside the ScanfTask (“TASK2”).Here is the final code implementation:
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
uint8_t ch[1];
/* USER CODE END Variables */
/* USER CODE BEGIN Header_StartMainTask */
/**
* @brief Function implementing the mainTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartMainTask */
void StartMainTask(void const * argument)
{
/* USER CODE BEGIN StartMainTask */
printf("Press the user button to write something\r\n");
osSemaphoreWait(CallScanfTaskHandle, 0xFFFF);
/* Infinite loop */
for(;;)
{
osSemaphoreWait(CallScanfTaskHandle, 0xFFFF);
printf("\r\nWrite something:\r\n");
HAL_UART_Receive_IT(&huart2, ch, 1);
osDelay(1);
}
/* USER CODE END StartMainTask */
}
/* USER CODE BEGIN Header_StartScanfTask */
/**
* @brief Function implementing the scanfT thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartScanfTask */
void StartScanfTask(void const * argument)
{
/* USER CODE BEGIN StartScanfTask */
uint32_t QueueSize = 0;
osEvent QueueData;
osSemaphoreWait(ReleaseQueueHandle, 0xFFFF);
/* Infinite loop */
for(;;)
{
osSemaphoreWait(ReleaseQueueHandle, 0xFFFF);
QueueSize = osMessageAvailableSpace(QueueHandle);
printf ("\r\nYou typed:\r\n");
for(uint8_t i = 0; i<=(255-QueueSize); i++)
{
QueueData = osMessageGet(QueueHandle, 100);
HAL_UART_Transmit(&huart2, (uint8_t*)&QueueData.value.v, 1, 4000);
}
printf ("\r\nPress again for a new scanf cycle\r\n");
}
/* USER CODE END StartScanfTask */
}
/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
void __io_putchar(int ch)
{
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF);
}
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{
osSemaphoreRelease(CallScanfTaskHandle);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (ch[0] == '\r' )
{
osSemaphoreRelease(ReleaseQueueHandle);
}
else
{
osMessagePut(QueueHandle, ch[0], 200);
HAL_UART_Receive_IT(&huart2, ch, 1);
}
}
/* USER CODE END Application */
6. Results
Here is the result at Tera Term console terminal:Hope you enjoyed the article!