Мост STM32 DMA Transfer между 2 портами uart

Я использую stm32f103 и пытаюсь просто передать все данные, полученные на 1 uart, на другой uart и наоборот.

При использовании двух терминальных программ все работает отлично, все, что я печатаю, передается без проблем. Но если отправить длинную строку, например, «12345678» за один раз, результатом будет «1357». Так что это в значительной степени пропускает каждый второй символ. Такое ощущение, что он пропускает каждый второй символ, когда занят передачей первого символа.

Любые идеи о том, как это можно изменить, чтобы этого не делать?

Это мой текущий код (база сгенерирована из stm32cubemx):

/* Включает ----------------------------------------------- -----*/
#include "stm32f1xx_hal.h"


/* Частные переменные ------------------------------------------------------------- -----------*/
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;

/* Частные переменные ------------------------------------------------------------- -----------*/

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

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


/* Прототипы закрытых функций ------------------------------------------------------------ --*/
недействительным SystemClock_Config (недействительным);
статическая недействительная MX_GPIO_Init (пустая);
статическая недействительная MX_DMA_Init (пустая);
статическая пустота MX_USART2_UART_Init(void);
статическая пустота MX_USART1_UART_Init(void);


недействительный uart1 (символ * сообщение)
{
    HAL_UART_Transmit_DMA(&huart1, (uint8_t *)msg, 1);
}

недействительный uart2 (символ * сообщение)
{
    HAL_UART_Transmit_DMA(&huart2, (uint8_t *)msg, 1);
}


недействительным HAL_UART_RxCpltCallback (UART_HandleTypeDef * huart)
{

    если (хуарт == &хуарт1)
    {
        __HAL_UART_FLUSH_DRREGISTER(&huart1); // Очистить буфер, чтобы предотвратить переполнение
        txBuffer = rxBuffer;
        uart2(&txBuffer);
        HAL_UART_Receive_DMA(&huart1, &rxBuffer, 1);

        возвращаться;
    }

    если (хуарт == &хуарт2)
    {
        __HAL_UART_FLUSH_DRREGISTER(&huart2); // Очистить буфер, чтобы предотвратить переполнение
        txBuffer2 = rxBuffer2;
        uart1(&txBuffer2);
        HAL_UART_Receive_DMA(&huart2, &rxBuffer2, 1);
        возвращаться;
    }
}



/* КОД ПОЛЬЗОВАТЕЛЯ КОНЕЦ 0 */

интервал основной (пустой)
{


  /* Конфигурация MCU-------------------------------------------------------------- ------------*/

  /* Сброс всех периферийных устройств, Инициализирует интерфейс Flash и Systick. */
  HAL_Init();

  /* Настройка системных часов */
  Системные часы_Конфигурация();

  /* Инициализировать все настроенные периферийные устройства */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART2_UART_Init();
  MX_USART1_UART_Init();

  // стартовый мост
  __HAL_UART_FLUSH_DRREGISTER(&huart1);
  HAL_UART_Receive_DMA(&huart1, &rxBuffer, 1);

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



  /* Бесконечная петля */
  пока (1)
  {
  }

}

/** Конфигурация системных часов
*/
недействительным SystemClock_Config (недействительным)
{

  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 */
  HAL_NVIC_SetPriority (SysTick_IRQn, 0, 0);
}

/* Функция инициализации USART1 */
недействительным MX_USART1_UART_Init (недействительным)
{

  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 */
недействительным MX_USART2_UART_Init (недействительным)
{

  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);

}

/**
  * Включить часы контроллера DMA
  */
недействительным MX_DMA_Init (недействительным)
{
  /* Включение часов контроллера DMA */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* Инициализация прерывания 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);

}

