avr-gcc:调用 ISR 后跳转到任意地址

avr-gcc: jump to arbitrary address after ISR has been invoked

我正在使用 ATmega168p 并使用 avr-gcc 进行编译。

具体来说,我有一个 RS485 从站,它通过 UART 接收字节并将它们写入 ISR 中的缓冲区。如果接收到结束字符,则在 ISR 中设置一个标志。在我的主循环中,这个标志被检查,输入缓冲区在必要时被处理。但是,由于其他“东西”,存在这样的问题,即在结束字节到达和主循环中的处理程序处理输入缓冲区的时间之间可能会经过一些时间。 这导致延迟可能长达几毫秒,因为例如在每第 n 次迭代中读取传感器。

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
  }

我想摆脱这种延迟,因为它是更高级别系统的瓶颈。

我希望在 ISR 收到结束字节后,程序 returns 到我的主循环的开头,输入缓冲区将在此处立即处理。我当然可以直接在 ISR 中处理输入缓冲区,但我知道这不是一个好的做法。当 ISR 在处理数据包时被调用时,这也会覆盖数据包。

那么,有没有办法覆盖 ISR 的 return 地址? C 是否包含这样的功能,可能类似于 goto? 还是我完全走错了路?

编辑: 下面是我的代码的简化版本,它也会导致所描述的延迟。

#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;
}

我知道延迟是由于阻塞代码造成的,在本例中是等待转换完成的 ADC_handler() 中的 while 循环。我可以在 ADC 处理程序中检查 packet_available 并在设置标志时使此函数 return 或者我什至可以使用 ADC 中断检索转换结果。这一切都很好,因为我是实施 ADC_handler() 的人。但如果我想使用第三方库(例如制造商提供的传感器库),我将取决于这些库的实现方式。所以我正在寻找一种方法来处理“在我这边”/在 UART 实现本身中的问题。

不要尝试使用 setjmp()/longjmp() 从 ISR 重新进入主级函数。这需要灾难,因为 ISR 永远不会正确完成。您可能想使用程序集来变通,但这确实很脆弱。我不确定这是否适用于 AVR。

由于您的波特率为 38400,因此传输一个字节至少需要大约 250µs。假设你的消息至少有4个字节,那么传输一条消息的时间至少为1ms。

有多种可能的解决方案;您的问题可能会被关闭,因为它们是基于意见的...

但是,这里有一些想法:

时间片主要任务

由于消息只能每毫秒或更短时间到达一次,因此您的应用程序不需要比这快得多。

将您的主要任务分成单独的步骤,每个步骤 运行ning 快于 1 毫秒。您可能喜欢使用状态机,例如允许较慢的 I/O 完成。

在每个步骤之后,检查是否有完整的消息。使用循环避免代码重复。

完全基于中断的应用程序

使用定时器中断来完成重复的工作。把它分成短任务,状态机在这里也很神奇。

使用一个未使用的中断来表示消息的结束。它的 ISR 可能 运行 长一点,因为它不会经常被调用。此 ISR 可以处理消息并更改应用程序的状态。

您需要仔细考虑中断优先级。

main() 中的无限循环实际上是空的,如 for (;;) {}