I'm aware of volatile keyboard and it doesn't ensure synchronization safety.
The following code is used inside an interrupt routine, and I'm using heavily the function
GetCharUART inside the main loop.
Is it safe to and stable to write code like that ? or I have to go to low level synchronization like a mutex, if that's true, how would I protect that rbuf ?
volatile char rbuf[5][UART_BUFSIZE];
vu16 rin[5] = { 0, 0, 0, 0, 0 };
vu16 rout[5] = { 0, 0, 0, 0, 0 };
void USART_IRQHandler(char Channel, USART_TypeDef *USARTx)
{
volatile unsigned int IIR;
IIR = USARTx->SR;
if (IIR & USART_FLAG_RXNE)
{ // read interrupt
USARTx->SR &= ~USART_FLAG_RXNE; // clear interrupt
rbuf[Channel][rin[Channel]] = USART_ReceiveData(USARTx);
rin[Channel]++;
if(rin[Channel]>=UART_BUFSIZE) rin[Channel]=0;
}
if (IIR & USART_FLAG_TXE)
{
USARTx->SR &= ~USART_FLAG_TXE; // clear interrupt
}
}
int GetCharUART (char Channel)
{
int result;
if (rin[Channel]==rout[Channel]) {
return EMPTY;
}
result=rbuf[Channel][rout[Channel]];
rout[Channel]++;
if(rout[Channel]>=UART_BUFSIZE)
rout[Channel]=0;
return result;
}
Modified code:
void USART_IRQHandler(char Channel, USART_TypeDef *USARTx)
{
volatile unsigned int IIR;
IIR = USARTx->SR;
if (IIR & USART_FLAG_RXNE)
{ // read interrupt
USARTx->SR &= ~USART_FLAG_RXNE; // clear interrupt
rbuf[Channel][rin[Channel]% UART_BUFSIZE] = USART_ReceiveData(USARTx);
rin[Channel]++;
}
if (IIR & USART_FLAG_TXE)
{
USARTx->SR &= ~USART_FLAG_TXE; // clear interrupt
}
}
/******************************************************************************/
int GetCharUART (char Channel)
{
int result;
if (rin[Channel]==rout[Channel]) {
return EMPTY;
}
result=rbuf[Channel][rout[Channel]% UART_BUFSIZE];
rout[Channel]++;
return result;
}
Your code has a functional bug.
In the ISR you don't check for a "buffer full" condition. The code just increments rin[Channel] without looking at rout[Channel]. So a whole buffer of data can be lost.
Example: If rout[Channel] equals zero and rin[Channel] equals UART_BUFSIZE-1 then the ISR will set rin[Channel] to zero. In other words the buffer will now appear empty and data is lost.
So the first step is to fix the code.
Rather than trying to make data structures that would work synchronously work with interrupts, it's best to use a different data structure that actually is safe for use in interrupts.
It is common for circular/ring buffers to be used for data structures that need to be shared between regular code and interrupt code.
https://en.wikipedia.org/wiki/Circular_buffer
A circular buffer is implemented with two indices, and each side is only modifying one of the indices, which has a sort of reader/writer style. This allows it to be pushed onto and popped off by concurrent executions.
Because of the dual indices and the fact the indices are always incremented in one direction, the worst case that could happen is that the interrupt dropped a byte when the buffer is almost full because it didn't pick up on a recent change to the other index, which is not a big deal.
If you make your own implementation, remember to test it, because it can be easy to mess up.
Related
Here is a scenario I'm facing right now, I have an interrupt(thread) UART that is reading to a ringbuffer from the values that I get from the serial port, and also writing the values from the serial port to the ring buffer.
I have a main loop that access that ringbuffer for reading the values from it, while writing an AT Command, and also writing to the ring buffer those AT Commands.
Do I need the ringbuffer to be lock free or surround the shared data with a semaphore or a mutex ? I don't have an OS for getting a mutex or semaphore working.
I have read alot about the subject and it seems I need a lock free ringbuffer. On ARM I would use a compare and swap instruction. The ringbuffer is implemented as an array so I wouldn't run into ABA problem
Declaration of buffers:
#define MAX_CHANNEL_COUNT 5
#define UART_BUFSIZE 512
char buffers[2][MAX_CHANNEL_COUNT][UART_BUFSIZE];
char* writeBuffers[MAX_CHANNEL_COUNT];
char* readBuffers[MAX_CHANNEL_COUNT];
volatile int readPos[MAX_CHANNEL_COUNT] = { 0 };
volatile int writePos[MAX_CHANNEL_COUNT] = { 0 };
here is the interrupt code
void USART_IRQHandler(char Channel, USART_TypeDef *USARTx)
{
volatile unsigned int IIR;
int c = 0;
IIR = USARTx->SR;
if (IIR & USART_FLAG_RXNE)
{ // read interrupt
USARTx->SR &= ~USART_FLAG_RXNE; // clear interrupt
c = USART_ReceiveData(USARTx);
writeBuffers[Channel][writePos[Channel]] = c;
writePos[Channel]++;
if(writePos[Channel]>=UART_BUFSIZE) writePos[Channel]=0;
}
if (IIR & USART_FLAG_TXE)
{
USARTx->SR &= ~USART_FLAG_TXE; // clear interrupt
}
}
code for initializing and swapping the buffers:
void initializeBuffers(void) {
int i = 0;
for (i = 0; i < MAX_CHANNEL_COUNT; ++i)
{
writeBuffers[i] = buffers[0][i];
readBuffers[i] = buffers[1][i];
}
}
void swapBuffers(int channel) {
int i;
char * buf = writeBuffers[channel];
__disable_irq();
writeBuffers[channel] = readBuffers[channel];
readBuffers[channel] = buf;
if ( readPos[channel] == UART_BUFSIZE)
readPos[channel] = 0;
for (i =0; i < UART_BUFSIZE; i++)
{
buf[i] = 0;
}
__enable_irq();
}
here I use this function to get a char from a specific channel and from a specific UART
int GetCharUART (char Channel)
{
int c = readBuffers[Channel][readPos[Channel]++];
if (c == 0 || readPos[Channel] == UART_BUFSIZE)
{
swapBuffers(Channel); // Make this clear your read buffer.
return EMPTY;
}
return c; // Note, your code that calls this should handle the case where c == 0
}
usage of GetCharUart
PutStringUART(UART_GSM, "AT");
PutStringUART(UART_GSM, pCommand);
PutCharUART(UART_GSM, '\r');
count = 0;
timer_100ms = 0;
while (timer_100ms <= timeout)
{
zeichen = GetCharUART(UART_GSM);
}
You need synchronization between interrupt handlers and the "main-thread", so yes a sort of mutex is needed. The usual method without OS, is to just disable interrupts before "entering the critical section" and re-enable afterwards. That ensures the critical section is "atomic" (i.e. no interrupts fire in the meantime).
On ARM I would use a compare and swap instruction
What compiler are you using?
GCC and Clang has intrinsics for atomic compare-and-swap.
See e.g.
https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html
https://llvm.org/docs/Atomics.html
C11 supports it via <stdatomic.h> I believe: https://en.cppreference.com/w/c/atomic/atomic_compare_exchange
Conceptually, I would recommend splitting the ring buffer into a reading and a writing buffer (can be a ring buffer).
When you use interrupts, you have concurrency, which means that you need to protect shared data (i.e., your buffers). This is often done by mutexes or semaphores. However, they are only available on OSs or RTOSs.
So the most common thing to do is to temporarily serialise your concurrent elements. This can be done by disabling the UART interrupt. More precisely, you need to deactivate all interrupts, which can trigger code that uses the shared resource.
I'm working with an ATmega168p and compiling with avr-gcc.
Specifically, I have an RS485 slave that receives bytes via UART and writes them to a buffer in an ISR. If an end character is received, a flag is set in the ISR. In my main loop this flag is checked and the input buffer is processed if necessary. However, there is the problem that some time can pass between the arrival of the end byte and the time when the handler in the main loop processes the input buffer, because of the other "stuff".
This results in a latency which can be up to several milliseconds, because e.g. sensors are read in every n-th iterations.
ISR(UART_RX_vect) {
write_byte_to_buffer();
if (byte==endbyte) // return to <HERE>
}
void main(){
init();
for(;;){
// <HERE> I want my program to continue after the ISR received an end byte
handle_buffer();
do_stuff(); // "stuff" may take a while
}
I want to get rid of this latency, as it is the bottleneck for the higher-level system.
I would like that after the ISR received the end byte, the program returns to the beginning of my main loop, where the input buffer would be processed immediately. I could of course process the input buffer directly in the ISR, but I am aware that this is not a good practice. This would also overwrite packets when the ISR gets invoked while processing a packet.
So, is there a way to overwrite an ISR's return address? Does C include such a feature, maybe something like goto?
Or am I completely on the wrong track?
Edit: Below is a reduced version of my code which also causes the described latency.
#define F_CPU 8000000UL
#define BAUD 38400
#define BUFFER_LENGTH 64
#include <util/setbaud.h>
#include <avr/interrupt.h>
#include <stdbool.h>
volatile char input_buffer[BUFFER_LENGTH + 1] = "";
volatile uint8_t input_pointer = 0;
volatile bool packet_started=false;
volatile bool packet_available = false;
ISR (USART_RX_vect) {
unsigned char nextChar;
nextChar = UDR0;
if (nextChar=='<') {
input_pointer=0;
packet_started=true;
}
else if (nextChar=='>' && packet_started) {
packet_started=false;
packet_available=true;
}
else {
if (input_pointer>=BUFFER_LENGTH) {
input_pointer=0;
packet_started=false;
packet_available=false;
}
else {
input_buffer[input_pointer++]=nextChar;
}
}
}
bool ADC_handler () {
ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
ADCSRA |= (1<<ADSC);
while (ADCSRA & (1<<ADSC)); // this loop blocks and causes latency
// assigning conversion result to a variable (not shown)
}
void ADC_init(void) {
ADMUX = (1<<REFS1)|(1<<REFS0)|(1<<MUX3);
ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
}
void process_buffer() {
// this function does something with the buffer
// but it takes "no" time and is not causing latency
return;
}
void UART_handler () {
if (packet_available) process_buffer();
}
void UART_init (void) {
UBRR0H = UBRRH_VALUE;
UBRR0L = UBRRL_VALUE;
UCSR0B |= (1<<RXCIE0)|(1<<RXEN0)|(1<<TXEN0);
UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00);
}
int main(void){
UART_init();
ADC_init();
// initializing some other things
sei();
for(;;){
UART_handler();
ADC_handler();
// other handlers like the ADC_handler follow
}
return 0;
}
I'm aware that the latency is due to blocking code, in this case the while loop in the ADC_handler() that waits for the conversion to finish. I could check for packet_available in the ADC handler and make this funtion return if the flag is set or I could even retrieve the conversion result with an ADC interrupt. That's all nice because I'm the one who implements the ADC_handler(). But if I wanted to use third party libraries (e.g. sensor libraries provided by manufacturers) I would depend on how those libraries are implemented. So what I'm looking for is a way to handle the problem "on my side"/in the UART implementation itself.
Don't try to use setjmp()/longjmp() to re-enter a main-level function from an ISR. This calls for disaster, because the ISR is never finished correctly. You might like to use assembly to work around, but this is really fragile. I'm not sure that this works at all on AVRs.
Since your baudrate is 38400, one byte needs at least some 250µs to transfer. Assumed that your message has a minimum of 4 bytes, the time to transfer a message is at least 1ms.
There are multiple possible solutions; your question might be closed because they are opinion-based...
However, here are some ideas:
Time-sliced main tasks
Since a message can arrive only once per millisecond or less, your application don't need to be much faster than that.
Divide your main tasks into separated steps, each running faster than 1 ms. You might like to use a state machine, for example to allow slower I/O to finish.
After each step, check for a completed message. Using a loop avoids code duplication.
Completely interrupt-based application
Use a timer interrupt to do the repeated work. Divide it in short tasks, a state machine does magic here, too.
Use an otherwise unused interrupt to signal the end of the message. Its ISR may run a bit longer, because it will not be called often. This ISR can handle the message and change the state of the application.
You need to think about interrupt priorities with much care.
The endless loop in main() will effectively be empty, like for (;;) {}.
I would like to receive and transmit data that will be put into a circular buffer. I have functions for inserting characters and reading characters.
How can I use these functions and where should I put them? Currently I transmit and receive what I send to the terminal and it works fine.
I am just learning, please give me some advice
#define UART_RX_BUF_SIZE 7
#define UART_TX_BUF_SIZE 7
int8_t uart_put_char(char data);
int8_t uart_get_char(char *data);
volatile char uart_rxBuff[UART_RX_BUF_SIZE];
volatile char uart_txBuff[UART_TX_BUF_SIZE];
void uart_put_string(char *s);
typedef struct {
volatile char *const buffer;
uint8_t head;
uint8_t tail;
} circ_buffer_t;
volatile circ_buffer_t uart_rx_circBuff = {uart_rxBuff, 0, 0};
volatile circ_buffer_t uart_tx_circBuff = {uart_txBuff, 0, 0};
uint8_t received_char;
int8_t uart_put_char(char data) {
uint8_t head_temp = uart_tx_circBuff.head + 1;
if (head_temp == UART_TX_BUF_SIZE)
head_temp = 0;
if (head_temp == uart_tx_circBuff.tail)
return 0;
uart_tx_circBuff.buffer[head_temp] = data;
uart_tx_circBuff.head = head_temp;
__HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE);
return 1;
}
int8_t uart_get_char(char *data) {
if (uart_rx_circBuff.head == uart_rx_circBuff.tail)
return 0;
uart_rx_circBuff.tail++;
if (uart_rx_circBuff.tail == UART_RX_BUF_SIZE)
uart_rx_circBuff.tail = 0;
*data = uart_rx_circBuff.buffer[uart_rx_circBuff.tail];
return 1;
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
// uart_put_char(&received_char);
HAL_UART_Transmit(&huart1, &received_char, 1, 100);
// uart_get_char(received_char);
HAL_UART_Receive_IT(&huart1, &received_char, 1);
}
}
I want to put a side-note first, so it doesn't get missed:
I would suggest incrementing your head and tail indices after you've put the byte in the buffer. So, upon initialisation, head is 0. When you call put_tx, the byte will be stored at index 0 and then be incremented to 1. When the interrupt calls get_tx, the tail is still 0 so you'll get that first character, then it'll be incremented to 1. Doesn't really matter, but I think it's easier to write clean code if you think in this way.
Also, it's totally a micro-optimisation, but think about setting your buffer size to a power of 2. That way, instead of going if (head==BUF_SIZE) head=0; you can go head &= BUF_SIZE-1; and you'll save a few clock cycles, no need for a test. ;-)
It definitely is a pain to set up, but very much worth it if you can get around the multiple steps. The HAL will probably handle much of this for you, but I don't know enough about that.
You need an interrupt handler for UART events.
You need to tell the UART to raise interrupts for RXNE (receive not empty), and TXE (transmit empty) when you've sent a character.
You need to tell the NVIC to enable the UART interrupt.
I definitely recommend having push and pop (or put and get) functions for both buffers. The interrupts will call put_rx and get_tx, while user code will call put_tx and get_rx. Or better yet, write some wrapper functions, like Uart_Send(char), Uart_SendBuffer(const char*, size_t), Uart_TryReceive(char*, size_t) that will call put_tx and get_rx.
If the HAL is as smart as I think it is, you can can probably combine steps 1-3 into a single step, and just implement HAL_UART_RxCpltCallback (as you've done) and HAL_UART_TxCpltCallback.
I don't know how the HAL works enough to give you an exact solution (all my STM32 work is built on headers I made myself before I realised even CMSIS existed - whoops!) so here's how I would do it in low-level code.
void USART1_IRQHandler() __attribute__((interrupt))
{
// This would probably be handled in HAL_UART_RxCpltCallback
if (USART1->SR.RXNE) // We've received a byte!
uart_push_rx(USART1->DR); // Push the received byte onto the back
// of the RX buffer.
// This would probably be handled in HAL_UART_TxCpltCallback
if (USART1->SR.TXE) // Transmit buffer is empty - send next byte
{
char nextByte;
if (uart_pop_tx(&nextByte)) // Get the next byte in the buffer and
USART1->DR = nextByte; // shove it in the UART data register.
else // No more data in the circular buffer,
USART1->CR1.TXEIE = 0; // so disable the TXE interrupt or we'll
// end up stuck in a loop.
}
if (USART1->SR.TC) // There's also a flag for 'transmit complete'. This
{ } // is different from TXE in that TXE means "Okay, I've started this
// one, tell me what'll come next," whereas TC says "Okay, I've
// finished sending everything now. Anything else, boss?"
// We won't use it, but be aware of the terminology. The HAL might
// try and confuse you with its words.
}
void InitialiseUart()
{
HAL_UART_Configure(&huart1, baud, stopBits, andStuff, probably, etc);
HAL_UART_Enable(&huart1);
// You probably don't need to worry about anything below here,
// if the HAL is smart. But I've included it for completeness,
// so you can understand more of what the MCU is doing.
// Enable RXNE (receive not empty) interrupt
USART1->CR1.RXNEIE = 1;
// Don't enable TXE (transmit empty) interrupt yet. Only when you send
// a character, or the interrupt will fire immediately.
// Enable UART interrupts at the system level
NVIC_EnableIRQ(USART1_IRQn);
}
If you need me to, I'll look into the HAL code and try and give you some more direct guidance, but I think it's valuable to take this, understand it, and translate it to your implementation.
I've had a closer look at the HAL source code, and it looks like by using the HAL_xxxx_IT() functions, all the interrupt code is already handled for you. That said, there are a lot of similarities to doing it in bare metal in your application, since you're only sending and receiving one character at a time.
When you call __HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE) you're telling the microcontroller to fire an interrupt when the data register is empty. But the HAL interrupt routine doesn't know what to transmit, so it'll send out garbage until it thinks it's finished.
I think another problem might be caused by directly accessing your circular buffer in multiple places, causing clashes. You'd be better off calling your HAL functions with a temporary buffer (or pointer to a single char) that either is taken from, or stores into, your circular buffer.
Main function
// Entry point
int main(void)
{
// Initialise system and peripherals
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM10_Init();
// Enable interrupts
HAL_NVIC_EnableIRQ(USART1_IRQn);
// Prepare reception of the first character
HAL_UART_Receive_IT(&huart1, &received_char, 1);
while (1)
{
char downloaded;
if (UartReceiveChar(&downloaded) && downloaded == 'x')
UartSendChar(downloaded);
}
}
UART wrapper
// Function declarations
int8_t UartSendChar (char data);
void UartSendString (char* str);
int8_t UartReceiveChar (char* data);
// Variables
int8_t isTransmitting = 0;
uint8_t sendingChar;
uint8_t receivedChar;
// Function definitions
// Callback function for when a character has finished sending by the HAL
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
// The HAL has just sent the tail byte from the TX buffer
// If there is still data in the buffer, we want to send the
// next byte in the buffer.
if (buffer_pop_tx(&sendingChar))
HAL_UART_Transmit_IT(huart, &sendingChar, 1);
else
isTransmitting = 0;
}
// Callback function for when a character is received by the HAL
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// The HAL has received a character into 'receivedChar'
// All we need to do is push it onto our circular buffer
buffer_push_rx(receviedChar);
// and prepare to receive the next character
HAL_UART_Receive_IT(huart, &receivedChar, 1);
}
// Send a character
int8_t UartSendChar(char data)
{
// Push the character onto the buffer
int8_t returnValue = buffer_push_tx(data);
// Start sending the buffer, if we're not already transmitting
if (!isTransmitting)
{
sendingChar = data;
isTransmitting = 1;
HAL_UART_Transmit_IT(&huart1, &sendingChar, 1);
}
return returnValue;
}
// Send a null-terminated string
int8_t UartSendString(char* str)
{
// Iterate through all the non-null characters
while (*str)
{
// Send the character; Wait if the buffer is full
while (!UartSendChar(*str)) ;
++str;
}
return 1;
}
// Receive a character
int8_t UartReceiveChar(char* data)
{
// Just a wrapper for buffer_pop_rx
return buffer_pop_rx(data);
}
Buffer implementation
// I've changed your circular buffer size for optimisation purposes
#define UART_RX_BUF_SIZE 8
#define UART_TX_BUF_SIZE 8
// Buffer type definition
typedef struct
{
volatile char *const buffer;
uint8_t head;
uint8_t tail;
uint8_t isFull : 1;
uint8_t isEmpty : 1;
uint8_t hasOverflowed : 1; // Overflow and underflow are only relevant if we choose to
uint8_t hasUnderflowed : 1; // allow pushing and popping with an empty/full buffer
} circ_buffer_t;
// Function declarations
int8_t buffer_push_tx (char data);
int8_t buffer_push_rx (char data);
int8_t buffer_pop_tx (char* data);
int8_t buffer_pop_rx (char* data);
// Variables
volatile char uart_rxBuff[UART_RX_BUF_SIZE];
volatile char uart_txBuff[UART_TX_BUF_SIZE];
volatile circ_buffer_t uart_rx_circBuff = {uart_rxBuff, 0, 0};
volatile circ_buffer_t uart_tx_circBuff = {uart_txBuff, 0, 0};
// Function definitions
// Push a character onto the transmit buffer
int8_t buffer_push_tx(char data)
{
if (uart_tx_circBuff.isFull) // buffer is full
{
// uart_tx_circBuff.hasOverflowed = 1; // Nasty things can happen if we allow overflows. But in some special cases, it may be necessary.
return 0;
}
// Put the character at the head position and increment the head index
uart_tx_circBuff.buffer[uart_tx_circBuff.head++] = data;
uart_tx.circBuff.head &= (UART_TX_BUF_SIZE - 1); // don't use &= if the buffer size isn't a power of 2
// mark the buffer as full if the head and tail indices are the same
uart_tx_circBuff.isFull = (uart_tx_circBuff.head == uart_tx_circBuff.tail);
uart_tx_circBuff.isEmpty = 0;
// OK
return 1;
}
// Push a character onto the receive buffer
int8_t buffer_push_rx(char data)
{
if (uart_rx_circBuff.isFull) // buffer is full
{
// uart_rx_circBuff.hasOverflowed = 1;
return 0;
}
// Put the character at the head position and increment the head index
uart_rx_circBuff.buffer[uart_rx_circBuff.head++] = data;
uart_rx.circBuff.head &= (UART_RX_BUF_SIZE - 1); // don't use &= if the buffer size isn't a power of 2
// mark the buffer as full if the head and tail indices are the same
uart_rx_circBuff.isFull = (uart_rx_circBuff.head == uart_rx_circBuff.tail);
uart_rx_circBuff.isEmpty = 0;
// OK
return 1;
}
// Try to get a character from the receive buffer.
int8_t uart_pop_rx(char *data)
{
if (uart_rx_circBuff.isEmpty) // buffer is empty
{
// uart_rx_circBuff.hasUnderflowed = 1;
return 0;
}
// Put the character from the tail position of the buffer into 'data' and increment the tail index
*data = uart_rx_circBuff.buffer[uart_rx_circBuff.tail++];
uart_rx_circBuff.tail &= (UART_RX_BUF_SIZE - 1); // // don't use &= if the buffer size isn't a power of 2
// mark the buffer as full if the head and tail indices are the same
uart_rx_circBuff.isEmpty = (uart_rx_circBuff.head == uart_rx_circBuff.tail);
uart_rx_circBuff.isFull = 0;
// OK
return 1;
}
// Try to get a character from the transmit buffer.
int8_t uart_pop_rx(char *data)
{
if (uart_tx_circBuff.head == uart_tx_circBuff.tail) // buffer is empty
{
// uart_tx_circBuff.hasUnderflowed = 1;
return 0;
}
// Put the character from the tail position of the buffer into 'data' and increment the tail index
*data = uart_tx_circBuff.buffer[uart_tx_circBuff.tail++];
uart_tx_circBuff.tail &= (UART_TX_BUF_SIZE - 1); // don't use &= if the buffer size isn't a power of 2
// mark the buffer as full if the head and tail indices are the same
uart_tx_circBuff.isEmpty = (uart_tx_circBuff.head == uart_tx_circBuff.tail);
uart_tx_circBuff.isFull = 0;
// OK
return 1;
}
I need advice on proper way of handling UART communication. I feel like I've done handling sending serial commands over UART well but I don't know if the way I'm parsing the response or receiving serial data is the best way to do it. Any tips are appreciated but I just want to know if there's a better more and elegant way to parse UART RX.
This is for an MSP430 uC by the way...
First I have these declared in the header file:
const unsigned char *UART_TX_Buffer;
unsigned char UART_TX_Index;
unsigned char UART_TX_Length;
unsigned char UART_TX_Pkt_Complete;
unsigned char UART_RX_Buffer[25];
unsigned char UART_RX_Pkt_Complete;
unsigned char UART_RX_Index;
Here is the function that is called once the flag UART_RX_Pkt_Complete is set in the ISR:
void Receive_Resp()
{
switch (UART_RX_Buffer[UART_RX_Index - 3])
{
case 0x4B:
break;
case 0x56:
P1OUT &= ~(tos_sel0 + tos_sel1);
break;
case 0x43:
P1OUT |= tos_sel0;
P1OUT &= ~tos_sel1;
break;
case 0x34:
P1OUT |= tos_sel1;
P1OUT &= ~tos_sel0;
break;
case 0x33:
P1OUT |= tos_sel0 + tos_sel1;
break;
default:
break;
}
UART_RX_Pkt_Complete = 0;
UART_RX_Index = 0;
}
For reference here's the RX ISR:
#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCIA0RX_ISR(void)
{
UART_RX_Buffer[UART_RX_Index++] = UCA0RXBUF;
if (UART_RX_Buffer[UART_RX_Index - 1] == 0x0A)
{
UART_RX_Pkt_Complete = 1;
_BIC_SR_IRQ(LPM3_bits);
}
IFG2 &= ~UCA0RXIFG;
}
Also here's the TX ISR and send UART command routine:
if (UART_TX_Index < UART_TX_Length) // Check if there are more bytes to be sent
{
UCA0TXBUF = UART_TX_Buffer[UART_TX_Index++];
}
else // Last byte has been sent
{
UART_TX_Pkt_Complete = 1; // Set flag to show last byte was sent
_BIC_SR_IRQ(LPM3_bits);
}
IFG2 &= ~UCA0TXIFG;
void Send_CMD (const unsigned char *Data, const unsigned char Length)
{
UART_TX_Buffer = Data; // Move into global variables
UART_TX_Length = Length;
UART_TX_Pkt_Complete = 0; // Starting values
UART_RX_Pkt_Complete = 0;
UART_TX_Index = 0;
UCA0TXBUF = UART_TX_Buffer[UART_TX_Index++];
while(!UART_TX_Pkt_Complete)
{
Delay(5,'u');
}
while(!UART_RX_Pkt_Complete)
{
Delay(5,'u');
}
}
If this works and meets your system's requirements then it's fine. But there are several ways it could be improved.
Receive_Resp() and USCIA0RX_ISR() are tightly coupled, which is undesirable. They both manipulate UART_RX_Index for the other (USCIA0RX_ISR() increments it and Receive_Resp() clears it) and they both rely on the other for part of the framing of each message (USCIA0RX_ISR() finds the end of the frame while Receive_Resp() interprets and resets for the next frame). It would be better if these routines were decoupled.
The character buffer should be a circular buffer with a head pointer (where characters get added) and a tail pointer (where characters get removed). The ISR should only add chars to the circular buffer and advance the head pointer. The ISR should also handle wrapping of the head pointer back to the beginning of the circular buffer. And the ISR should protect from overruns by making sure the head pointer doesn't pass the tail pointer.
The Receive routine should be responsible for framing the message. This includes pulling chars from the tail pointer and identifying the beginning and end of the message. The Receive routine increments and wraps the tail pointer. Typically the Receive routine is implemented as a state machine with states for identifying the start, body, and end of a frame.
For even less coupling you might have a separate function that interprets the content of the message (i.e., separate the framing from the interpretation of the message).
Your ReceiveResp() routine doesn't handle any errors such as a dropped character. It assumes that all three characters were received correctly. Maybe that's fine for your application and requirements. But typically there should be some error checking performed here. At the very least you should ensure that UART_RX_Index >= 3 before you subtract 3 from it. In other words, make sure the message length is sane. A more robust serial protocol would have a checksum or CRC in each frame to ensure the frame was received correctly but that is probably overkill for your application.
Your Transmit side could be improved with some of the same advice. Typically there is a circular buffer for transmit chars. The Transmit routine adds chars to the buffer and manages the head pointer. The TX ISR copies chars from the buffer to the UART and manages the tail pointer.
Do the calls to Delay in Send_CMD() mean that your application is totally stalled while it's waiting to finish the transmission? Again, maybe that's OK for your application but typically that is undesirable. Typically you want the application to continue to function even while it's waiting for the UART to be ready to transmit. By using a circular transmit buffer, it would be possible for multiple messages to queue up in the transmit buffer and you wouldn't have to wait for the previous message to finish before queuing up another. But then you should add protection for a buffer overrun and this may complicate things unnecessarily for you. Which brings me back to my first point, if what you have works and meets your requirements then it is fine.
Personally, I'd write a completely separate UART module that contained methods for write and read operations. Under the hood I'd create two circular buffers (of whatever the appropriate size may be) and use those for storing bytes as data comes in or goes out. This would allow an interrupt driven solution with buffers.
For instance, my interrupt for receive would do something like:
#pragma vector=USCIAB0RX_VECTOR
__interrupt void UartRxIsr()
{
...
// Add new byte to my receive buffer.
...
}
Then I can call my Uart.read() method to read out that byte. The read method might look something like the following:
char read()
{
if (Uart.rxBuffer.length > 0)
{
return Uart.rxBuffer.buffer[Uart.rxBuffer.write++];
}
}
This is assuming that you've implemented circular buffers using pointers.
I have a solution lying around somewhere. I'll try to find it and post it.
I am looking for ideas for a receive buffer for a small application dealing with 15 byte packets at 921.6Kbaud over rs485. I am thinking of using a circular buffer as the interface between the UART ISR and main. As it is a microprocessor I was wanting to put
while (uartindex!=localindex) { do stuff }
in the
while (;;) {do forever}
part of main but I have been told this is not acceptable.
How do people deal with their uarts under similar circumstances?
ISR should fill a FIFO. Main should consume it.
Bellow a very simple fifo algorithm:
#define RINGFIFO_SIZE (1024) /* serial buffer in bytes (power 2) */
#define RINGFIFO_MASK (RINGFIFO_SIZE-1ul) /* buffer size mask */
/* Buffer read / write macros */
#define RINGFIFO_RESET(ringFifo) {ringFifo.rdIdx = ringFifo.wrIdx = 0;}
#define RINGFIFO_WR(ringFifo, dataIn) {ringFifo.data[RINGFIFO_MASK & ringFifo.wrIdx++] = (dataIn);}
#define RINGFIFO_RD(ringFifo, dataOut){ringFifo.rdIdx++; dataOut = ringFifo.data[RINGFIFO_MASK & (ringFifo.rdIdx-1)];}
#define RINGFIFO_EMPTY(ringFifo) (ringFifo.rdIdx == ringFifo.wrIdx)
#define RINGFIFO_FULL(ringFifo) ((RINGFIFO_MASK & ringFifo.rdIdx) == (RINGFIFO_MASK & (ringFifo.wrIdx+1)))
#define RINGFIFO_COUNT(ringFifo) (RINGFIFO_MASK & (ringFifo.wrIdx - ringFifo.rdIdx))
/* buffer type */
typedef struct{
uint32_t size;
uint32_t wrIdx;
uint32_t rdIdx;
uint8_t data[RINGFIFO_SIZE];
} RingFifo_t;
RingFifo_t gUartFifo;
(Care must be taken with this FIFO algorithm, size MUST be power of 2)
The ISR should behave like this:
void ISR_Handler()
{
uint8_t c;
while(UART_NotEmpty()) {
c = UART_GetByte();
RINGFIFO_WR(gUartFifo, c);
}
}
And the Main:
while(1)
{
if (!RINGFIFO_EMPTY(gUartFifo)) {
/* consume fifo using RINGFIFO_RD */
}
}
This algorithm reads the FIFO directly from the main loop, you should use a intermediate layer that checks if there is a full packet in the buffer, and deals with it, in such a manner that main would be like this:
uint8_t ptrToPacket;
uint32_t packetSize;
while(1)
{
if (!Uart_HasValidPacket()) {
Uart_GetPacket(&ptrToPacket, &packetSize)
/* Process packet using ptrToPacket and packetSize */
}
}
The approach you suggest would probably be workable if the uartindex is never written in the main loop (except to initialize it while interrupts are disabled), and localindex is never touched by the interrupt routine.
I would suggest that you make your buffer size be a power of 2, use unsigned integers for the two indices, and allow them to count freely over their full 32-bit size; use bit masking when indexing your buffer in both the "stuff" and "fetch" routines. If you do that, then
(unsigned)(uartindex-localindex)
should indicate how many characters are in the buffer, even when it's completely full, without requiring special-case behavior in the buffer-full case and without limiting an N-byte buffer to holding N-1 items.
Note that while the typecast in the aforementioned expression isn't strictly necessary, I would recommend including it since it makes obvious that the wrapping behavior when subtracting unsigned quantities is deliberate and expected.