Peripherals - UART

Created:2018-02-18  Last modified:2018-02-18


  1. Introduction

    Generalization of the communication peripheral

    UART, SPI, I2C are built-in peripherals that use i/o pins, and internal data bus. They also have corresponding interrupts

    Ways to get data

    1. Polling
    2. Interrupt
    3. DMA

    Question need to be considered

    1. In APB1 or APB2?
    2. GPIO alternative function configuration
    3. Define the corresponding HAL_xxx_MspInit
    4. Corresponding DMA channel.

    UART modules

    UART serial communication is one of the function provided by the USART module. Besides UART, the USART module also provides functions such as IrDA, modem control

    The STM32 F103RB consists three independent USART modules

    USART1 (APB2)

    Function nameDefault pinsRemap pins 1Remap pins2
    RXPA10PB7
    TXPA9PB6
    CKPA8
    CTSPA11
    RTSPA12

    USART2 (APB1)

    In nucleo, the usart2's rx/tx is hard wired to the st-link virtual COM.

    Function nameDefault pinsRemap pins 1Remap pins2
    RXPA3PD6
    TXPA2PD5
    CKPA4PD7
    CTSPA0PD3
    RTSPA1PD4

    USART3 (APB1)

    Function nameDefault pinsRemap pins 1Remap pins2
    RXPB11PD9PC11
    TXPB10PD8PC10
    CKPB12PD10PC12
    CTSPB13PD11
    RTSPB14PD12

    Struct

  2. Polling

  3. DMA

    Configuration

    Every data channel needs a DMA channel. RX, TX are independent, so they needs to use 2 DMA channel. UART_RX -> DMA1_channel16; UART_TX -> DMA1_channel17

    The developerdefines two functions in the main

    1. DMA_init():
      i. enable DMA1 clock
      ii. set channels' priorities and enable interrupt.
      static void MX_DMA_Init(void) 
      {
        /* DMA controller clock enable */
        __HAL_RCC_DMA1_CLK_ENABLE();
      
        /* DMA interrupt init */
        /* DMA1_Channel6_IRQn interrupt configuration */
        HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 0, 0);
        HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);
        /* DMA1_Channel7_IRQn interrupt configuration */
        HAL_NVIC_SetPriority(DMA1_Channel7_IRQn, 0, 0);
        HAL_NVIC_EnableIRQ(DMA1_Channel7_IRQn);
      
      }
          
    2. UART_Init():
      i. set the huart2.Init (UART_InitTypeDef) such as baudrate, #bits, parity
      ii. invoke HAL_UART_Init().
          /* USART2 init function */
      static void MX_USART2_UART_Init(void)
      {
      
        huart2.Instance = USART2;
        huart2.Init.BaudRate = 115200;
        huart2.Init.WordLength = UART_WORDLENGTH_8B;
        huart2.Init.StopBits = UART_STOPBITS_1;
        huart2.Init.Parity = UART_PARITY_NONE;
        huart2.Init.Mode = UART_MODE_TX_RX;
        huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
        huart2.Init.OverSampling = UART_OVERSAMPLING_16;
        if (HAL_UART_Init(&huart2) != HAL_OK)
        {
          _Error_Handler(__FILE__, __LINE__);
        }
      
      }
          

    Inside HAL_xxx_Init:

    1. Call the weak function HAL_UART_MspInit(); // developer should override this function
    2. Using *.Init to set *.Instance's registers

    The developer should override HAL_xxx_MspInit():

    * Define in the stm32f1xx_hal_msp.c file

    1. 1. check if the instance is the desire instance. Because all UART1, UART2 and UART3 are going to use this function.
              if(huart->Instance == USART2){
          
    2. Enable corresponding GPIO clock, and peripheral clock
          __HAL_RCC_GPIOA_CLK_ENABLE();
          __HAL_RCC_USART2_CLK_ENABLE();
      
    3. Configure GPIO
          GPIO_InitStruct.Pin = GPIO_PIN_2;
          GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // alternative function推免
          GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
          HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
      
          GPIO_InitStruct.Pin = GPIO_PIN_3;
          GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
          GPIO_InitStruct.Pull = GPIO_NOPULL;
          HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
      
    4. Configure DMA channels
          /* USART2 DMA Init */
          /* USART2_RX Init */
          hdma_usart2_rx.Instance = DMA1_Channel6;
          hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
          hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
          hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE;
          hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
          hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
          hdma_usart2_rx.Init.Mode = DMA_NORMAL;
          hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW;
          if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK)
          {
            _Error_Handler(__FILE__, __LINE__);
          }
      
          __HAL_LINKDMA(huart,hdmarx,hdma_usart2_rx); 
          // bind the huart's RX DMA channel to this DMA
          // bind DMA parent to the huart
      
          /* USART2_TX Init */
          hdma_usart2_tx.Instance = DMA1_Channel7;
          hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
          hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
          hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE;
          hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
          hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
          hdma_usart2_tx.Init.Mode = DMA_NORMAL;
          hdma_usart2_tx.Init.Priority = DMA_PRIORITY_LOW;
          if (HAL_DMA_Init(&hdma_usart2_tx) != HAL_OK)
          {
            _Error_Handler(__FILE__, __LINE__);
          }
      
          __HAL_LINKDMA(huart,hdmatx,hdma_usart2_tx);
      
    5. Enable UART interrupt
          /* USART2 interrupt Init */
          HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
          HAL_NVIC_EnableIRQ(USART2_IRQn);
      

    Tranfer data in DMA mode

    1. HAL_UART_Transmit_DMA()

      A non-blocking function. Return HAL_OK or HAL_BUSY

      // two successive call to the function, the second may be ignored.
      HAL_UART_Transmit_DMA(&huart1, data, 5 );
      HAL_UART_Transmit_DMA(&huart1, data2, 6); // the second would be ignore if the first is not completed.
      // !!!!design a queue that send the following in the uart complete callback.
      
      //This function bind the pre-defined handler to the DMA TX channel, and start DMA transmit
      
    2. HAL_UART_Receive_DMA()

      A non-blocking function. Return HAL_OK or HAL_BUSY

      // The DMA will block in the background if no data is coming.
      
    3. user defined callback functions

      void HAL_UART_TxCpltCallback(UART_HandleTypeDef * huart);
      void HAL_UART_RxCpltCallback(UART_HandleTypeDef * huart);
      void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef * huart);
      void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef * huart);
      void HAL_UART_ErrorCallback(UART_HandleTypeDef * huart);
      //....
      

    Interrupt relations

    Three type of interrupts are involved in this app.

    Code in the stm32f1xx_it. This wrapper provides an argument to the core handler via software C "extern" keyword.

    1. void DMA1_Channel6_IRQHandler(void)
      {
        HAL_DMA_IRQHandler(&hdma_usart2_rx); // defined in the "stm32f1xx_hal_dma.c"
      }
      
    2. void DMA1_Channel7_IRQHandler(void)
      {
        HAL_DMA_IRQHandler(&hdma_usart2_tx);
      }
      
    3. void USART2_IRQHandler(void)
      {
        HAL_UART_IRQHandler(&huart2);
      }
      

    DMA interrupt [HAL_DMA_IRQHandler]

    Three source can trigger DMA interrupt, half complete, full complete and error. Inside the HAL_DMA_IRQHandler, it polling the three conditions and invoke the callback method defined in the DMA_HandlerTypeDef

    The callback functions are defined by the HAL uart library in the stm32f1xx_hal_uart.c. They will bind to the DMA handler when calling to transmit/receive data.

    1. UART_DMATransmitCplt(DMA_HandleTypeDef *hdma)

      //This function is defined in the stm32f1xx_hal_uart.c
      //It invokes after the DMA transmit completes, therefore, both rx/tx will invoke this function
      //During normal mode, it enable the uart transmit complete interrupt. Becuase the transmit is complete, so the transmit complete function would be invoked immediately.
      
      //The UART complete handler will just clear the the busy state of this uart.
      static void UART_DMATransmitCplt(DMA_HandleTypeDef *hdma)
      {
        UART_HandleTypeDef* huart = ( UART_HandleTypeDef* )((DMA_HandleTypeDef* )hdma)->Parent;
        /* DMA Normal mode*/
        if((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U)
        {
          huart->TxXferCount = 0U;
      
          /* Disable the DMA transfer for transmit request by setting the DMAT bit
             in the UART CR3 register */
          CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAT);
      
          /* Enable the UART Transmit Complete Interrupt */
          SET_BIT(huart->Instance->CR1, USART_CR1_TCIE);
      
        }
        /* DMA Circular mode */
        else
        {
          HAL_UART_TxCpltCallback(huart);
        }
      }
      
      static void UART_DMAReceiveCplt(DMA_HandleTypeDef *hdma)
      {
        UART_HandleTypeDef* huart = ( UART_HandleTypeDef* )((DMA_HandleTypeDef* )hdma)->Parent;
        /* DMA Normal mode*/
        if((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U)
        {
          huart->RxXferCount = 0U;
        
          /* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */
          CLEAR_BIT(huart->Instance->CR1, USART_CR1_PEIE);
          CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);
          
          /* Disable the DMA transfer for the receiver request by setting the DMAR bit 
             in the UART CR3 register */
          CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);
      
          /* At end of Rx process, restore huart->RxState to Ready */
          huart->RxState = HAL_UART_STATE_READY;
        }
        HAL_UART_RxCpltCallback(huart);
      }
      
    2. When transmitting data, the transmit function will bind the tx DMA channel's interrupt to the following handlers.

      UART_DMATxHalfCplt(DMA_HandleTypeDef *hdma)

      //ask the developer's action via the weak function HAL_UART_TxHalfCpltCallback
      static void UART_DMATxHalfCplt(DMA_HandleTypeDef *hdma)
      {
        UART_HandleTypeDef* huart = (UART_HandleTypeDef*)((DMA_HandleTypeDef*)hdma)->Parent;
      
        HAL_UART_TxHalfCpltCallback(huart); // By default, it does nothing
      }
      
      static void UART_DMARxHalfCplt(DMA_HandleTypeDef *hdma)
      {
        UART_HandleTypeDef* huart = (UART_HandleTypeDef*)((DMA_HandleTypeDef*)hdma)->Parent;
        HAL_UART_RxHalfCpltCallback(huart); 
      }
      
    3. UART_DMAError(DMA_HandleTypeDef *hdma)

      
      

    UART Interrupt[HAL_UART_IRQHandler]

    UART interrupt handler defined in the stm32_hal_uart.c. It pulls the source that cause the interrupt.

    void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
    {
       uint32_t isrflags   = READ_REG(huart->Instance->SR);
       uint32_t cr1its     = READ_REG(huart->Instance->CR1);
       uint32_t cr3its     = READ_REG(huart->Instance->CR3);
       uint32_t errorflags = 0x00U;
       uint32_t dmarequest = 0x00U;
    
      /* If no error occurs */
      errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
      if(errorflags == RESET)
      {
        /* UART in mode Receiver -------------------------------------------------*/
        if(((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
        {
          UART_Receive_IT(huart);
          return;
        }
      }
     //////// not entire code are shown here. ///////
      
      /* UART in mode Transmitter ------------------------------------------------*/
      if(((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
      {
        UART_Transmit_IT(huart);
        return;
      }
      
      /* UART in mode Transmitter end --------------------------------------------*/
      if(((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
      {
        UART_EndTransmit_IT(huart);
        return;
      }
    }