I would recommend using an ISR to handle each character that is received by the UART, and maintaining a state machine indicating the current reception status (e.g. waiting for SOF, reading data, waiting for EOF). This has the advantage that it will naturally re-synchronise with the data stream if it is ever interrupted, which is hard to do if you are DMAing fixed sized blobs of data and hoping they are aligned.
I'd also maintain a circular buffer of received messages, which the ISR is writing into. I'd then have my main "thread" loop reading messages out of this queue, processing them, and sending responses. This means that if a bunch of messages come in really quickly, and the processing can't keep up, you won't lose any.
Some commented (untested) code that hopefully shows the idea:
#define DATA_SIZE 4u // How many bytes in each message
#define NUM_MESSAGES 32u // How many messages can be in the queue
enum state_e {
STATE_WAIT_FOR_SOF,
STATE_DATA,
STATE_WAIT_FOR_EOF
};
struct message {
uint8_t data[DATA_SIZE];
};
// A circular buffer of messages
// Volatile since the ISR modifies them. (Probably not necessary, but harmless)
static volatile struct message msgs[NUM_MESSAGES];
// The index for the next entry to be written
// Volatile since the ISR can modify it
static volatile uint8_t head_ix = 0u;
// The index for the next entry to read
static uint8_t tail_ix = 0u;
// Start off waiting for SOF
static enum state_e state = STATE_WAIT_FOR_SOF;
// An index into the current message (being written)'s data[] array
static uint8_t data_ix;
static void process_msg(struct message *msg)
{
// Process msg and send response
}
int main(void)
{
// Any other initialisation happens...
while (1)
{
// Any messages in the queue? Queue is empty if head_ix == tail_ix.
if (tail_ix != head_ix)
{
// Consume a message
process_message(&msgs[tail_ix]);
tail_ix = (tail_ix + 1u) % NUM_MESSAGES;
}
}
}
// A ficticious interrupt handler that deals with a single character.
// Your real implementation will have to deal with reading the character
// from the UART, clearing the interrupt, etc, then call something like this.
void handle_rx_interrupt(uint8_t datum)
{
switch (state)
{
case STATE_WAIT_FOR_SOF:
if (datum == SOF)
{
// Got SOF, move to data state
state = STATE_DATA;
// Waiting for the first byte of data
data_ix = 0u;
}
break;
case STATE_DATA:
// Store this datum in the message
msgs[head_ix].data[data_ix] = datum;
++data_ix;
// If we have a full message, move to waiting for EOF state
if (data_ix == DATA_SIZE)
{
state = STATE_WAIT_FOR_EOF;
}
break;
case STATE_WAIT_FOR_EOF:
if (datum == EOF)
{
// See if we have space in the queue for this message
uint8_t new_head = (head_ix + 1u) % NUM_MESSAGES;
if (new_head != tail_ix)
{
// Yes, add this message
head_ix = new_head;
}
else
{
// No space for this message. It will just be overwritten
// in place by the next message
}
}
// No matter what happened, we're now waiting for SOF
state = STATE_WAIT_FOR_SOF;
break;
}
}
Note that in reality there can only ever be NUM_MESSAGES - 1 entries in the queue, due to how the full/empty condition works. The alternative (to reclaim the "wasted" entry) involves things like disabling interrupts in certain places etc, and adds more work for little gain.
I would recommend using an ISR to handle each character that is received by the UART, and maintaining a state machine indicating the current reception status (e.g. waiting for SOF, reading data, waiting for EOF). This has the advantage that it will naturally re-synchronise with the data stream if it is ever interrupted, which is hard to do if you are DMAing fixed sized blobs of data and hoping they are aligned.
I'd also maintain a circular buffer of received messages, which the ISR is writing into. I'd then have my main "thread" loop reading messages out of this queue, processing them, and sending responses. This means that if a bunch of messages come in really quickly, and the processing can't keep up, you won't lose any.
Some commented (untested) code that hopefully shows the idea:
#define DATA_SIZE 4u // How many bytes in each message
#define NUM_MESSAGES 32u // How many messages can be in the queue
enum state_e {
STATE_WAIT_FOR_SOF,
STATE_DATA,
STATE_WAIT_FOR_EOF
};
struct message {
uint8_t data[DATA_SIZE];
};
// A circular buffer of messages
// Volatile since the ISR modifies them. (Probably not necessary, but harmless)
static volatile struct message msgs[NUM_MESSAGES];
// The index for the next entry to be written
// Volatile since the ISR can modify it
static volatile uint8_t head_ix = 0u;
// The index for the next entry to read
static uint8_t tail_ix = 0u;
// Start off waiting for SOF
static enum state_e state = STATE_WAIT_FOR_SOF;
// An index into the current message (being written)'s data[] array
static uint8_t data_ix;
static void process_msg(struct message *msg)
{
// Process msg and send response
}
int main(void)
{
// Any other initialisation happens...
while (1)
{
// Any messages in the queue? Queue is empty if head_ix == tail_ix.
if (tail_ix != head_ix)
{
// Consume a message
process_message(&msgs[tail_ix]);
tail_ix = (tail_ix + 1u) % NUM_MESSAGES;
}
}
}
// A ficticious interrupt handler that deals with a single character.
// Your real implementation will have to deal with reading the character
// from the UART, clearing the interrupt, etc, then call something like this.
void handle_rx_interrupt(uint8_t datum)
{
switch (state)
{
case STATE_WAIT_FOR_SOF:
if (datum == SOF)
{
// Got SOF, move to data state
state = STATE_DATA;
// Waiting for the first byte of data
data_ix = 0u;
}
break;
case STATE_DATA:
// Store this datum in the message
msgs[head_ix].data[data_ix] = datum;
++data_ix;
// If we have a full message, move to waiting for EOF state
if (data_ix == DATA_SIZE)
{
state = STATE_WAIT_FOR_EOF;
}
break;
case STATE_WAIT_FOR_EOF:
if (datum == EOF)
{
// See if we have space in the queue for this message
uint8_t new_head = (head_ix + 1u) % NUM_MESSAGES;
if (new_head != tail_ix)
{
// Yes, add this message
head_ix = new_head;
}
else
{
// No space for this message. It will just be overwritten
// in place by the next message
}
}
// No matter what happened, we're now waiting for SOF
state = STATE_WAIT_FOR_SOF;
break;
}
}
Note that in reality there can only ever be NUM_MESSAGES - 1 entries in the queue, due to how the full/empty condition works. The alternative (to reclaim the "wasted" entry) involves things like disabling interrupts in certain places etc, and adds more work for little gain.
You can create a basic buffer handling data structure: a pointer to a buffer (or buffer array) and an index.
typedef struct{
int8_t* RxBuffer;
volatile uint8_t RxFirstEmptyIndex;
uint8_t RxBufferMaxLength;
}UART_RxBufferHandle;
Allocate some memory for RxBuffer (malloc or anything else), put max length into the appropriate variable.
Upon data received interrupt, you read UART data register into RxBuffer[RxFirstEmptyIndex], then increment RxFirstEmptyIndex by 1. If it reaches MaxLength (end of buffer), you need to handle it (otherwise USART peripheral will have overrun error and will refuse to work until you resolve it - read incoming data and clear error flag). After receiving N characters you can handle the input (for example, after receiving 6 characters - when RxFirstEmptyIndex becomes 6), and reset RxFirstEmptyIndex back to 0; Volatile because its value is changes in an interrupt.
You can create a global variable from the struct to make it visible for the interrupt handler. Or find some other solution for it.
You can utilize more or less similar mechanisms for other protocols and communications by adding/changing variables in such a struct. But buffer handling usually happens similar to this. Obviously, if you feel like 256 characters is too little, you're welcome to change index counter and length to uint16_t or uint32_t.
c - HAL STM32 Uart Receive Interrupt: Reset Receive Buffer Problem - Stack Overflow
c - How receive data with HAL_UART? - Stack Overflow
'Best' way to load UART data to ring buffer with STM32/HAL - Page 1
embedded - STM32 - HAL_UART_Receive first byte is always zero - Electrical Engineering Stack Exchange
Videos
Because it is interrupt routine it works in the background. And it will receeive data to this buffer until the end of the buffer.
If your data is not fixed length you should enable the "IDLE" interrupt and process the data in this interrupt handler (or better in HAL callback). Then restart the reception.
The problem can be solved by receiving data byte-by-byte using DMA.
char rx_buff[10];
HAL_UART_Receive_DMA(&huart1, (uint8_t*)rx_buff, 1);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
...
array[i++] = rx_buff[0];
/* Then reset buffer using bzero() or memset() */
}
AFAIK there is no similar function in HAL. The common way to do it is:
// index of first unread byte in the RX buffer
static uint32_t rxpos = 0;
// find end of data position in the RX buffer
uint32_t pos = rxsize - puart->hdmarx->Instance->CNDTR;
// pass accumulated bytes to listener
if (pos != rxpos) {
if (pos > rxpos) {
// Process data directly by subtracting indexes
processData(rxbuffer + rxpos, pos - rxpos);
} else {
// First process data to the end of the buffer
processData(rxbuffer + rxpos, rxsize - rxpos);
// Continue with beginning of the buffer
processData(rxbuffer, pos);
}
}
// update starting index
rxpos = (pos == rxsize)? 0 : pos;
You can call the above code from HAL_UART_RxHalfCpltCallback, HAL_UART_RxCpltCallback and, if you have code for processing either IDLE or RTO events, from corresponding handlers as well.
Note that current HAL implementation treats RTO as an error, eliminating the possibility to handle it properly. You'd have to bypass that if you want to get circular DMA transfers handled without added latency.
UPDATE
To answer your question in comments, please note that checking for available bytes in a buffer and using blocking HAL_UART_Receive call are two different approaches. You did not specify baud rate or whether or not you have variable message length. These two really define correct method of doing things in STM32.
But in general, you either need low latency communication, or you don't care how long it takes and your code has nothing to do meanwhile.
In the former case you have to use either interrupt or circular DMA versions of Receive function. With DMA you can check for available data length as described above.
In the latter you can do a loop calling blocking Receive which will either give you expected data or will time out. At low speeds you may even be better handling incoming data byte by byte in UART interrupt handler.
The methods available to you are quite different from Arduino approach. I would suggest to follow the examples available for HAL instead of trying to fit HAL functions into Arduino perspective.
Arduino environment does a lot of stuff behind the scenes to implement it. It sets up data reception to happen with interrupts that stores the received data into a buffer, which keeps state how many bytes there is in the buffer.
Obviously, no MCU library will not provide you with the exact same interface, for multiple reasons. One of the reasons is that you can just as easily implement the same concept yourself on any MCU, with or without any HAL. If you don't need a buffer or interrupts, you can simply use the HAL to see if has received a byte or not, and if it has then read the data. Or if you want similar what Arduino does, the concept is exactly the same - set up receive interrupts, store data into buffer, keep count how many bytes there are in the buffer.