Skip to main content

UART Data Communication in STM32Cube Framework

Sometimes implementation of UART communication is asymmetric. In general, Rx tasks are time critical and total size of the data is unknown, thus it is best to handle the task in an interrupt service routine where individual incoming bytes are checked without delay. While Tx tasks can be implemented rather relaxed manner. For example, you can use a blocking call inside the main loop.

In this sense, UART HAL functions provided by STM32Cube framework is useful for Tx but not very much so for Rx task. Thus you may have to write your own UART interrupt handler using LL drivers while still using HAL UART Tx functions in Tx task.

Use STM32Cube to generate all the chores of setting the UART module except the interrupt part. LL interrupt is activated after the UART port is initialized using LL functions: Source file
  414 void SerialComm_Init()
  415 {
  416     LL_USART_EnableIT_RXNE(huart1.Instance);
  417 }
Then write the interrupt handler to call Rx routine: Source 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 STM32Cube convention, this interrupt handler is located in the interrupt handler file (stm32xxx_it.c). In actual Rx routine, each incoming byte is checked by the packet decoder (SerialComm_Decoder) and loaded into one of the two pingpong buffers when valid packet is received. Source file
  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 }
At this point you need another task that checks this pingpong buffer and generates events when new packet is loaded. This task runs in regular interval, whose frequency should be higher than packet rate that the protocol defines. Source file
  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 }
Finally, the main event handler takes care of the events. Source file
  166             case EVT_UART_RXPKT:
  167 
  168                 if(event[2] == SYS_SRESET)
  169                 {
  170                     HAL_NVIC_SystemReset();
  171                 }
  172                 else if(event[2] == SYS_WRESET)
  173                 {
  174                     HAL_NVIC_SystemReset();
  175                 }
  176                 else if(event[2] == DIO_SETVAL)
  177                 {
  178                     if(event[3] == 0x01)
  179                     {
  180                         HAL_GPIO_WritePin(TEST_LED_GPIO_Port, TEST_LED_Pin,
  181                                 GPIO_PIN_SET);
  182 
  183                         SerialComm_SendByte(PKT_ACK);
  184                     }
  185                 }
For the Tx task, you can use either LL function or HAL function (blocking call). Source file
  166             case EVT_UART_RXPKT:
  167 
  168                 if(event[2] == SYS_SRESET)
  169                 {
  170                     HAL_NVIC_SystemReset();
  171                 }
  172                 else if(event[2] == SYS_WRESET)
  173                 {
  174                     HAL_NVIC_SystemReset();
  175                 }
  176                 else if(event[2] == DIO_SETVAL)
  177                 {
  178                     if(event[3] == 0x01)
  179                     {
  180                         HAL_GPIO_WritePin(TEST_LED_GPIO_Port, TEST_LED_Pin,
  181                                 GPIO_PIN_SET);
  182 
  183                         SerialComm_SendByte(PKT_ACK);
  184                     }
  185                 }
<< source code >>

Comments

Popular posts from this blog

A Simple STM32 Example Project

Most of the embedded projects share certain initial steps. You need to confirm the clock settings before doing anything, then you want to have debug connection via a UART channel since it is cheap  but still it can provide useful information for debugging. Let us start with CubeMX. You select a device/board of your choice, set up the pinouts for one GPIO output and one UART port. Configure the clock if necessary then create a project. Clock Checking using SysTick The sanity of the clock setting can be done by checking the SysTick interval. All Cortex-M series core have SysTick timer by default, which should fire at 1msec interval while the MCU is active. In the STM32Cube, the SysTick is initialized by HAL_Init() call, which in turn calls SysTick_Config() in CMSIS. Once the SysTick is initialized, it generates 1 msec interrupt and this interrupt is handled by SysTick_Handler() according to the Cube framework. Source file 1 /** 2 * @brief This function handles

STM32 USB MSD with SD Card

Build a low level driver for SD card, then the glue logic for FatFs and USB MSD is pretty much the same as Flash memory case posed before. In case of SD card, sector size is 512 in most of the cases. Thus the memory requirement is much relaxed. You can even allocate a file buffer that is bigger than the sector size. FatFs site has a  dedicated page for MMC/SDC, on which you can find fairly detailed explanation about how to interface MMC/SDC via SPI bus. Implementation should be straightforward until you encounter with cheap SD cards that do not behave very well. In such cases, you either have to protect your code with redundancy or just stick with quality devices. If you choose SanDisk or Kingston brand, you will be safe. ADATA on the other hand, frequently generates timeout error at first try. Most of the SD card sockets have a pin to detect the presence of the card. This pin is usually connected to GND pin or some other pin. You can use this to generate interrupt whenever a ca

STM32 USB MSC Device with FLASH memory

USB Mass Storage Class framework implements bulk-only-transfer (BOT) with SCSI protocol. USB packets from the host eventually converted SCSI transport commands by the middleware, in which data is exchanged (read / write) in the unit of logical block, typically 512 bytes. This SCSI commands works well with SD card system where a dedicated controller does the job of managing the actual memory elements. If you want to use a FLASH chip as a memory unit instead, you need to handle read / write operation directly. Fortunately, most of flash memory support 4KB block erase. This makes the 4096 bytes as a natural choice for the size of the logical block in the file usbd_storage_if.c. In this case, 8Mbit Flash memory was used. During initial enumeration, this information is registered to the host. The middleware maintains one logical block size of buffer and handles USB transaction where each payload is only 64 bytes. It then calls SCSI requests to store / retrieve data to / from physical