--- title: UART Manager author: Pat Beirne email: date: 2025/01/30 licence: MIT --- [Back](CooperativeMultitasking.html) > 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.
Reality Check I recommend sending ASCII chars through the UART. It makes debugging ***way*** easier. For some of my projects, I use fixed-length packets, where the first characters determine the packet length. For example: **Master -> UART -> Slave (this processor) ** | command | syntax | meaning | | --- | --- | --- | | light LED | Lnf | set LED {n} either {f==0} off or {f==1} on | | beep | Bn | issue a beep for {n} x100 ms | | inject key press | kn | simulate key {n} press | | inject key release | Kn | simulate key {n} release | **Slave (this processor) -> UART -> Master ** | report | syntax | meaning | | --- | --- | --- | | keypress | kn | key {n} is pressed | | keyrelease | Kn | key {n} is released | | debug string | Dnxxxxxxxx | general debugging, of length {n} | | init finished | I | the startup process is finished, processor ready | > {n} is one of `"0123456789ABCDE...XYZabcd.....xyz"`
We create some buffers in RAM to hold the characters until they can be sent or processed: ```C #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: ```C 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: ```C #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; ```
STM32F0xx specific init code ```C void initUart(void) { USART2->CR2 = 0; // no auto baud on USART2 :( USART2->CR3 = 0; // USART2->BRR = 0x341; // from 8MHz clock 16x oversample USART2->BRR = 0x44; // for 115200 USART2->CR1 = 0x016AD; // 8 bits, even parity, tx/rx/usart enable + tx/rx interrupts NVIC->ISER[0] |= (1<<28); // and enable the NVIC } ```
```C 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; } ```