Puente de transferencia STM32 DMA entre 2 puertos uart

Estoy usando un stm32f103 y estoy tratando de transmitir simplemente todos los datos recibidos en 1 uart a otro uart y viceversa.

Cuando uso 2 programas de terminal funciona muy bien, todo lo que escribo se transmite sin ningún problema. Pero si envía una cadena larga, por ejemplo, '12345678' a la vez, el resultado es '1357'. Así que prácticamente se salta cada segundo carácter. Parece que pierde cada segundo carácter cuando está ocupado transmitiendo el primer carácter.

¿Alguna idea sobre cómo se puede cambiar esto para no hacer esto?

Este es mi código actual (base generada a partir de stm32cubemx):

/* Incluye ----------------------------------------------- -------------------*/
#incluye "stm32f1xx_hal.h"


/* Variables privadas ---------------------------------------------- -----------*/
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;

/* Variables privadas ---------------------------------------------- -----------*/

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

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


/* Prototipos de funciones privadas --------------------------------------------- --*/
vacío SystemClock_Config (vacío);
vacío estático MX_GPIO_Init (vacío);
vacío estático MX_DMA_Init (vacío);
vacío estático MX_USART2_UART_Init (vacío);
vacío estático MX_USART1_UART_Init (vacío);


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

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


anular HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{

    si ( huart == & huart1 )
    {
        __HAL_UART_FLUSH_DRREGISTER(&huart1); // Limpiar el búfer para evitar la saturación
        txBuffer = rxBuffer;
        uart2(&txBúfer);
        HAL_UART_Receive_DMA(&huart1, &rxBuffer, 1);

        devolver;
    }

    si ( huart == & huart2 )
    {
        __HAL_UART_FLUSH_DRREGISTER(&huart2); // Limpiar el búfer para evitar la saturación
        txBuffer2 = rxBuffer2;
        uart1(&txBúfer2);
        HAL_UART_Receive_DMA(&huart2, &rxBuffer2, 1);
        devolver;
    }
}



/* CÓDIGO DE USUARIO FIN 0 */

int principal (vacío)
{


  /* Configuración de MCU------------------------------------------------------- ------------*/

  /* Restablecimiento de todos los periféricos, Inicializa la interfaz Flash y el Systick. */
  HAL_Init();

  /* Configurar el reloj del sistema */
  Reloj del sistema_Config();

  /* Inicializar todos los periféricos configurados */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART2_UART_Init();
  MX_USART1_UART_Init();

  // puente inicial
  __HAL_UART_FLUSH_DRREGISTER(&huart1);
  HAL_UART_Receive_DMA(&huart1, &rxBuffer, 1);

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



  /* Bucle infinito */
  mientras (1)
  {
  }

}

/** Configuración del reloj del sistema
*/
vacío SystemClock_Config (vacío)
{

  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 configuración de interrupción */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/* Función de inicio USART1 */
vacío MX_USART1_UART_Init (vacío)
{

  huart1.Instancia = USART1;
  huart1.Init.Velocidad de baudios = 230400;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Modo = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.Sobremuestreo = UART_OVERSAMPLING_16;
  HAL_UART_Init(&huart1);

}

/* Función de inicio USART2 */
vacío MX_USART2_UART_Init (vacío)
{

  huart2.Instancia = USART2;
  huart2.Init.Velocidad de baudios = 230400;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Modo = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.Sobremuestreo = UART_OVERSAMPLING_16;
  HAL_UART_Init(&huart2);

}

/**
  * Habilitar el reloj del controlador DMA
  */
vacío MX_DMA_Init (vacío)
{
  /* Habilitación del reloj del controlador DMA */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* Inicialización de interrupción DMA */
  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);

}

