STM32F4 UART HAL драйвер

Я пытаюсь понять, как использовать этот новый драйвер HAL. Я хочу получать данные с помощью HAL_UART_Receive_IT(), который настраивает устройство для выполнения функции прерывания при получении данных.

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

у драйвера HAL, похоже, есть проблема, где, если вы установите HAL_UART_Receive_IT() для получения x количество символов, а затем попробуйте отправить больше, чем x символы, будет ошибка.

В настоящее время я понятия не имею, правильно ли я это делаю, есть идеи?

6 ответов


я решил пойти с DMA, чтобы получить работу приема. Я использую круговой буфер 1 байт для обработки данных, как он набирается на последовательном терминале передатчика. Вот мой окончательный код (только часть приема, дополнительная информация о передаче внизу).

некоторые определяет и переменные:

#define BAUDRATE              9600
#define TXPIN                 GPIO_PIN_6
#define RXPIN                 GPIO_PIN_7
#define DATAPORT              GPIOB
#define UART_PRIORITY         6
#define UART_RX_SUBPRIORITY   0
#define MAXCLISTRING          100 // Biggest string the user will type

uint8_t rxBuffer = '0'; // where we store that one character that just came in
uint8_t rxString[MAXCLISTRING]; // where we build our string from characters coming in
int rxindex = 0; // index for going though rxString

настройки ввода-вывода:

__GPIOB_CLK_ENABLE();
__USART1_CLK_ENABLE();
__DMA2_CLK_ENABLE();

GPIO_InitTypeDef GPIO_InitStruct;

GPIO_InitStruct.Pin = TXPIN | RXPIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(DATAPORT, &GPIO_InitStruct);

настройка UART:

UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;

huart1.Instance = USART1;
huart1.Init.BaudRate = BAUDRATE;
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);

настройка DMA:

extern DMA_HandleTypeDef hdma_usart1_rx; // assuming this is in a different file

hdma_usart1_rx.Instance = DMA2_Stream2;
hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_rx.Init.MemInc = DMA_MINC_DISABLE;
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW;
hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&hdma_usart1_rx);

__HAL_LINKDMA(huart, hdmarx, hdma_usart1_rx);

HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, UART_PRIORITY, UART_RX_SUBPRIORITY);
HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);

настройка прерывания DMA:

extern DMA_HandleTypeDef hdma_usart1_rx;

void DMA2_Stream2_IRQHandler(void)
{
    HAL_NVIC_ClearPendingIRQ(DMA2_Stream2_IRQn);
    HAL_DMA_IRQHandler(&hdma_usart1_rx);
}

старт DMA:

__HAL_UART_FLUSH_DRREGISTER(&huart1);
HAL_UART_Receive_DMA(&huart1, &rxBuffer, 1);

DMA получить обратный вызов:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    __HAL_UART_FLUSH_DRREGISTER(&huart1); // Clear the buffer to prevent overrun

    int i = 0;

    print(&rxBuffer); // Echo the character that caused this callback so the user can see what they are typing

    if (rxBuffer == 8 || rxBuffer == 127) // If Backspace or del
    {
        print(" \b"); // "\b space \b" clears the terminal character. Remember we just echoced a \b so don't need another one here, just space and \b
        rxindex--; 
        if (rxindex < 0) rxindex = 0;
    }

    else if (rxBuffer == '\n' || rxBuffer == '\r') // If Enter
    {
        executeSerialCommand(rxString);
        rxString[rxindex] = 0;
        rxindex = 0;
        for (i = 0; i < MAXCLISTRING; i++) rxString[i] = 0; // Clear the string buffer
    }

    else
    {
        rxString[rxindex] = rxBuffer; // Add that character to the string
        rxindex++;
        if (rxindex > MAXCLISTRING) // User typing too much, we can't have commands that big
        {
            rxindex = 0;
            for (i = 0; i < MAXCLISTRING; i++) rxString[i] = 0; // Clear the string buffer
            print("\r\nConsole> ");
        }
    }
}

так что это почти весь код для получения символов и создания строки (массива символов), которая показывает, что ввел пользователь. Если пользователь нажимает backspace или del, последний символ в массиве перезаписывается, и если они нажимают enter, этот массив отправляется другой функции и обрабатывается как команда.

чтобы увидеть, как работает синтаксический анализ и передача кода команды, см. Мой проект здесь

спасибо @Flip и @Dormen за их предложения!


