I decided to go with DMA to get the receive working. I'm using a 1 byte circular buffer to handle data as it is typed on the transmitter's serial terminal. Here's my final code (only the receive part, more info on transmit at the bottom).
Some defines and 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
Set up 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);
Set up the 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);
Set up 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);
Set up DMA interrupt:
extern DMA_HandleTypeDef hdma_usart1_rx;
void DMA2_Stream2_IRQHandler(void)
{
HAL_NVIC_ClearPendingIRQ(DMA2_Stream2_IRQn);
HAL_DMA_IRQHandler(&hdma_usart1_rx);
}
Start DMA:
__HAL_UART_FLUSH_DRREGISTER(&huart1);
HAL_UART_Receive_DMA(&huart1, &rxBuffer, 1);
DMA receive callback:
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> ");
}
}
}
So that's pretty much all the code to receive characters and build a string (char array) that shows what the user has entered. If the user hits backspace or del, the last character in the array is overwritten and if they hit enter, that array is sent to another function and processed as a command.
To see how the command parsing and transmit code works, see my project Here
Thanks to @Flip and @Dormen for their suggestions!