5
\$\begingroup\$

I am using a stm32f103 and I'm trying to simply transmit all data received on 1 uart to another uart and vice versa.

When using 2 terminal programs it works great, everything I type gets transmitted without any issues. But if send a long string, for example '12345678' at one time, then the result is '1357'. So it's pretty much skipping every 2nd character. It feels like it misses every 2nd character when it's busy transmitting the first character.

Any ideas on how this can be changed to not do this?

This is my current code (base generated from stm32cubemx):

/* Includes ------------------------------------------------------------------*/
#include "stm32f1xx_hal.h"


/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
UART_HandleTypeDef huart2;
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;
DMA_HandleTypeDef hdma_usart2_rx;
DMA_HandleTypeDef hdma_usart2_tx;

/* Private variables ---------------------------------------------------------*/

uint8_t rxBuffer = '\000';
uint8_t rxBuffer2 = '\000';

uint8_t txBuffer = '\000';
uint8_t txBuffer2 = '\000';


/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_USART1_UART_Init(void);


void uart1( char *msg )
{
    HAL_UART_Transmit_DMA(&huart1, (uint8_t *)msg, 1);
}

void uart2( char *msg )
{
    HAL_UART_Transmit_DMA(&huart2, (uint8_t *)msg, 1);
}


void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{

    if ( huart == &huart1 )
    {
        __HAL_UART_FLUSH_DRREGISTER(&huart1); // Clear the buffer to prevent overrun
        txBuffer = rxBuffer;
        uart2(&txBuffer);
        HAL_UART_Receive_DMA(&huart1, &rxBuffer, 1);

        return;
    }

    if ( huart == &huart2 )
    {
        __HAL_UART_FLUSH_DRREGISTER(&huart2); // Clear the buffer to prevent overrun
        txBuffer2 = rxBuffer2;
        uart1(&txBuffer2);
        HAL_UART_Receive_DMA(&huart2, &rxBuffer2, 1);
        return;
    }
}



/* USER CODE END 0 */

int main(void)
{


  /* MCU Configuration----------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART2_UART_Init();
  MX_USART1_UART_Init();

  // starting bridge
  __HAL_UART_FLUSH_DRREGISTER(&huart1);
  HAL_UART_Receive_DMA(&huart1, &rxBuffer, 1);

  __HAL_UART_FLUSH_DRREGISTER(&huart2);
  HAL_UART_Receive_DMA(&huart2, &rxBuffer2, 1);



  /* Infinite loop */
  while (1)
  {
  }

}

/** System Clock Configuration
*/
void SystemClock_Config(void)
{

  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = 16;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL12;
  HAL_RCC_OscConfig(&RCC_OscInitStruct);

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1);

  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  /* SysTick_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/* USART1 init function */
void MX_USART1_UART_Init(void)
{

  huart1.Instance = USART1;
  huart1.Init.BaudRate = 230400;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  HAL_UART_Init(&huart1);

}

/* USART2 init function */
void MX_USART2_UART_Init(void)
{

  huart2.Instance = USART2;
  huart2.Init.BaudRate = 230400;
  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;
  HAL_UART_Init(&huart2);

}

/** 
  * Enable DMA controller clock
  */
void MX_DMA_Init(void) 
{
  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
  HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);
  HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);
  HAL_NVIC_SetPriority(DMA1_Channel7_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel7_IRQn);

}

