How to Receive Data in Blocking & Interrupt mode
This is the 3rd tutorial in the series on the UART peripheral of STM32 Microcontrollers. In this series we will cover different ways of transmitting and receiving data over the UART protocol. We will also see different UART modes available in the STM32 microcontrollers and how to use them.
In this tutorial, we will see how to receive the data using the blocking mode and in the interrupt mode and what are the advantages and disadvantages for the both. We will also see how to receive the data of unknown length.
The connection will be the same as what we used in the previous tutorial, so I will skip that part.
Receiving in Blocking Mode
In Blocking mode, the CPU is blocked and it waits for the required number of bytes to arrive or the timeout, whatever happens first. This prevents the CPU from running other tasks until all the required data have arrived or the timeout happened.
Below is the code showing how to receive 5 bytes in the blocking mode.
while (1)
{
HAL_UART_Receive(&huart2, RxData, 5, 1000);
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(1000);
}
HAL_UART_Receive function is used to received the 5 bytes of data in the blocking mode. The timeout is set to 1 second.
If the 5 bytes are received before 1 second, the function will again exit and the LED will blink. If there is no data incoming, the CPU will still be blocked for 1 second. This will affect the LED blinking rate and the it will blink every 2 seconds.
Also if the CPU is waiting for the delay (HAL_Delay(1000)) and the data arrived in that period, the data will be simply lost.
So a lot of things needs to go right in order to receive the data in the blocking mode. The data must arrive when the control is inside the receive function. We also need to know the data size in advance, so that we can program the MCU to receive number of bytes.
Receiving in Interrupt Mode
The interrupt mode helps us receive the data in the background. The CPU can process other tasks as usual and once the required number of bytes has been received, the interrupt will trigger.
Below is the code showing how to receive 5 bytes of data in the interrupt mode.
int main ()
{
......
HAL_UART_Receive_IT(&huart2, RxData, 5);
while (1)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(1000);
}
}
HAL_UART_Receive_IT is used to receive 5 bytes of data in the interrupt mode. The data will be received in the background and the CPU will continue to blink the LED every 1 second.
Once all the 5 bytes have been received, an interrupt will trigger and the RX complete callback will be called. We can process the data inside the callback function.
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// do something with the data
HAL_UART_Receive_IT(&huart2, RxData, 5);
}
In the code above, I am not performing any operation with the data. The interrupt is disabled after each trigger, so we need to call the Receive_IT function again at the end of the callback.
Below is the image showing the data received in the RxData buffer. Also the LED is blinking every 1 second indicating that the rest of the processes work as usual.
Receiving unknown length
The interrupt method shown above can receive the data in the background but we still need to provide how many data bytes we are expecting. We can modify the code above to receive the data of unknown length.
Basically we will receive 1 byte at a time and store it in a buffer. There will be a terminating character in the data which will indicate that all the data has been transmitted. Once we have received this character, we will process the received data. Below is the code shows the implementaion.
uint8_t FinalData[20];
uint8_t RxData[20];
uint8_t temp[2];
int indx = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
memcpy(RxData+indx, temp, 1);
if (++indx >= 20) indx = 0;
HAL_UART_Receive_IT(&huart2, temp, 1);
}
int main()
{
....
HAL_UART_Receive_IT(&huart2, temp, 1);
while (1)
{
if (temp[0] == '\n')
{
memcpy (FinalData, RxData, indx);
indx = 0;
}
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(1000);
}
The HAL_UART_Receive_IT is set to receive only 1 byte. The interrupt will trigger after receiving each data byte and hence the callback will be called.
Inside the callback we will store the data received in the RxData buffer. The index variable keeps track of how many bytes have been received and it also helps in storing the new data at a new position in the buffer.
We will also monitor the the incoming byte for the terminating character (‘\n’). Once we received the terminating character, this means that the sender has finished sending the data, so we can start processing it. Here I am just copying the data to the main buffer.
This method works well for receiving data of any length. Below is the image showing the data received in the RxData and main data buffer.
Although the method works well for small amount of data, but this could fail when you are receiving large data at a very high rate. The interrupt is being called for each byte and it do take a considerable amount of time to serve the ISR and call the callback. There is a chance that we might loose some data during that time.
We will use a more robust method to receive the data of unknown length in the upcoming tutorials.