关于C#:AVR ATmega在主循环之前使用printf时会不断重置

AVR ATmega keeps resetting while using printf before main loop

我正在AVR ATmega328P微控制器上使用avr-libc开发C应用程序。由于我没有ICE调试器,因此我按照这些说明和本教程进行操作,以使stdio.h功能(例如printf)能够将硬件UART用作stdout

那行得通,我可以在连接到目标板上的PC终端上看到输出,但是奇怪的是:当我在主处理器上只有一个printf时,但是在主循环之前,有什么东西导致处理器复位,而如果我只有printf仅在主循环内或在主循环与循环内之前有效,则它运行良好。像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#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();
    }
}

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

1
START LOOP LOOP LOOP LOOP LOOP LOOP ... LOOP

这是预期的。如果我们注释printf("START")行,它将产生以下内容:

1
LOOP LOOP LOOP LOOP LOOP LOOP LOOP ... LOOP

也可以问题是,如果我在while循环内没有任何printf,它会像这样:

1
START START START START START START ... START

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

使用GPIO引脚监控执行

为了了解情况,我围绕主循环中有问题的print("START")sleep_mode打开和关闭了GPIO引脚:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
    }
}

事实证明,GPIO1保持打开状态持续132μs(printf("START")调用时间),然后保持关闭状态6.6ms(大约是以9600bit / s的速度传输六个字符的时间),而GPIO2切换12次(两次中断的次数是6次:准备发送中断和UART空数据寄存器中断),在GPIO1再次导通之前,睡眠又活动了1.4ms,这表明新的printf("START")-因此在复位之后。我可能必须检查一下UART代码,但是我很确定非中断UART版本也会出现相同的问题,并且这也不能解释为什么在主循环中使用printf可以正常工作,不会发生复位(我希望在任何情况下,如果UART代码出错,都会发生复位)。


(已解决!):为完整起见,UART初始化和发送代码如下**

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

现在,我刚刚为USART0_TX_vect定义了一个空的中断服务例程(ISR),并且此操作不再复位,显示@PeterGibson正确地设置了它。非常感谢!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// 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);
}

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

为了帮助对AVR ATmega感兴趣或开发类似的中断驱动UART驱动程序的任何人,此处提供了上面已解决和测试的问题的代码。感谢所有帮助我发现缺少ISR的问题的人!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// 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 == '
'
) {
        uart_drv_send_byte('
'
, 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;
}


您可能已经启用了UDRE(Uart数据寄存器为空)中断,并且未为其设置向量,所以当该中断触发处理器复位时(根据默认值)。 在主循环中连续调用printf时,永远不会触发该中断。

来自文档

Catch-all interrupt vector

If an unexpected interrupt occurs (interrupt is enabled and no handler
is installed, which usually indicates a bug), then the default action
is to reset the device by jumping to the reset vector. You can
override this by supplying a function named BADISR_vect which should
be defined with ISR() as such. (The name BADISR_vect is actually an
alias for __vector_default. The latter must be used inside assembly
code in case is not included.)


我现在遇到了同样的情况,但是由于我在stackoverflow上没有很高的声誉,因此我无法投票。

这是导致此问题的初始化过程的片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void USART_Init()
{
    cli();
    /* Set baud rate */
    UBRR0H = (uint8_t)(BAUD_PRESCALE>>8);
    UBRR0L = (uint8_t)BAUD_PRESCALE;
    /* Enable receiver and transmitter */
    UCSR0B |= (1<<RXEN0)|(1<<TXEN0);
    /* Set frame format: 8data, 1stop bit 8N1 => 86uS for a byte*/
    UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00);
    /*enable Rx and Tx Interrupts*/
    UCSR0B |= (1 << RXCIE0) | (1 << TXCIE0); //<- this was the problem
    /*initialize the RingBuffer*/
    RingBuffer_Init(&RxBuffer);
    sei();
}

问题是我最初使用基于中断的传输,但是后来我更改了设计,并为Tx序列进行了10ms的轮询,并且忘记了在初始化过程中也更改了这条线。

非常感谢Peter Gibson指出这一点。