/** Настройте контакты как
        * Аналоговый
        * Вход
        * Выход
        * EVENT_OUT
        * ВНЕШНИЙ
*/
недействительным MX_GPIO_Init (недействительным)
{

  GPIO_InitTypeDef GPIO_InitStruct;

  /* Включение синхронизации портов GPIO */
  __GPIOA_CLK_ENABLE();
  __GPIOB_CLK_ENABLE();

  /*Настроить выходной уровень контакта GPIO */
  HAL_GPIO_WritePin(DUT_RESET_GPIO_Port, DUT_RESET_Pin, GPIO_PIN_RESET);

  /*Настроить выходной уровень контакта GPIO */
  HAL_GPIO_WritePin(GPIOA, LED_Pin|GPIO_PIN_15, GPIO_PIN_RESET);

  /*Настройте вывод 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);

  /*Настройте контакты 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);

  /*Настройте вывод 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);

}

/* КОД ПОЛЬЗОВАТЕЛЯ НАЧАЛО 4 */

/* КОД ПОЛЬЗОВАТЕЛЯ КОНЕЦ 4 */

#ifdef USE_FULL_ASSERT

/**
   * @brief Сообщает имя исходного файла и номер строки исходного кода
   * где произошла ошибка assert_param.
   * @param file: указатель на имя исходного файла
   * Строка @param: номер источника строки ошибки assert_param
   * @retval Нет
   */
void assert_failed (файл uint8_t*, строка uint32_t)
{
  /* НАЧАЛО КОДА ПОЛЬЗОВАТЕЛЯ 6 */
  /* Пользователь может добавить свою собственную реализацию для сообщения имени файла и номера строки,
    ex: printf("Неверное значение параметра: файл %s в строке %d\r\n", файл, строка) */
  /* КОД ПОЛЬЗОВАТЕЛЯ КОНЕЦ 6 */

}

#endif

/**
  * @}
  */

/**
  * @}
*/

Похоже, вам нужен буфер посередине.
Это в основном функция эха UART, за исключением того, что вместо эха к источнику вы эхо-эхо где-то еще. Вы используете прерывания? Потому что прерывание UART, которое просто записывает все, что находится в регистре RX, в регистр TX другого UART, должно работать нормально. Вам не нужен DMA или буфер, если входящие и исходящие скорости передачи одинаковы, если только вы не хотите повторять только действительные сообщения, и в этом случае вы должны DMA получить все сообщение за один раз, проверить его достоверность, а затем DMA передает одно и то же сообщение, чтобы повторить его.

Ответы (3)

Рассмотрим следующий фрагмент из руководства STM32F1 , касающийся регистра данных USART и соответствующего бита состояния «TXE»:

Однобайтовая связь

Бит TXE всегда очищается записью в регистр данных. Бит TXE устанавливается аппаратно и указывает:

• Данные были перемещены из TDR в регистр сдвига, и передача данных началась.

• Регистр TDR пуст.

Следующие данные могут быть записаны в регистр USART_DR без перезаписи предыдущих данных.

Этот флаг генерирует прерывание, если установлен бит TXEIE.

Можете ли вы представить сценарий, в котором вы записываете в регистр данных до того, как предыдущий байт попал в сдвиговый регистр? Подумайте о последовательности событий, когда вы получаете поток байтов, особенно когда вы получаете третий или четвертый байт, что в этот момент делает ваш передатчик? Занят ли его регистр данных?

Как упоминалось в предыдущем комментарии, вам, вероятно, понадобится способ буферизации предыдущих байтов, если бит состояния TXE не установлен. Запись в регистр данных USART, когда бит состояния TXE не установлен, является гарантированным способом потери информации.


Изменить в ответ на комментарий:

Это один из способов сделать это, я думаю, он сработает. Однако я думаю, что лучше отказаться от DMA и использовать прерывания USART. У вас может быть 2 подпрограммы обслуживания прерывания, когда байт получен и когда регистр передаваемых данных пуст. Основываясь на этих двух событиях, вы можете решить буферизовать данные (в обработчике RX) и очистить буфер (в обработчике завершения TX). Необходимо соблюдать осторожность при принятии решения о включении/отключении прерывания завершения передачи. Я думаю, что накладные расходы на настройку DMA имеют больше смысла для более крупных последовательных транзакций в (или из) буферов и меньше для случая одиночного байта за байтом.

хорошо, это имеет смысл. Но я не могу ждать внутри обратного вызова, пока не будет установлен txe. Итак, внутри обратного вызова я бы проверил, установлен ли txe, если нет, то я добавлю его в массив. Но мне нужно где-то еще проверить, установлен ли txe, чтобы я мог отправить данные? Могу ли я просто сделать это в основном цикле? т.е. проверить, есть ли буферизованные данные, и проверить, установлен ли txe, если да, то передать все в буфер?
@TomVandenBon - ответ был отредактирован, чтобы предложить подход

Это не то, как следует использовать DMA. Вы настраиваете 1-байтовые передачи DMA и реагируете на их завершение, что не имеет смысла, поскольку DMA предназначен для передачи блоков и потоков данных за кулисами, не слишком беспокоя его. По сути, то, что вы делаете сейчас, такое же, как получение байтов по одному в прерывании приема, только настройка DMA для каждого байта медленнее, чем просто использование прерывания приема, что приводит к потере символов.

Два решения. Один из них - не использовать DMA, просто использовать прерывания для каждого байта. Другой способ — использовать DMA в режиме двойного буфера с автоматическим перезапуском, но вам может понадобиться небольшой промежуточный буфер, так как я не уверен на 100%, что возможно использовать режим DMA между устройствами, где целевой адрес памяти не увеличивается (поэтому он может всегда указывать на адрес USART TXD). Или, может быть, он может с длиной передачи 1 до авторестарта.

Критика DMA — это одно, но реальность такова, что эту проблему нельзя безопасно решить, если нельзя гарантировать, что выходная скорость передачи данных выше, чем входная, включая любую ошибку передачи данных .

Я использую stm32f103 и пытаюсь просто передать все данные, полученные на 1 uart, на другой uart и наоборот.

Помимо проблем с реализацией, эту проблему нельзя решить надежно , если вы не можете гарантировать, что скорость поступления данных на каждый вход меньше, чем скорость, с которой вы можете сбрасывать данные на соответствующий выход. Для двунаправленной системы это особенно сложно.

Сначала давайте посмотрим, что произойдет, если вы попытаетесь сделать это без буферов. В этом случае, когда вы получаете байт из одного порта, вам нужно избавиться от него через другой, но вы не можете этого сделать, пока промежуточный регистр передачи не станет пустым. Если скорость ввода выше, чем скорость вывода - даже с небольшой ошибкой , то характер выборки приемника UART в конечном итоге приведет к тому, что он выдаст два байта с более близким интервалом во времени, чем его выходная скорость передачи данных может быть синхронизирована.

Вы можете добавить буфер, но это по-прежнему представляет ту же проблему в долгосрочной перспективе, если только вы не можете гарантировать, что будет пауза в данных, прежде чем одна сторона сможет получить данные, превышающие объем буфера, перед другой.

Если бы ваш поток был однонаправленным , простым решением было бы использовать более высокую скорость передачи на выходе, чем на входе. Некоторые устройства действительно могут поддерживать разные скорости передачи и приема, но неясно, смогут ли все, что есть в вашей системе.

Надеюсь, ваши потоки достаточно прерывисты, чтобы умеренная буферизация могла решить вашу проблему. В таком случае, вероятно, проще всего иметь выходной буфер и сразу помещать полученные байты в него в ISR, но есть и другие возможные схемы. DMA будет проще всего, если у вас есть промежутки для перевооружения; двойной круговой прямой доступ к памяти теоретически возможен, но его довольно сложно настроить.

Если этот режим работы предназначен только для «пассивного сквозного» режима работы, смутно возможно, что быстрая петля поляризации сможет копировать сигналы между GPIO без слишком большого джиттера, чтобы результат был разборчивым, особенно если вы могли бы иметь MCU больше ничего не делает, пока каким-то образом не сработает для выхода из этого режима. Предположительно, возможно даже скопировать с того же входа GPIO, который получает UART, чтобы отслеживать триггер выхода.