STM32F4 драйвер UART HAL
Я пытаюсь понять, как использовать этот новый драйвер HAL. Я хочу получать данные с помощью HAL_UART_Receive_IT()
, который устанавливает устройство для запуска функции прерывания при получении данных.
Проблема заключается в том, что вы должны указать длину данных для чтения перед триггерами прерываний. Я планирую отправлять консоль, как команды различной длины, поэтому не может иметь фиксированную длину. Я предполагаю, что единственный способ сделать это - прочитать отдельные символы за раз и создать отдельную строку.
У драйвера HAL возникает проблема: если вы установите HAL_UART_Receive_IT()
для получения x
количества символов, а затем попытайтесь отправить больше, чем x
символов, будет ошибка.
В настоящее время я понятия не имею, правильно ли я подойду, какие идеи?
Ответы
Ответ 1
Я решил пойти с 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 = '\000'; // 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
Настройка IO:
__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> ");
}
}
}
Так что почти весь код для приема символов и построения строки (массив char) показывает, что пользователь ввел. Если пользователь нажимает backspace или del, последний символ в массиве перезаписывается, и если они попадают в enter, этот массив отправляется в другую функцию и обрабатывается как команда.
Чтобы узнать, как работает синтаксический анализ и передача кода, см. мой проект Здесь
Благодаря @Flip и @Dormen за их предложения!
Ответ 2
Получение данных при заполнении регистра данных (DR) приведет к ошибке переполнения. Проблема в том, что функция UART_Receive_IT(UART_HandleTypeDef*)
перестанет считывать регистр DR после получения достаточного количества данных. Любые новые данные вызовут ошибку переполнения.
То, что я сделал, было скорее использовать круговую структуру приема DMA. Затем вы можете использовать currentPosInBuffer - uart->hdmarx->Instance->NDTR
, чтобы определить, сколько данных получено, которое вы еще не обработали.
Это немного сложнее, потому что, в то время как DMA выполняет круговую буферизацию, вам нужно вручную реализовать loopback в начале, если вы пройдете мимо конца буфера.
Я также обнаружил сбой, когда контроллер говорит, что он передал данные (т.е. NDTR
уменьшился), но данные еще не находятся в буфере. Это может быть проблема с конфликтом доступа к DMA/шине, но это раздражает.
Ответ 3
Драйверы START START UART немного отстают. Единственный способ, которым они работают из коробки, - это знать точное количество персонажей, которые вы собираетесь получить. Если вы хотите получить неуказанное количество символов, есть несколько решений, с которыми я столкнулся и попытался:
-
Задайте количество символов для приема в 1 и постройте отдельную строку. Это работает, но имеет проблемы при получении данных очень быстро, потому что каждый раз, когда драйвер читает rxBuffer, он прерывает прерывание, поэтому некоторые символы могут быть потеряны.
-
Задайте количество символов для получения максимального размера сообщения и выполните тайм-аут, после которого будет прочитано все сообщение.
-
Напишите свою собственную функцию UART_Receive_IT, которая записывается непосредственно в круговой буфер. Это больше работает, но в итоге я нашел лучшие результаты. Однако вам нужно изменить некоторые из драйверов hal, поэтому код менее портативен.
Другой способ - использовать DMA, например, @Flip.
Ответ 4
Мне пришлось столкнуться с той же проблемой в моем проекте.
Я начал читать 1 байт с HAL_USART_Receive_IT()
сразу после инициализации периферии.
Затем я написал обратный вызов по завершению передачи, который помещает байт в буфер, устанавливает флаг, если команда завершена, а затем снова вызывает HAL_USART_Receive_IT()
для другого байта.
Кажется, это сработало для меня, так как я получаю команды через USART, чей первый байт сообщает мне, сколько еще байтов команда будет длинной.
Возможно, это может сработать и для вас!
Ответ 5
Использовать другой подход, например, "void USART2_IRQHandler (void)" в файле "stm32l0xx_it.c" (или l4xx при необходимости). Каждый раз, когда принимается символ, это прерывание вызывается. Существует пространство для вставки кода пользователя, который остается неизменным при обновлении с помощью генератора кода CubeMX. Patch:
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 */
}
Я поставляю небольшой буфер символов и запускаю функцию приема. До 115200 бод он никогда не потреблял более 1 байт, оставив остальную часть буфера неиспользованным.
st = HAL_UART_Receive_IT( &huart2, (uint8_t*)rx2BufIT, RX_BUF_IT_SIZE );
При получении байта я беру его и помещаю в свой собственный кольцевой буфер и устанавливаю указатель на символ и -counter обратно:
// 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.
Ответ 6
Обычно я написал свою собственную циклическую буферизацию UART. Как уже говорилось, функции прерывания UART библиотеки STM32 HAL немного странны.
Вы можете написать свой собственный круговой буфер с помощью всего 2 массивов и указателей с использованием флагов прерывания UART.