avr-gcc:调用 ISR 后跳转到任意地址 按时间划分的主要任务完全基于中断的应用

问题描述

我正在使用 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 收到结束字节后,程序返回到我的主循环的开头,在那里将立即处理输入缓冲区。我当然可以直接在 ISR 中处理输入缓冲区,但我知道这不是一个好的做法。这也会在处理数据包时调用 ISR 时覆盖数据包。

那么,有没有办法覆盖 ISR 的返回地址? 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 并在设置标志时返回此函数,或者我什至可以通过 ADC 中断检索转换结果。这一切都很好,因为我是实现 ADC_handler() 的人。但是如果我想使用第三方库(例如制造商提供的传感器库),我将取决于这些库的实现方式。所以我正在寻找一种方法来处理“在我这边”/在 UART 实现本身中的问题。

解决方法

不要尝试使用 setjmp()/longjmp() 从 ISR 重新输入主级函数。这需要灾难,因为 ISR 永远不会正确完成。您可能喜欢使用程序集来解决问题,但这真的很脆弱。我不确定这是否适用于 AVR。

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

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

但是,这里有一些想法:

按时间划分的主要任务

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

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

在每一步之后,检​​查是否有完成的消息。使用循环可避免代码重复。

完全基于中断的应用

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

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

您需要非常小心地考虑中断优先级。

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

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...