hal_uart_transmit hal_uart_receive_it hal_uart_receive example stm32 stm32f4discovery hal

hal_uart_receive_it - STM32F4 UART HAL Driver



hal_uart_transmit example (6)

Estoy tratando de averiguar cómo usar este nuevo controlador HAL. Quiero recibir datos utilizando HAL_UART_Receive_IT() que configura el dispositivo para ejecutar una función de interrupción cuando se reciben datos.

El problema es que tiene que especificar la longitud de los datos para leer antes de que se desencadene la interrupción. Planeo enviar comandos de consola de longitud variable, por lo que no puedo tener una longitud fija. Supongo que la única forma de hacerlo sería leer caracteres individuales a la vez y crear una cadena separada.

El controlador HAL parece tener un problema en el que si configura HAL_UART_Receive_IT() para recibir x número de caracteres y luego intenta enviar más de x caracteres, se producirá un error.

Actualmente no tengo idea de si lo estoy haciendo de la manera correcta, ¿alguna idea?


Decidí ir con DMA para que la recepción funcionara. Estoy usando un búfer circular de 1 byte para manejar los datos a medida que se escriben en el terminal serial del transmisor. Aquí está mi código final (solo la parte de recepción, más información sobre la transmisión en la parte inferior).

Algunas definiciones y variables:

#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

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

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

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

Configurar la interrupción DMA:

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

Iniciar DMA:

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

DMA recibir devolución de llamada:

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

Así que eso es prácticamente todo el código para recibir caracteres y construir una cadena (matriz de caracteres) que muestre lo que el usuario ha ingresado. Si el usuario pulsa la tecla de retroceso o delimitador, el último carácter de la matriz se sobrescribe y si pulsa enter, esa matriz se envía a otra función y se procesa como un comando.

Para ver cómo funciona el comando de análisis y transmisión de código, vea mi proyecto Here

Gracias a @Flip y @Dormen por sus sugerencias!


La recepción de datos mientras el Registro de datos (DR) está lleno dará como resultado un error de saturación. El problema es que la función UART_Receive_IT(UART_HandleTypeDef*) dejará de leer el registro de DR una vez que haya recibido suficientes datos. Cualquier dato nuevo causará el error de saturación.

Lo que hice fue usar una estructura circular de recepción DMA. Luego puede usar currentPosInBuffer - uart->hdmarx->Instance->NDTR para determinar la cantidad de datos recibidos que aún no ha procesado.

Es un poco más complicado porque mientras el DMA realiza el búfer circular en sí mismo, debe implementar manualmente el bucle de retorno al principio si pasa el final del búfer.

También encontré una falla donde el controlador dice que ha transferido los datos (es decir, NDTR ha disminuido) pero los datos aún no están en el búfer. Puede ser un problema de contención de acceso a DMA / bus, pero es molesto.


Los controladores STM32 UART son un poco torcidos. La única forma de que funcionen fuera de la caja es si conoce la cantidad exacta de caracteres que va a recibir. Si desea recibir un número no especificado de caracteres, hay un par de soluciones que he encontrado y probado:

  1. Establezca la cantidad de caracteres para recibir en 1 y cree una cadena separada. Esto funciona pero tiene problemas al recibir datos muy rápido, porque cada vez que el controlador lee el rxBuffer desactiva la interrupción, por lo que algunos caracteres se pueden perder.

  2. Establezca la cantidad de caracteres para recibir al mayor tamaño de mensaje posible e implemente un tiempo de espera, después del cual se leerá todo el mensaje.

  3. Escriba su propia función UART_Receive_IT, que escribe directamente en un búfer circular. Esto es más trabajo, pero es lo que encontré que funciona mejor al final. Sin embargo, tiene que cambiar algunos de los controladores hal, por lo que el código es menos portátil.

Otra forma es usar DMA como sugirió @Flip.


Por lo general escribí mi propia implementación de búfer circular UART. Como se dijo antes, las funciones de interrupción UART de la biblioteca STM32 HAL son un poco extrañas. Puede escribir su propio búfer circular con solo 2 matrices y punteros utilizando indicadores de interrupción UART.


Tenga un parche de enfoque diferente, por ejemplo, "void USART2_IRQHandler (void)" en el archivo "stm32l0xx_it.c" (o l4xx según sea necesario). Cada vez que se recibe un personaje se llama a esta interrupción. Hay espacio para insertar el código de usuario que se mantiene sin cambios cuando se actualiza con el generador de código CubeMX. Parche:

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 */ }

Suministro un búfer de caracteres pequeños y comienzo la función de recepción de TI. Hasta 115200 baudios, nunca consumió más de 1 byte dejando el resto del búfer sin usar.

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

Cuando recibo un byte, lo capturo y lo coloco en mi propio búfer de anillo y pongo el puntero y el contador de caracteres atrás:

// 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 } } }

Este método resultó ser bastante rápido. Recibir solo un byte utilizando IT o DMA siempre se inicializa y necesita inicializar nuevamente el proceso de recepción, lo que resultó ser demasiado lento. El código de arriba es solo un marco; Solía ​​contar los caracteres de nueva línea aquí en una estructura de estado que me permite en cualquier momento leer las líneas completadas desde el búfer de anillo. También debe verificarse si un carácter recibido o algún otro evento causado la interrupción debe incluirse.
EDITAR:
Este método demostró funcionar bien con USARTS que no son compatibles con DMA y, en su lugar, utilizan TI. El uso de DMA con 1 byte en modo circular es más corto y más fácil de implementar cuando se usa el generador CubeMX con la biblioteca HAL.


Tuve que enfrentar el mismo problema en mi proyecto. Lo que hice fue comenzar a leer 1 byte con HAL_USART_Receive_IT() justo después de la inicialización periférica.

Luego escribí una devolución de llamada en la transferencia completa que coloca el byte en un búfer, establece una marca si el comando está completo y luego llama a HAL_USART_Receive_IT() nuevamente para otro byte.

Parece que funciona bien para mí ya que recibo comandos a través del USART cuyo primer byte me dice cuántos bytes más va a ser el comando. Tal vez podría funcionar para ti también!