UART Data Communication

In UART communication, TX process and RX process are usually handled differently. When transmitting data, you first construct a full packet of data. Then you send it rather relaxed manner. You can let the DMA handle the transfer, or you can use blocking call outside of any time critical function. Most of SDK provide such functions by default.

When receiving data however, you do not know the size or the validity of the packet beforehand. So you have to check each byte and verify it against the protocol as it arrives. Thus it is best to handle the RX task in an UART interrupt callback function instead of using functions provided by SDK.

In STM32Cube framework, you first activate UART RX interrupt using LL.

  412 /** Initialize SerialComm
  413  */
  414 void SerialComm_Init()
  415 {
  416     LL_USART_EnableIT_RXNE(huart1.Instance);
  417 }

Then write an interrupt handler for UART RX interrupt. In STM32Cube convention, a skeleton code for this interrupt handler is populated in stm32xxxx_it.c file.

  126 void USART1_IRQHandler(void)
  127 {
  128     if(LL_USART_IsActiveFlag_RXNE(USART1) && LL_USART_IsEnabledIT_RXNE(USART1))
  129     {
  130         SerialComm_RxRoutine();
  131     }
  132 }

In acutual RX routine, each incoming byte is checked by the packet decoder, SerialComm_Decoder and loaded into one of the two pingpong buffers whenever a valid packet is received.

  419 void SerialComm_RxRoutine()
  420 {
  421     pkt_status status;
  422 
  423     status = SerialComm_Decoder(LL_USART_ReceiveData8(huart1.Instance),
  424             UartIsrBuf);
  425 
  426     if(status == PKT_RECEIVED)
  427     {
  428         // switch ping pong buffer
  429         if(UartIsrBuf == UartRxBuf1)
  430         {
  431             UartIsrBuf = UartRxBuf2;
  432             UartRxBuf = UartRxBuf1;
  433         }
  434         else
  435         {
  436             UartIsrBuf = UartRxBuf1;
  437             UartRxBuf = UartRxBuf2;
  438         }
  439         // raise flag
  440         bPktReceived = true;
  441     }
  442 }

Then you need another task that checks the contents of this pingpong buffer and generates events when new packet is loaded.

  386 void UartRxTask()
  387 {
  388     int i;
  389     uint8_t event[EVT_QWIDTH];
  390 
  391     // packet received
  392     if(bPktReceived)
  393     {
  394         // event id
  395         event[0] = EVT_UART_RXPKT;
  396         // event data size
  397         event[1] = UartRxBuf[1];
  398 
  399         // copy the payload
  400         for(i = 0; i< UartRxBuf[1]; i++)
  401         {
  402             event[2+i] = UartRxBuf[2+i];
  403         }
  404     
  405         // register the event
  406         Evt_EnQueue(event);
  407         // clear the flag
  408         bPktReceived = 0;
  409     }
  410 }

This task runs as a software timer routine in regular interval, whose frequency should be higher than packet rate that the protocol defines.

  107     // start a timer routine: 10msec period, perpetual
  108     result = UsrTimer_Set(10, 0, UartRxTask);

This seemingly redundant UartRxTask is necessary to avoid race condition since EvtQueue is designed to be used by software timer callback for posting event. If Evt_EnQueue function is called from SerialComm_RxRoutine, a callback of the UART RX interrupt directly it can be disturbed by SysTick interrupt whose priority is higher.