0
votes

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.

1
This sounds like what setjmp/longjmp does. You'll have to check the documentation for your compiler to see if it's supported for interrupt handlers, though.Nate Eldredge
What you want might be possible with plain standard C. But you should really think about it very long and hard, as not letting functions finish normally could lead to its internal state being wrong so the next time you call the function it might not work as expected. A possible different solution is to add checks in the do_stuff function for the special condition set by the ISR, and handle it then and there (by calling a function of course).Some programmer dude
But this would mean that whatever event is in progress when the interrupt arrives would be aborted. Is that what you want? Is there cleanup that would need to be done?Nate Eldredge
Yes, anything in "stuff" has minor priority and I want it to be aborted. setjmp/longjmp looks like what I was looking for, I'll check my compiler!Sim Son
setjmp/longjmp is for signal handlers in POSIX application programs. This will not work for HW ISRs. You may need to completely restructure your code so that most of the processing is done in the ISR as the bytes arrive rather than waiting until you have some end-of-packet sentinel byte [or not]. Maybe you need to communicate the bytes via a ring queue [or not]. Or, ... You may need to tell your H/W engineer that the processor is not fast enough to handle the load and the board needs a redesign with a faster model processor ...Craig Estey

1 Answers

0
votes

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 (;;) {}.