AVR ATmega在主循环之前使用printf时保持重置

Cla*_*dio 3 c embedded avr stdio avr-gcc

我正在AVR ATmega328P微控制器上使用avr-libc开发一个C应用程序.由于我没有ICE调试器,因此我按照这些说明本教程进行了诸如能够使用硬件UART等功能.stdio.hprintfstdout

这是有效的,我可以看到连接到我的目标板的PC终端上的输出,但奇怪的是:当我只有一个printf在主,但在主循环之前有什么导致处理器重置,而如果我有一个printf只在主循环内部或主循环之前和循环内部它工作正常.像这样的东西:

#include <stdio.h>

/* stream definitions for UART input/output */
FILE uart_output = FDEV_SETUP_STREAM(uart_drv_send_byte, NULL, _FDEV_SETUP_WRITE);
FILE uart_input = FDEV_SETUP_STREAM(NULL, uart_drv_read_byte, _FDEV_SETUP_READ);

int main() {
    /* Definition of stdout and stdin */
    stdout = &uart_output;
    stdin = &uart_input;

    /* Configures Timer1 for generating a compare interrupt each 1ms (1kHz) */
    timer_init()

    /* UART initialization */
    uart_drv_start(UBRRH_VALUE, UBRRL_VALUE, USE_2X, &PORTB, 2);

    /* Sets the sleep mode to idle */
    set_sleep_mode(SLEEP_MODE_IDLE);

    printf("START ");

    /* main loop */
    while(1) {
        printf("LOOP ");

        /* Sleeps so the main loop iterates only on interrupts (avoids busy loop) */
        sleep_mode();
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码产生以下输出:

START LOOP LOOP LOOP LOOP LOOP LOOP ... LOOP
Run Code Online (Sandbox Code Playgroud)

这是预料之中的.如果我们评论printf("START ")它产生的线:

LOOP LOOP LOOP LOOP LOOP LOOP LOOP ... LOOP
Run Code Online (Sandbox Code Playgroud)

这也没关系.问题是,如果我printfwhile循环中没有任何内容,它会像这样:

START START START START START START ... START
Run Code Online (Sandbox Code Playgroud)

这清楚地表明处理器正在重新启动,因为预期的输出只是一个START,而无限循环继续仅在1 kHz定时器中断时唤醒.为什么会这样?我应该强调,没有配置看门狗定时器(如果有的话,只LOOP打印的情况也会被新的打断START).

使用GPIO引脚监控执行

为了尝试深入了解这种情况,我在问题print("START ")sleep_mode主循环中打开和关闭GPIO引脚:

int main() {

    /* Irrelevant parts suppressed... */

    GPIO1_ON;
    printf("START ");
    GPIO1_OFF;

    /* Main loop */
    while(1) {

        /* Sleeps so the main loop iterates only on interrupts (avoids busy loop) */
        GPIO2_ON;
        sleep_mode();
        GPIO2_OFF;
    }
}
Run Code Online (Sandbox Code Playgroud)

事实证明,GPIO1保持ON状态持续132μs(printf("START ")呼叫时间)然后关闭6.6 ms - 大致是以9600 bit/s传输6个字符的时间 - 并且GPIO2切换12次(两次中断6次:UART就绪- 发送中断和UART空数据寄存器中断),在GPIO1再次接通之前显示休眠时间为1.4 ms,表示新的printf("START ")- 因此在复位后.我可能要检查UART代码,但我很确定非中断UART版本也会显示同样的问题,这并不能解释为什么printf主循环内部工作正常,没有复位发生(我希望在任何情况下都会发生重置,如果UART代码有问题).


(已解决!):为完整起见,UART init和TX代码低于**

这是我第一次尝试为AVR编写中断驱动的UART驱动程序,但可以在RS-232或RS-485上使用,这需要在传输数据时激活TX_ENABLE引脚.事实证明,由于我必须在ATmega328P或ATmega644上使代码可用,因此中断向量具有不同的名称,因此我#define TX_VECTOR根据所使用的处理器使用a 来假定正确的名称.在制作和测试驱动程序的过程中,为UDRE数据空中断选择"TX_VECTOR"最终屏蔽了我尚未定义的事实USART0_TX_vect(这是正在进行的工作,我甚至可能都不需要两者...... )

现在我刚刚定义了一个空的中断服务程序(ISR),USART0_TX_vect并且该东西不再重置,显示@PeterGibson正确地将它钉在上面.非常感谢!

// Interrupt vectors for Atmega328P
#if defined(__AVR_ATmega328P__)
    #define RX_VECTOR USART_RX_vect
    #define TX_VECTOR USART_UDRE_vect
    // Interrupt vectors for Atmega644
#elif defined(__AVR_ATmega644P__)
    #define RX_VECTOR USART0_RX_vect
    #define TX_VECTOR USART0_UDRE_vect
#endif

ISR(TX_VECTOR)
{
    uint8_t byte;

    if (!ringbuffer_read_byte(&txrb, &byte)) {

        /* If RS-485 is enabled, sets TX_ENABLE high */
        if (TX_ENABLE_PORT)
            *TX_ENABLE_PORT |= _BV(TX_ENABLE_PIN);
        UDR0 = byte;
    }
    else {
        /* No more chars to be read from ringbuffer, disables empty
         * data register interrupt */
        UCSR0B &= ~_BV(UDRIE0);
    }

    /* If RS-485 mode is on and the interrupt was called with TXC0 set it
     * means transmission is over. TX_ENABLED should be cleared. */
    if ((TX_ENABLE_PORT) && (UCSR0A & _BV(TXC0) & _BV(UDR0))) {
        *TX_ENABLE_PORT &= ~_BV(TX_ENABLE_PIN);
        UCSR0B &= ~_BV(UDRIE0);
    }
}

void uart_drv_start(uint8_t ubrrh, uint8_t ubrrl, uint8_t use2x,
                    volatile uint8_t* rs485_tx_enable_io_port,
                    uint8_t rs485_tx_enable_io_pin)
{
    /* Initializes TX and RX ring buffers */
    ringbuffer_init(&txrb, &tx_buffer[0], UART_TX_BUFSIZE);
    ringbuffer_init(&rxrb, &rx_buffer[0], UART_RX_BUFSIZE);

    /* Disables UART */
    UCSR0B = 0x00;

    /* Initializes baud rate */
    UBRR0H = ubrrh;
    UBRR0L = ubrrl;
    if (use2x)
        UCSR0A |= _BV(U2X0);
    else
        UCSR0A &= ~_BV(U2X0);

    /* Configures async 8N1 operation */
    UCSR0C = _BV(UCSZ00) | _BV(UCSZ01);

    /* If a port was specified for a pin to be used as a RS-485 driver TX_ENABLE,
     * configures the pin as output and enables the TX data register empty
     * interrupt so it gets disabled in the end of transmission */
    if (rs485_tx_enable_io_port) {
        TX_ENABLE_PORT = rs485_tx_enable_io_port;
        TX_ENABLE_PIN = rs485_tx_enable_io_pin;
        /* Configures the RS-485 driver as an output (on the datasheet the data
         * direction register is always on the byte preceding the I/O port addr) */
        *(TX_ENABLE_PORT-1) |= _BV(TX_ENABLE_PIN);
        /* Clears TX_ENABLE pin (active high) */
        *TX_ENABLE_PORT &= ~_BV(TX_ENABLE_PIN);
        /* Enables end of transmission interrupt */
        UCSR0B = _BV(TXCIE0);
    }
    /* Enables receptor, transmitter and RX complete interrupts */
    UCSR0B |= _BV(RXEN0) | _BV(TXEN0) | _BV(RXCIE0);
}
Run Code Online (Sandbox Code Playgroud)

固定UART代码(现在100%工作!)

为了帮助任何有兴趣或为AVR ATmega开发类似的中断驱动UART驱动程序的人,这里的代码解决了上面修复和测试的问题.感谢所有帮助我发现缺少ISR问题的人!

// Interrupt vectors for Atmega328P
#if defined(__AVR_ATmega328P__)
    #define RX_BYTE_AVAILABLE USART_RX_vect
    #define TX_FRAME_ENDED USART_TX_vect
    #define TX_DATA_REGISTER_EMPTY USART_UDRE_vect
    // Interrupt vectors for Atmega644
#elif defined(__AVR_ATmega644P__)
    #define RX_BYTE_AVAILABLE USART0_RX_vect
    #define TX_FRAME_ENDED USART0_TX_vect
    #define TX_DATA_REGISTER_EMPTY USART0_UDRE_vect
#endif

/* I/O port containing the pin to be used as TX_ENABLE for the RS-485 driver */
static volatile uint8_t* TX_ENABLE_PORT = NULL;

/** Pin from the I/O port to be used as TX_ENABLE for the RS-485 driver */
static volatile uint8_t TX_ENABLE_PIN = 0;

ISR(RX_BYTE_AVAILABLE)
{
    // Read the status and RX registers.
    uint8_t status = UCSR0A;
    // Framing error - treat as EOF.
    if (status & _BV(FE0)) {
        /* TODO: increment statistics */
    }
    // Overrun or parity error.
    if (status & (_BV(DOR0) | _BV(UPE0))) {
        /* TODO: increment statistics */
    }
    ringbuffer_write_byte(&rxrb, UDR0);
}

ISR(TX_FRAME_ENDED)
{
    /* The end of frame interrupt will be enabled only when in RS-485 mode, so
     * there is no need to test, just turn off the TX_ENABLE pin */
    *TX_ENABLE_PORT &= ~_BV(TX_ENABLE_PIN);
}

ISR(TX_DATA_REGISTER_EMPTY)
{
    uint8_t byte;

    if (!ringbuffer_read_byte(&txrb, &byte)) {
        /* If RS-485 is enabled, sets TX_ENABLE high */
        if (TX_ENABLE_PORT)
            *TX_ENABLE_PORT |= _BV(TX_ENABLE_PIN);
        UDR0 = byte;
    }
    else {
        /* No more chars to be read from ringbuffer, disables empty
         * data register interrupt */
        UCSR0B &= ~_BV(UDRIE0);
    }
}

void uart_drv_start(uint8_t ubrrh, uint8_t ubrrl, uint8_t use2x,
                    volatile uint8_t* rs485_tx_enable_io_port,
                    uint8_t rs485_tx_enable_io_pin)
{
    /* Initializes TX and RX ring buffers */
    ringbuffer_init(&txrb, &tx_buffer[0], UART_TX_BUFSIZE);
    ringbuffer_init(&rxrb, &rx_buffer[0], UART_RX_BUFSIZE);

    cli();

    /* Disables UART */
    UCSR0B = 0x00;

    /* Initializes baud rate */
    UBRR0H = ubrrh;
    UBRR0L = ubrrl;
    if (use2x)
        UCSR0A |= _BV(U2X0);
    else
        UCSR0A &= ~_BV(U2X0);

    /* Configures async 8N1 operation */
    UCSR0C = _BV(UCSZ00) | _BV(UCSZ01);

    /* If a port was specified for a pin to be used as a RS-485 driver TX_ENABLE,
     * configures the pin as output and enables the TX data register empty
     * interrupt so it gets disabled in the end of transmission */
    if (rs485_tx_enable_io_port) {
        TX_ENABLE_PORT = rs485_tx_enable_io_port;
        TX_ENABLE_PIN = rs485_tx_enable_io_pin;

        /* Configures the RS-485 driver as an output (on the datasheet the data
         * direction register is always on the byte preceding the I/O port addr) */
         *(TX_ENABLE_PORT-1) |= _BV(TX_ENABLE_PIN);

        /* Clears TX_ENABLE pin (active high) */
        *TX_ENABLE_PORT &= ~_BV(TX_ENABLE_PIN);

        /* Enables end of transmission interrupt */
        UCSR0B = _BV(TXCIE0);
    }
    /*  Enables receptor, transmitter and RX complete interrupts */
    UCSR0B |= _BV(RXEN0) | _BV(TXEN0) | _BV(RXCIE0);

    sei();
}

void uart_drv_send_byte(uint8_t byte, FILE *stream)
{
    if (byte == '\n') {
        uart_drv_send_byte('\r', stream);
    }
    uint8_t sreg = SREG;
    cli();

    /* Write byte to the ring buffer, blocking while it is full */
    while(ringbuffer_write_byte(&txrb, byte)) {
        /* Enable interrupts to allow emptying a full buffer */
        SREG = sreg;
        _NOP();
        sreg = SREG;
        cli();
    }

    /* Enables empty data register interrupt */
    UCSR0B |= _BV(UDRIE0);
    SREG = sreg;
}

uint8_t uart_drv_read_byte(FILE *stream)
{
    uint8_t byte;
    uint8_t sreg = SREG;
    cli();
    ringbuffer_read_byte(&rxrb, &byte);
    SREG = sreg;
    return byte;
}
Run Code Online (Sandbox Code Playgroud)

Pet*_*son 6

您可能已启用UDRE(Uart数据寄存器空)中断而不为其设置向量,因此当中断触发时,处理器将重置(根据默认值).当printf在主循环中连续调用时,永远不会触发此中断.

来自文档

捕获所有中断向量

如果发生意外中断(中断已启用且未安装处理程序,这通常表示存在错误),则默认操作是通过跳转到重置向量来重置设备.您可以通过提供名为BADISR_vect的函数来覆盖它,该函数应该使用ISR()来定义.(名称BADISR_vect实际上是__vector_default的别名.后者必须在汇编代码中使用,以防万一.)