La guía paso a paso o código que verás en este artículo es la resolución más fácil y efectiva que encontramos a esta inquietud o dilema.
Solución:
Decidí ir con DMA para que la recepción funcionara. Estoy usando un búfer circular de 1 byte para manejar los datos tal como están escritos en el terminal serial del transmisor. Aquí está mi código final (solo la parte de recepción, más información sobre 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 = ' 00'; // 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);
Configure la 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);
Configure 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 recibe devolución de llamada:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
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("rnConsole> ");
Eso es prácticamente todo el código para recibir caracteres y crear un string (carbonizarse array) que muestra lo que ha introducido el usuario. Si el usuario pulsa la tecla de retroceso o del, el último carácter de la array se sobrescribe y si presionan enter, ese array 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 aquí
¡Gracias a @Flip y @Dormen por sus sugerencias!
Recibir datos mientras el Registro de datos (DR) está lleno resultará en un error de saturación. El problema es que la función UART_Receive_IT(UART_HandleTypeDef*)
dejará de leer el registro DR una vez que haya recibido suficientes datos. Cualquier dato nuevo provocará el error de saturación.
Lo que hice fue utilizar una estructura de recepción circular DMA. A continuación, puede utilizar currentPosInBuffer - uart->hdmarx->Instance->NDTR
para determinar cuántos datos se recibieron que aún no ha procesado.
Es un poco más complicado porque, si bien el DMA realiza el almacenamiento en búfer circular, debe implementar manualmente el bucle de retorno al principio si pasa del final del búfer.
También encontré una falla en la que 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 al bus / DMA, pero es molesto.
Los controladores STM32 UART son un poco inestables. La única forma en que funcionan de inmediato es si conoce la cantidad exacta de caracteres que va a recibir. Si desea recibir una cantidad no especificada de caracteres, hay un par de soluciones que encontré y probé:
-
Establezca la cantidad de caracteres a recibir en 1 y construya una string. 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 se pueden perder algunos caracteres.
-
Establezca la cantidad de caracteres que se recibirán con el mayor tamaño de mensaje posible e implemente un tiempo de espera, después del cual se leerá todo el mensaje.
-
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, debe cambiar algunos de los controladores hal, por lo que el código es menos portátil.
Otra forma es usar DMA como sugirió @Flip.