title: UART Manager author: Pat Beirne email: patb@pbeirne.com date: 2025/01/30
This is not a state machine, per se. This page is included because I found it very common to need UART communication, and I found I could fit it into the event-dispatcher system pretty easily.
Suppose we have a project which needs to use the UART to send and receive information. The outbound stream contains diagnostic information and measured parameters; the report stream is composed of chunks of up to 100 bytes at a time. Output reports can be generated by any task, and the chunks of data must not be interleaved.
The input stream consists of command chunks of 1-10 bytes, used for testing, for remote control and for injecting input stimuli.
When data arrives (a command), all tasks are informed with an
EVT_CHAR
. The tasks can then query the UART service routines to see if they need to process the input.
We create some buffers in RAM to hold the characters until they can be sent or processed:
#define TX_SIZE 256
#define RX_SIZE 32
/* circular buffers */
char tx_buffer[TX_SIZE];
char *tx_next_empty = tx_buffer, *tx_next_send = tx_buffer;
const char * const tx_end = tx_buffer + TX_SIZE;
char rx_buffer[RX_SIZE];
char *rx_next_empty = rx_buffer, *rx_next_rcv = rx_buffer;
const char * const rx_end = rx_buffer + RX_SIZE;
This task will have a few service routines which any task can access at any time:
const char uartGetChar(void); // returns -1 for no-char-available
const char uartCheckChar(void); // leave char in buffer
const int uartGetCharCount(void);
void uartDiscardChars(int n);
const char uartSendChar(char c); // return -1 if buffer is full
const int uartSendString(const char* p, int len);
The UART itself operates at the interrupt level; we get distinct interrupts for rx data ready and tx buffer empty.
The uartSendChar()
pushes a char into the tx buffer. The first character
inserted causes the UART hardware to send; subsequent chars are simply
pushed into the tx_buffer
. When the UART is finished
sending, it triggers an interrupt which checks the tx_buffer
for more
bytes and sends them, until the buffer is emptied.
Characters are inserted into the tx_buffer
in order, and
as long as the entire packet of data is inserted during the same
task process (EVT_TICK or whatever), then those bytes will remain adjacent.
The rx_buffer
is filled automatically by the rx interrupt. The rx interrupt
also generates the EVT_CHAR
message to inform all the tasks that rx data
is ready.
The uartGetChar()
checks the rx buffer, and returns a char if one is available
(-1 otherwise). The uartCheckChar()
and uartGetCharCount()
let the designer check the first character of a command string, decide if
it is relevant to a specific task, plus determine
whether the command has been fully loaded into the rxBuffer.
Here is a fully functional module that implements the above:
#define TX_SIZE 256
#define RX_SIZE 32
/* circular buffers */
char tx_buffer[TX_SIZE];
char *tx_next_empty = tx_buffer, *tx_next_send = tx_buffer;
const char * const tx_end = tx_buffer + TX_SIZE;
char rx_buffer[RX_SIZE];
char *rx_next_empty = rx_buffer, *rx_next_rcv = rx_buffer;
const char * const rx_end = rx_buffer + RX_SIZE;
const char uartGetChar(void) {
if (rx_next_rcv == rx_next_empty)
return -1; // no char available
else {
char c = *rx_next_rcv++;
if (rx_next_rcv >= rx_end)
rx_next_rcv = rx_buffer; // wrap to beginning
return c;
}
}
const char uartCheckChar(void) {
if (rx_next_rcv == rx_next_empty)
return -1; // no char available
else
return *rx_next_rcv;
}
int uartGetCharCount(void) {
int ret = rx_next_empty - rx_next_rcv;
if (ret<0)
ret += RX_SIZE;
return ret;
}
void uartDiscardChar(int i) {
while (i--)
uartGetChar();
}
/** add character to the tx buffer
* @return -1 if the buffer is full
* */
char uartSendChar(char c) {
*tx_next_empty = c; // increment AFTER adding to queue
if (++tx_next_empty >= tx_end)
tx_next_empty = tx_buffer;
if (tx_next_empty == tx_next_send)
c = -1;
// if the USART is empty, trigger the first send
if (USART2->ISR & 0x80)
USART2->CR1 |= 0x80; // enable the tx interrupt
return c;
}
/** send a string a specified length out the uart */
void uartSendString(const char* p, int len) {
while (len--)
uartSendChar(*p++);
}
/* ============ INTERRUPTS =================== */
/* the for a RX character
* deposit it into the buffer, issue a EVT_CHAR and return
*/
void uart_rx_isr(void) {
// rx data is ready, read it.....status bit auto-clears
*rx_next_empty++ = USART2->RDR;
if (rx_next_empty >= rx_end) // wrap back to beginning
rx_next_empty = rx_buffer;
newEvent(EVT_CHAR); // inform all tasks that there is
// something in the rx buffer
}
void uart_tx_isr(void) {
// the uart tx is empty
// if there is tx data available to send, push it into the uart
if (tx_next_send != tx_next_empty) {
USART2->TDR = *tx_next_send;
if (++tx_next_send >= tx_end) // wrap back to beginning
tx_next_send = tx_buffer;
}
// else, just disable the interrupt
else
USART2->CR1 &= ~0x80;
}