STM32 DMA-Übertragungsbrücke zwischen 2 UART-Ports

Ich verwende einen stm32f103 und versuche, alle auf 1 Uart empfangenen Daten einfach an einen anderen Uart zu übertragen und umgekehrt.

Bei Verwendung von 2 Terminalprogrammen funktioniert es hervorragend, alles, was ich tippe, wird ohne Probleme übertragen. Wenn Sie jedoch eine lange Zeichenfolge senden, z. B. „12345678“, lautet das Ergebnis „1357“. Es wird also so ziemlich jedes 2. Zeichen übersprungen. Es fühlt sich an, als würde es jedes 2. Zeichen verpassen, wenn es damit beschäftigt ist, das erste Zeichen zu übertragen.

Irgendwelche Ideen, wie dies geändert werden kann, um dies nicht zu tun?

Dies ist mein aktueller Code (Basis generiert aus stm32cubemx):

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


/* Private Variablen ---------------------------------------------- -----------*/
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 Variablen ---------------------------------------------- -----------*/

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

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


/* Private Funktionsprototypen --------------------------------------------- --*/
void SystemClock_Config (void);
statisch void MX_GPIO_Init (void);
statisch void MX_DMA_Init (void);
statisch void MX_USART2_UART_Init (void);
statisch 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)
{

    wenn ( huart == &huart1 )
    {
        __HAL_UART_FLUSH_DRREGISTER(&huart1); // Löschen Sie den Puffer, um einen Überlauf zu verhindern
        txPuffer = rxPuffer;
        uart2(&txPuffer);
        HAL_UART_Receive_DMA(&huart1, &rxBuffer, 1);

        zurückkehren;
    }

    wenn ( huart == &huart2 )
    {
        __HAL_UART_FLUSH_DRREGISTER(&huart2); // Löschen Sie den Puffer, um einen Überlauf zu verhindern
        txBuffer2 = rxBuffer2;
        uart1(&txPuffer2);
        HAL_UART_Receive_DMA(&huart2, &rxBuffer2, 1);
        zurückkehren;
    }
}



/* BENUTZERCODE ENDE 0 */

int Haupt(leer)
{


  /* MCU-Konfiguration ---------------------------------------------- ------------*/

  /* Reset aller Peripheriegeräte, Initialisiert das Flash-Interface und den Systick. */
  HAL_Init();

  /* Systemuhr konfigurieren */
  SystemClock_Config();

  /* Alle konfigurierten Peripheriegeräte initialisieren */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART2_UART_Init();
  MX_USART1_UART_Init();

  // Startbrücke
  __HAL_UART_FLUSH_DRREGISTER(&huart1);
  HAL_UART_Receive_DMA(&huart1, &rxBuffer, 1);

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



  /* Endlosschleife */
  während (1)
  {
  }

}

/** Konfiguration der Systemuhr
*/
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.HSICilibrationValue = 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-Konfiguration */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/* USART1-Init-Funktion */
Nichtig MX_USART1_UART_Init(nichtig)
{

  huart1.Instanz = 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-Funktion */
ungültig MX_USART2_UART_Init (nichtig)
{

  huart2.Instanz = 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);

}

/**
  * DMA-Controller-Takt aktivieren
  */
Nichtig MX_DMA_Init(nichtig)
{
  /* DMA-Controller-Takt aktivieren */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA-Interrupt initialisieren */
  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);

}

