AVR ATmega在主循环之前使用printf时保持重置
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AVR ATmega在主循环之前使用printf时保持重置相关的知识,希望对你有一定的参考价值。
我正在AVR avr-libc微控制器上使用ATmega328P开发一个C应用程序。由于我没有ICE调试器,我跟随these instructions和this tutorial使stdio.h
函数如printf
能够使用硬件UART作为stdout
。
这是有效的,我可以在连接到我的目标板的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();
}
}
上面的代码产生以下输出:
START LOOP LOOP LOOP LOOP LOOP LOOP ... LOOP
这是预料之中的。如果我们评论printf("START ")
线它产生这个:
LOOP LOOP LOOP LOOP LOOP LOOP LOOP ... LOOP
这也没关系。问题是,如果我在printf
循环中没有任何while
,它会像这样:
START START START START START START ... START
这清楚地表明处理器正在重新启动,因为预期的输出只是一个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;
}
}
事实证明,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
来假定正确的名称。在制作和测试驱动程序的过程中,为UDRE数据空中断选择“TX_VECTOR”最终屏蔽了我还没有定义USART0_TX_vect
的事实(这是正在进行的工作,我甚至可能都不需要两者... 。)
现在我刚刚为interrupt service routine定义了一个空的USART0_TX_vect
(ISR),并且该东西不再重置,显示@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);
}
固定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 == '
') {
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
时,永远不会触发此中断。
来自the docs
捕获所有中断向量
如果发生意外中断(中断已启用且未安装处理程序,这通常表示存在错误),则默认操作是通过跳转到重置向量来重置设备。您可以通过提供名为BADISR_vect的函数来覆盖它,该函数应该使用ISR()来定义。 (名称BADISR_vect实际上是__vector_default的别名。后者必须在汇编代码中使用,以防万一。)
我现在遇到了同样的情况,但由于我在stackoverflow上没有很高的声誉,我不能投票。
这是我的初始化过程的片段,这给我带来了这个问题:
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();
}
问题是我最初使用基于中断的传输,但后来我改变了设计并进行了10ms轮询Tx序列,并忘记在init过程中更改此行。
非常感谢Peter Gibson指出这一点。
以上是关于AVR ATmega在主循环之前使用printf时保持重置的主要内容,如果未能解决你的问题,请参考以下文章
USART 传输 - AVR (atmega169p) 接收 0 或 null 而不是 char 'a'