/** Configure pins as 
        * Analog 
        * Input 
        * Output
        * EVENT_OUT
        * EXTI
*/
void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct;

  /* GPIO Ports Clock Enable */
  __GPIOA_CLK_ENABLE();
  __GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(DUT_RESET_GPIO_Port, DUT_RESET_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, LED_Pin|GPIO_PIN_15, GPIO_PIN_RESET);

  /*Configure GPIO pin : DUT_RESET_Pin */
  GPIO_InitStruct.Pin = DUT_RESET_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
  HAL_GPIO_Init(DUT_RESET_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pins : LED_Pin PA15 */
  GPIO_InitStruct.Pin = LED_Pin|GPIO_PIN_15;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pin : PB9 */
  GPIO_InitStruct.Pin = GPIO_PIN_9;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

#ifdef USE_FULL_ASSERT

/**
   * @brief Reports the name of the source file and the source line number
   * where the assert_param error has occurred.
   * @param file: pointer to the source file name
   * @param line: assert_param error line source number
   * @retval None
   */
void assert_failed(uint8_t* file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
    ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */

}

#endif

/**
  * @}
  */ 

/**
  * @}
*/ 

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Sounds like you need a buffer in the middle. \$\endgroup\$ Commented Mar 11, 2016 at 14:35
  • \$\begingroup\$ This is basically UART echo functionality except instead of echoing to the source you are echoing somewherere else. Are you using interrupts? Because a UART interrupt that just writes whatever is in the RX register into the other UART's TX register should do it just fine. You shouldn't need a DMA or a buffer if the incoming and outgoing bit rates are the same unless you only want to repeat only valid messages, in which case you would DMA receive the entire message in one go, check it's validity, and then DMA transmit the same message to repeat it. \$\endgroup\$
    – DKNguyen
    Commented Mar 26, 2019 at 20:42

3 Answers 3

3
\$\begingroup\$

Consider the following snippet from the STM32F1 manual regarding the USART data register and its corresponding status bit 'TXE':

Single byte communication

The TXE bit is always cleared by a write to the data register. The TXE bit is set by hardware and it indicates:

• The data has been moved from TDR to the shift register and the data transmission has started.

• The TDR register is empty.

The next data can be written in the USART_DR register without overwriting the previous data.

This flag generates an interrupt if the TXEIE bit is set.

Can you imagine a scenario where you are writing into the data register before the previous byte has made it into the shift register? Think about the sequence of events when you receive a stream of bytes, especially when you receive the third or fourth byte, what is your transmitter doing at this point? Is its data register occupied?

As a previous comment mentioned, you likely need a way to buffer previous byte(s) if the TXE status bit is not set. Writing into the USART data register while the TXE status bit is not set is a guaranteed way to lose information.


Edit in response to comment:

That's one way to do it, I would imagine it would work. However, I think doing away with DMA and using the USART interrupts would be a better approach. You can have 2 interrupt service routines for when a byte is received and when the transmit data register is empty. Based on those two events, you can decide to buffer the data (in the RX handler) and to empty the buffer (in TX complete handler). Care must be taken in deciding when to enable/disable the TX complete interrupt. I think the overhead of setting up DMA makes more sense for larger sequential transactions into (or out of) buffers, and less so for the single byte-by-byte case.

\$\endgroup\$
2
  • \$\begingroup\$ ok, that makes sense. But I can't wait inside the callback until txe is set. So inside the callback I would check if txe is set, if not then then I'll add it into an array. But I would need somewhere else to check if the txe is set so that I can send the data? Can I just do that in the main loop ? ie. check if is there is buffered data and check if txe is set, if it is then transmit everything in buffer? \$\endgroup\$ Commented Mar 12, 2016 at 11:54
  • \$\begingroup\$ @TomVandenBon - answer has been edited to suggest an approach \$\endgroup\$
    – Jon L
    Commented Mar 13, 2016 at 17:39
2
\$\begingroup\$

That's not how DMA should be used. You are setting up 1-byte DMA transfers and reacting to their completion, which makes no sense, as DMA is meant to transfer blocks and streams of data behind the scenes without it bothering too much. Basically, what you do now is same than receiving bytes one at a time in the receive interrupt, only that setting up DMA for every byte is slower than just using the receive interrupt which makes it lose characters.

Two solutions. One is not to use DMA, just use interrupts for each byte. Another one is to use DMA in autorestart double buffer mode, but you may need a small buffer in between, as I am not 100% sure it possible to do device to device DMA mode where the target memory address is not incremented (so it could point to the USART TXD address always). Or maybe it can with transfer length of 1 before autorestart.

\$\endgroup\$
1
  • \$\begingroup\$ The criticism of DMA is one thing, but the reality is that this problem can't be safely solved unless it can be guaranteed that the output baud rate is higher than the input, inclusive of any baud error. \$\endgroup\$ Commented Mar 26, 2019 at 21:46
0
\$\begingroup\$

I am using a stm32f103 and I'm trying to simply transmit all data received on 1 uart to another uart and vice versa.

Implementation issues aside, this problem cannot be solved reliably, unless you can guarantee that the rate of data arrival on each input is less than the rate at which you can flush data on the corresponding output. For a bi-directional system, that is especially hard.

First let's look at what happens if you try to do it without buffers. In this case, when you get a byte from one port, you need to dispose of it out the other - but you cannot do so until the transmit staging register is empty. If the input rate is faster than the output - even by a small error then the sampling nature of the UART receiver will eventually cause it to produce two bytes more closely spaced in time than its output baud rate can clock out.

You could add a buffer, but that still presents the same problem in the long run, unless you can guarantee that there will be pause in the data before one side can get more than a buffer's worth of data ahead of the other.

If your flow were unidirectional a simple solution would be to use a faster baud rate on the output than the input. A few devices can actually support distinct transmit and receive baud rates, but it's not clear that everything involved in your system could.

Hopefully, your flows are intermittent enough that moderate buffering can solve your problem. In such a case, it's probably easiest to have an output buffer, and to put received bytes immediately into that in an ISR, but there are other possible schemes. DMA would be simplest if you have gaps in which to re-arm; doubly circular DMA might in theory be possible but would be quite challenging to set up.

If this mode of operation is only for a "passive passthrough" mode of operation, it's vaguely possible that a fast poling loop might be able to copy signals between GPIOs without too much jitter for the result to be intelligible, especially if you could have the MCU do nothing else until somehow triggered to exit that mode. It's presumably even possible to copy from the same GPIO input that the UART is receiving to watch for an exit trigger.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.