/** Pins konfigurieren als
        *Analog
        * Eingang
        * Ausgang
        * EREIGNIS_AUS
        * EXT
*/
Nichtig MX_GPIO_Init(nichtig)
{

  GPIO_InitTypeDef GPIO_InitStruct;

  /* GPIO-Ports Uhr aktivieren */
  __GPIOA_CLK_ENABLE();
  __GPIOB_CLK_ENABLE();

  /* GPIO-Pin-Ausgangspegel konfigurieren */
  HAL_GPIO_WritePin (DUT_RESET_GPIO_Port, DUT_RESET_Pin, GPIO_PIN_RESET);

  /* GPIO-Pin-Ausgangspegel konfigurieren */
  HAL_GPIO_WritePin (GPIOA, LED_Pin | GPIO_PIN_15, GPIO_PIN_RESET);

  /*GPIO-Pin konfigurieren: 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);

  /*GPIO-Pins konfigurieren: 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);

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

}

/* BENUTZERCODE ANFANG 4 */

/* BENUTZERCODE ENDE 4 */

#ifdef USE_FULL_ASSERT

/**
   * @brief Gibt den Namen der Quelldatei und die Nummer der Quellzeile aus
   * wo der assert_param-Fehler aufgetreten ist.
   * @param file: Zeiger auf den Namen der Quelldatei
   * @param-Zeile: assert_param-Fehlerzeilen-Quellennummer
   * @retval Keine
   */
void assert_failed (uint8_t*-Datei, uint32_t-Zeile)
{
  /* BENUTZERCODE ANFANG 6 */
  /* Der Benutzer kann seine eigene Implementierung hinzufügen, um den Dateinamen und die Zeilennummer zu melden,
    Bsp.: printf("Falscher Parameterwert: Datei %s in Zeile %d\r\n", Datei, Zeile) */
  /* BENUTZERCODE ENDE 6 */

}

#endif

/**
  * @}
  */

/**
  * @}
*/

Klingt so, als ob Sie einen Puffer in der Mitte brauchen.
Dies ist im Grunde die UART-Echofunktionalität, außer dass Sie, anstatt zur Quelle zu echoen, woanders echoen. Benutzt du Interrupts? Denn ein UART-Interrupt, der einfach alles, was im RX-Register steht, in das TX-Register des anderen UART schreibt, sollte es gut machen. Sie sollten keinen DMA oder Puffer benötigen, wenn die eingehenden und ausgehenden Bitraten gleich sind, es sei denn, Sie möchten nur gültige Nachrichten wiederholen. In diesem Fall würden Sie die gesamte Nachricht auf einmal per DMA empfangen, ihre Gültigkeit überprüfen und dann DMA sendet dieselbe Nachricht, um sie zu wiederholen.

Antworten (3)

Betrachten Sie den folgenden Ausschnitt aus dem STM32F1-Handbuch bezüglich des USART-Datenregisters und des entsprechenden Statusbits „TXE“:

Single-Byte-Kommunikation

Das TXE-Bit wird immer durch einen Schreibvorgang in das Datenregister gelöscht. Das TXE-Bit wird von der Hardware gesetzt und zeigt Folgendes an:

• Die Daten wurden vom TDR in das Schieberegister verschoben und die Datenübertragung hat begonnen.

• Das TDR-Register ist leer.

Die nächsten Daten können in das USART_DR-Register geschrieben werden, ohne die vorherigen Daten zu überschreiben.

Dieses Flag erzeugt einen Interrupt, wenn das TXEIE-Bit gesetzt ist.

Können Sie sich ein Szenario vorstellen, in dem Sie in das Datenregister schreiben, bevor das vorherige Byte in das Schieberegister gelangt ist? Denken Sie an die Abfolge der Ereignisse, wenn Sie einen Bytestrom empfangen, insbesondere wenn Sie das dritte oder vierte Byte empfangen. Was macht Ihr Sender zu diesem Zeitpunkt? Ist sein Datenregister belegt?

Wie bereits erwähnt, benötigen Sie wahrscheinlich eine Möglichkeit, vorherige Bytes zu puffern, wenn das TXE-Statusbit nicht gesetzt ist. Das Schreiben in das USART-Datenregister, während das TXE-Statusbit nicht gesetzt ist, ist ein garantierter Weg, um Informationen zu verlieren.


Bearbeiten als Antwort auf Kommentar:

Das ist eine Möglichkeit, ich könnte mir vorstellen, dass es funktioniert. Ich denke jedoch, dass es ein besserer Ansatz wäre, DMA abzuschaffen und die USART-Interrupts zu verwenden. Sie können 2 Interrupt-Service-Routinen haben, wenn ein Byte empfangen wird und wenn das Sendedatenregister leer ist. Basierend auf diesen beiden Ereignissen können Sie entscheiden, die Daten zu puffern (im RX-Handler) und den Puffer zu leeren (im TX-Complete-Handler). Bei der Entscheidung, wann der TX-Abschluss-Interrupt aktiviert/deaktiviert werden soll, muss sorgfältig vorgegangen werden. Ich denke, der Overhead für die Einrichtung von DMA ist für größere sequentielle Transaktionen in (oder aus) Puffern sinnvoller und weniger für den Fall von einzelnen Byte für Byte.

okay, das macht Sinn. Aber ich kann innerhalb des Rückrufs nicht warten, bis txe gesetzt ist. Innerhalb des Callbacks würde ich also prüfen, ob txe gesetzt ist, wenn nicht, dann füge ich es einem Array hinzu. Aber müsste ich woanders nachschauen ob der txe gesetzt ist damit ich die Daten senden kann? Kann ich das einfach in der Hauptschleife machen? dh. prüfen, ob es gepufferte Daten gibt und prüfen, ob txe gesetzt ist, ob dann alles im Puffer übertragen wird?
@TomVandenBon - Antwort wurde bearbeitet, um einen Ansatz vorzuschlagen

So sollte DMA nicht verwendet werden. Sie richten 1-Byte-DMA-Übertragungen ein und reagieren auf deren Abschluss, was keinen Sinn ergibt, da DMA dazu gedacht ist, Blöcke und Datenströme hinter den Kulissen zu übertragen, ohne dass es zu sehr stört. Im Grunde ist das, was Sie jetzt tun, dasselbe, als wenn Sie Bytes einzeln im Empfangsinterrupt empfangen, nur dass das Einrichten von DMA für jedes Byte langsamer ist, als nur das Verwenden des Empfangsinterrupts, wodurch Zeichen verloren gehen.

Zwei Lösungen. Eine besteht darin, kein DMA zu verwenden, sondern nur Interrupts für jedes Byte zu verwenden. Eine andere Möglichkeit besteht darin, DMA im automatischen Neustart-Doppelpuffermodus zu verwenden, aber Sie benötigen möglicherweise einen kleinen Puffer dazwischen, da ich nicht 100% sicher bin, dass es möglich ist, einen Gerät-zu-Gerät-DMA-Modus durchzuführen, bei dem die Zielspeicheradresse nicht inkrementiert wird (so könnte es zeigen immer auf die USART TXD-Adresse). Oder vielleicht kann es mit einer Übertragungslänge von 1 vor dem automatischen Neustart.

Die Kritik an DMA ist eine Sache, aber die Realität ist, dass dieses Problem nicht sicher gelöst werden kann, wenn nicht garantiert werden kann, dass die Ausgabe-Baudrate höher ist als die Eingabe, einschließlich aller Baud- Fehler .

Ich verwende einen stm32f103 und versuche, alle auf 1 Uart empfangenen Daten einfach an einen anderen Uart zu übertragen und umgekehrt.

Abgesehen von Implementierungsproblemen kann dieses Problem nicht zuverlässig gelöst werden , es sei denn, Sie können garantieren, dass die Datenankunftsrate an jedem Eingang geringer ist als die Rate, mit der Sie Daten an den entsprechenden Ausgang spülen können. Für ein bidirektionales System ist das besonders schwierig.

Schauen wir uns zunächst an, was passiert, wenn Sie versuchen, es ohne Puffer zu tun. Wenn Sie in diesem Fall ein Byte von einem Port erhalten, müssen Sie es am anderen ausgeben – aber Sie können dies nicht tun, bis das Staging-Register für die Übertragung leer ist. Wenn die Eingaberate schneller als die Ausgabe ist - selbst bei einem kleinen Fehler - führt die Abtastnatur des UART-Empfängers schließlich dazu, dass er zwei zeitlich enger beabstandete Bytes erzeugt, als seine Ausgabe-Baudrate austakten kann.

Sie könnten einen Puffer hinzufügen, aber das stellt auf lange Sicht immer noch das gleiche Problem dar, es sei denn, Sie können garantieren, dass es eine Pause in den Daten gibt, bevor eine Seite mehr als einen Pufferwert an Daten vor der anderen erhalten kann.

Wenn Ihr Fluss unidirektional wäre , wäre eine einfache Lösung, eine schnellere Baudrate am Ausgang als am Eingang zu verwenden. Einige Geräte können tatsächlich unterschiedliche Sende- und Empfangsbaudraten unterstützen, aber es ist nicht klar, dass alle an Ihrem System beteiligten Geräte dies können.

Hoffentlich sind Ihre Flüsse intermittierend genug, dass eine moderate Pufferung Ihr Problem lösen kann. In einem solchen Fall ist es wahrscheinlich am einfachsten, einen Ausgangspuffer zu haben und empfangene Bytes sofort in einen ISR zu stecken, aber es gibt andere mögliche Schemata. DMA wäre am einfachsten, wenn Sie Lücken zum Aufrüsten haben; doppelt zirkulärer DMA wäre theoretisch möglich, wäre aber ziemlich schwierig einzurichten.

Wenn dieser Betriebsmodus nur für einen "passiven Passthrough" -Betriebsmodus gilt, ist es vage möglich, dass eine schnelle Poling-Schleife Signale zwischen GPIOs ohne zu viel Jitter kopieren kann, damit das Ergebnis verständlich ist, insbesondere wenn Sie das haben könnten Die MCU tut nichts anderes, bis sie irgendwie ausgelöst wird, um diesen Modus zu verlassen. Es ist vermutlich sogar möglich, von demselben GPIO-Eingang zu kopieren, den der UART empfängt, um nach einem Exit-Trigger zu suchen.