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

Data Plotting in wxPython

wx.lib.plot wxPython has its own plotting library, which provides simple way of drawing large number of data on a canvas. It is convenient to use and it is fast. However you have only one axis per canvas and you can plot 2D graphs only. To plot a line graph like above, you create line objects using numpy Source file 214 x = np . linspace ( 0 , 10 , 500 ) 215 y = np . sin ( x ) 216 217 # create lines 218 line1 = wxplot . PolyLine ( list ( zip ( x , np . sin ( x ))), 219 colour = 'red' , width = 3 , style = wx . PENSTYLE_DOT_DASH ) 220 line2 = wxplot . PolyLine ( list ( zip ( x , - np . sin ( x ))), 221 colour = 'blue' , width = 3 , style = wx . PENSTYLE_LONG_DASH ) Then generate a graphics object and render it on the canvas Source file 223 # create a graphics 224

wxPython COM Terminal

PySerial provides a convenient way to handle data transfer over COM ports. It works on most of the platforms. The program is written using wxPython to create a simple terminal emulator. It is pretty much similar to the example presented by PySerial itself.  However in this case, the UI is built on wx.Panel instead of wx.Frame, thus it is easy to embed the terminal emulator into any wxPython GUI. All GUI components are populated on a single panel inherited from the wx.Panel. On the left a wx.TextCtrl is located as a terminal window. On the right, handful controls are added for for basic setup and control of the terminal window and the COM port. The controls on the right side can be hidden or shown at any time.This terminal supports ASCII mode and Hexadecimal mode. You can also save data to a disk file. Since all the features are implemented on a wx.Panel (TermPanel), it can be embedded into any window including main frame window simply by creating an instance and passing a PySer