/** Configurar pines como
        * Analógico
        * Aporte
        * Producción
        * EVENTO_SALIDA
        * EXTERIOR
*/
vacío MX_GPIO_Init (vacío)
{

  GPIO_InitTypeDef GPIO_InitStruct;

  /* Habilitar reloj de puertos GPIO */
  __GPIOA_CLK_ENABLE();
  __GPIOB_CLK_ENABLE();

  /*Configurar el nivel de salida del pin GPIO */
  HAL_GPIO_WritePin(DUT_RESET_GPIO_Port, DUT_RESET_Pin, GPIO_PIN_RESET);

  /*Configurar el nivel de salida del pin GPIO */
  HAL_GPIO_WritePin(GPIOA, LED_Pin|GPIO_PIN_15, GPIO_PIN_RESET);

  /*Configurar pin GPIO: 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);

  /*Configurar pines GPIO: 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);

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

}

/* CÓDIGO DE USUARIO EMPIEZA 4 */

/* CÓDIGO DE USUARIO FIN 4 */

#ifdef USE_FULL_ASSERT

/**
   * @brief Informa el nombre del archivo de origen y el número de línea de origen
   * donde se ha producido el error assert_param.
   * Archivo @param: puntero al nombre del archivo de origen
   * Línea @param: número de origen de línea de error de assert_param
   * @retval Ninguno
   */
anular afirmación_fallida (archivo uint8_t*, línea uint32_t)
{
  /* CÓDIGO DE USUARIO EMPIEZA 6 */
  /* El usuario puede agregar su propia implementación para informar el nombre del archivo y el número de línea,
    ej: printf("Valor de parámetros incorrecto: archivo %s en la línea %d\r\n", archivo, línea) */
  /* CÓDIGO DE USUARIO FIN 6 */

}

#terminara si

/**
  * @}
  */

/**
  * @}
*/

Parece que necesitas un búfer en el medio.
Esta es básicamente la funcionalidad de eco UART, excepto que en lugar de hacer eco en la fuente, está haciendo eco en otro lugar. ¿Estás usando interrupciones? Porque una interrupción de UART que solo escribe lo que está en el registro RX en el registro TX del otro UART debería funcionar bien. No debería necesitar un DMA o un búfer si las tasas de bits entrantes y salientes son las mismas, a menos que solo desee repetir mensajes válidos, en cuyo caso DMA recibiría el mensaje completo de una sola vez, verificaría su validez y luego DMA transmite el mismo mensaje para repetirlo.

Respuestas (3)

Considere el siguiente fragmento del manual STM32F1 con respecto al registro de datos USART y su bit de estado correspondiente 'TXE':

Comunicación de un solo byte

El bit TXE siempre se borra mediante una escritura en el registro de datos. El bit TXE lo establece el hardware e indica:

• Los datos se han movido de TDR al registro de desplazamiento y se ha iniciado la transmisión de datos.

• El registro TDR está vacío.

Los siguientes datos se pueden escribir en el registro USART_DR sin sobrescribir los datos anteriores.

Este indicador genera una interrupción si se establece el bit TXEIE.

¿Puede imaginar un escenario en el que esté escribiendo en el registro de datos antes de que el byte anterior haya llegado al registro de desplazamiento? Piense en la secuencia de eventos cuando recibe un flujo de bytes, especialmente cuando recibe el tercer o cuarto byte, ¿qué está haciendo su transmisor en este momento? ¿Está ocupado su registro de datos?

Como se mencionó en un comentario anterior, es probable que necesite una forma de almacenar en búfer los bytes anteriores si el bit de estado TXE no está configurado. Escribir en el registro de datos USART mientras el bit de estado TXE no está configurado es una forma garantizada de perder información.


Editar en respuesta al comentario:

Esa es una forma de hacerlo, me imagino que funcionaría. Sin embargo, creo que eliminar DMA y usar las interrupciones USART sería un mejor enfoque. Puede tener 2 rutinas de servicio de interrupción para cuando se recibe un byte y cuando el registro de transmisión de datos está vacío. En función de esos dos eventos, puede decidir almacenar en búfer los datos (en el controlador de RX) y vaciar el búfer (en el controlador completo de TX). Se debe tener cuidado al decidir cuándo habilitar/deshabilitar la interrupción completa de TX. Creo que la sobrecarga de configurar DMA tiene más sentido para transacciones secuenciales más grandes dentro (o fuera de) búferes, y menos para el caso de un solo byte por byte.

bien, eso tiene sentido. Pero no puedo esperar dentro de la devolución de llamada hasta que se establezca txe. Entonces, dentro de la devolución de llamada, verificaría si txe está configurado, si no, lo agregaré a una matriz. Pero necesitaría otro lugar para verificar si el txe está configurado para poder enviar los datos. ¿Puedo hacer eso en el ciclo principal? es decir. verifique si hay datos en el búfer y verifique si txe está configurado, si es así, ¿transmite todo en el búfer?
@TomVandenBon: la respuesta se ha editado para sugerir un enfoque

No es así como se debe usar DMA. Está configurando transferencias DMA de 1 byte y reaccionando a su finalización, lo que no tiene sentido, ya que DMA está destinado a transferir bloques y flujos de datos entre bastidores sin que moleste demasiado. Básicamente, lo que hace ahora es lo mismo que recibir bytes uno a la vez en la interrupción de recepción, solo que configurar DMA para cada byte es más lento que simplemente usar la interrupción de recepción, lo que hace que pierda caracteres.

Dos soluciones. Una es no usar DMA, solo usar interrupciones para cada byte. Otra es usar DMA en el modo de doble búfer de reinicio automático, pero es posible que necesite un pequeño búfer en el medio, ya que no estoy 100% seguro de que sea posible hacer un modo DMA de dispositivo a dispositivo donde la dirección de la memoria de destino no se incrementa (por lo que podría apunte a la dirección USART TXD siempre). O tal vez pueda hacerlo con una longitud de transferencia de 1 antes del reinicio automático.

La crítica de DMA es una cosa, pero la realidad es que este problema no se puede resolver de manera segura a menos que se pueda garantizar que la tasa de baudios de salida sea más alta que la de entrada, incluido cualquier error de baudios .

Estoy usando un stm32f103 y estoy tratando de transmitir simplemente todos los datos recibidos en 1 uart a otro uart y viceversa.

Dejando a un lado los problemas de implementación, este problema no se puede resolver de manera confiable , a menos que pueda garantizar que la tasa de llegada de datos en cada entrada sea menor que la tasa a la que puede vaciar los datos en la salida correspondiente. Para un sistema bidireccional, eso es especialmente difícil.

Primero, veamos qué sucede si intentas hacerlo sin buffers. En este caso, cuando obtiene un byte de un puerto, debe desecharlo por el otro, pero no puede hacerlo hasta que el registro de transmisión esté vacío. Si la tasa de entrada es más rápida que la salida, incluso por un pequeño error , entonces la naturaleza de muestreo del receptor UART eventualmente hará que produzca dos bytes más espaciados en el tiempo de lo que su tasa de baudios de salida puede registrar.

Puede agregar un búfer, pero eso aún presenta el mismo problema a largo plazo, a menos que pueda garantizar que habrá una pausa en los datos antes de que un lado pueda obtener más datos que el valor de un búfer antes que el otro.

Si su flujo fuera unidireccional , una solución simple sería usar una velocidad de transmisión más rápida en la salida que en la entrada. Algunos dispositivos en realidad pueden admitir distintas tasas de transmisión y recepción en baudios, pero no está claro que todo lo relacionado con su sistema pueda hacerlo.

Con suerte, sus flujos son lo suficientemente intermitentes como para que un almacenamiento en búfer moderado pueda resolver su problema. En tal caso, probablemente sea más fácil tener un búfer de salida y colocar los bytes recibidos inmediatamente en un ISR, pero existen otros esquemas posibles. DMA sería más simple si tiene espacios en los que volver a armar; En teoría, el DMA doblemente circular podría ser posible, pero sería bastante difícil de configurar.

Si este modo de operación es solo para un modo de operación de "paso pasivo", es vagamente posible que un bucle de polarización rápido pueda copiar señales entre GPIO sin demasiada fluctuación para que el resultado sea inteligible, especialmente si usted podría tener el MCU no hace nada más hasta que de alguna manera se active para salir de ese modo. Presumiblemente, incluso es posible copiar desde la misma entrada GPIO que recibe el UART para ver si hay un activador de salida.