получение данных при заполнении регистра данных (DR) приведет к ошибке переполнения. Проблема в том, что функция UART_Receive_IT(UART_HandleTypeDef*) прекратит чтение регистра DR, как только он получит достаточно данных. Любые новые данные вызовут ошибку переполнения.

то, что я сделал, было скорее использовать круговую структуру приема DMA. Затем вы можете использовать currentPosInBuffer - uart->hdmarx->Instance->NDTR чтобы определить, сколько данных было получено, что вам еще не обработаны.

Это немного сложнее, потому что пока DMA выполняет циклическую буферизацию сама, вы должны вручную реализовать цикл до начала, если вы пройдете мимо конца буфера.

Я также нашел глюк, когда контроллер говорит, что он передал данные (т. е. NDTR уменьшилось) , но данные еще не находятся в буфере. Это может быть какая-то проблема с доступом к DMA/bus, но это раздражает.


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

  1. установите количество символов для получения 1 и создайте отдельную строку. Это работает, но имеет проблемы при получении данных очень быстро, потому что каждый раз, когда водитель читает rxBuffer это dissables прерываний, так что некоторые символы могут быть потеряны.

  2. установите количество символов для получения максимально возможного размера сообщения и реализуйте тайм-аут, после которого будет прочитано все сообщение.

  3. напишите свою собственную функцию UART_Receive_IT, которая записывает непосредственно в круговой буфер. Это больше работы, но это то, что я нашел работает лучше всего. Однако вам нужно изменить некоторые драйверы hal, поэтому код менее портативный.

другой способ-использовать DMA, как @Flip.


мне пришлось столкнуться с той же проблемой в моем проекте. Что я сделал, так это начал читать 1 байт с HAL_USART_Receive_IT() сразу после инициализации периферии.

затем я написал обратный вызов при передаче complete, который помещает байт в буфер, устанавливает флаг, если команда завершена, а затем вызывает HAL_USART_Receive_IT() снова для другого байта.

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


обычно я писал свою собственную реализацию кругового буфера UART. Как уже говорилось ранее, функции прерывания UART библиотеки STM32 HAL немного странные. Вы можете написать свой собственный круговой буфер всего с 2 массивами и указателями, используя флаги прерывания UART.


имеют другой подход, например, исправление " void USART2_IRQHandler (void)" в файле "stm32l0xx_it.c " (или l4xx по мере необходимости). Каждый раз, когда символ получен, это прерывание вызывается. Существует пространство для вставки пользовательского кода, который остается неизменным при обновлении с генератором кода CubeMX. Патч:

void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */

  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */
  usart_irqHandler_callback( &huart2 ); // patch: call to my function 
  /* USER CODE END USART2_IRQn 1 */
}

Я предоставляю небольшой символьный буфер и запускаю функцию receive IT. До 115200 бод он никогда не потреблял более 1 байта, оставляя остальную часть буфера неиспользованный.

st = HAL_UART_Receive_IT( &huart2, (uint8_t*)rx2BufIT, RX_BUF_IT_SIZE );

при получении байта я захватываю его и помещаю в свой собственный кольцевой буфер и устанавливаю символьный указатель и счетчик:

// placed in my own source-code module:
void usart_irqHandler_callback( UART_HandleTypeDef* huart ) {
  HAL_UART_StateTypeDef  st;
  uint8_t c;
  if(huart->Instance==USART2) {
    if( huart->RxXferCount >= RX_BUF_IT_SIZE ) {
      rx2rb.err = 2;           // error: IT buffer overflow
    }
    else {
      huart->pRxBuffPtr--;     // point back to just received char
      c = (uint8_t) *huart->pRxBuffPtr; // newly received char
      ringbuf_in( &rx2rb, c ); // put c in rx ring-buffer
      huart2.RxXferCount++;    // increment xfer-counter avoids end of rx
    }
  }
}

этот метод оказался довольно быстро. Получение только одного байта с его помощью или DMA всегда деинициализируется и требует инициализации процесса получения снова, который оказался слишком медленным. Код выше - это только фрейм; я использовал для подсчета символов новой строки здесь в структуре состояния, которая позволяет мне в любое время читать завершенные строки из кольцевого буфера. Также проверка, если полученный символ или какое-либо другое событие вызвало прерывание должны быть включены.
EDIT:
Этот метод оказался хорошо работать с USARTS, которые не поддерживаются DMA и использовать его вместо этого. Использование DMA с 1 байтом в круговом режиме короче и проще реализовать при использовании генератора CubeMX с библиотекой HAL.