ATMega128的AVR代码在Proteus中异常循环问题排查
ATmega128代码仿真问题排查
我有一段用于ATmega128的代码,部分库和函数还没用到(代码尚未完成)——这个问题已经卡了我好几天。
在Proteus中仿真这段代码时,发现main函数陷入无限循环,虚拟终端持续重复执行ConfigureWatchdogTimer()函数。但我预期的行为是:配置函数仅运行一次,CPU进入睡眠模式,仅在USART0接收字节触发中断时唤醒。另外,编写的中断仅部分生效:持续发送字符时,字符偶尔才会出现在另一个终端中。
Proteus运行表现:虚拟终端持续重复显示看门狗配置菜单
代码片段
#include <avr/io.h> #include <avr/interrupt.h> #include <avr/sleep.h> #include <avr/delay.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define F_CPU 8000000UL #define BAUDRATE 9600 #define UBRR_VALUE ((F_CPU)/(16UL*BAUDRATE))-1 //For UBRR registers #define MASTER_WATCHDOG_MENU "\rEnter MS WD Choice (& period):\rA-30ms\rB-250ms\rC-500ms\rYour input:\0" #define SLAVE_WATCHDOG_MENU "\rEnter SLV WD Choice (& period):\rA-0.5s\rB-1.0s\rC-2.0s\rYour input:\0" #define USER_MENU "\rEnter choice (and period): 1-Mem Dump 2-Last Entry 3-Restart\rYour input:\0" void ConfigureXMEMMode(void){ MCUCR |= (1 << SRE); XMCRA = 0; XMCRB = (1 << XMM2) | (0 << XMM1) | (1 << XMM0); } void ConfigureUSARTInterfaces(void){ UBRR0H = (unsigned char)(UBRR_VALUE >> 8); UBRR0L = (unsigned char)(UBRR_VALUE); UBRR1H = (unsigned char)(UBRR_VALUE >> 8); UBRR1L = (unsigned char)(UBRR_VALUE); UCSR0B = (1<<RXEN0) | (1<<RXCIE0) | (1<<TXEN0) | (1<<TXCIE0); UCSR1B = (1<<RXEN1) | (1<<RXCIE1) | (1<<TXEN1) | (1<<TXCIE1); UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); UCSR1C = (1 << UCSZ01) | (1 << UCSZ00); } void USART0_TransmitByte(unsigned char byteToTransmit){ while(!(UCSR0A & (1<<UDRE0))); UDR0 = byteToTransmit; } void USART0_TransmitString(unsigned char *stringToTransmit){ unsigned char i = 0; for(i = 0; stringToTransmit[i] != '\0'; i++){ USART0_TransmitByte(stringToTransmit[i]); } } void USART1_TransmitByte(unsigned char byteToTransmit){ while(!(UCSR1A & (1<<UDRE1))); UDR1 = byteToTransmit; } void USART1_TransmitString(unsigned char *stringToTransmit){ unsigned char i = 0; for(i = 0; stringToTransmit[i] != '\0'; i++){ USART1_TransmitByte(stringToTransmit[i]); } } unsigned char USART0_ReceiveByte(void){ while(!(UCSR0A & (1<<RXC0))); return UDR0; } unsigned char USART1_ReceiveByte(void){ while(!(UCSR1A & (1<<RXC1))); return UDR1; } void ConfigureWatchdogTimer(void){ while(EECR & (1 << EEWE)); EEARH = 0x00; EEARL = 0x00; EECR = 1 << EERE; if(EEDR == 0xFF){ EEARL = 0x01; if(EEDR == 0xFF){ EECR = 0 << EERE; ConfigureMasterWD(); } } while(EECR & (1 << EEWE)); EEARH = 0x00; EEARL = 0x02; EECR = 1 << EERE; if(EEDR == 0xFF){ EEARL = 0x03; if (EEDR == 0xFF){ EECR = 0 << EERE; ConfigureSlaveWD(); } } } void ConfigureMasterWD(){ USART0_TransmitString(MASTER_WATCHDOG_MENU); unsigned char userInput = USART0_ReceiveByte(); switch(userInput){ case 'A': case 'a': WDTCR = (1 << WDE) | (0 << WDP2) | (0 << WDP1) | (1 << WDP0); //30ms break; case 'B': case 'b': WDTCR = (1 << WDE) | (1 << WDP2) | (0 << WDP1) | (0 << WDP0); //250ms break; case 'C': case 'c': WDTCR = (1 << WDE) | (1 << WDP2) | (0 << WDP1) | (1 << WDP0); //500ms break; } USART0_TransmitString("\rDone!\r"); } void ConfigureSlaveWD(){ USART0_TransmitString(SLAVE_WATCHDOG_MENU); unsigned char userInput = USART0_ReceiveByte(); switch(userInput){ case 'A': case 'a': WDTCR = (1 << WDE) | (1 << WDP2) | (0 << WDP1) | (1 << WDP0); //500ms break; case 'B': case 'b': WDTCR = (1 << WDE) | (1 << WDP2) | (1 << WDP1) | (0 << WDP0); //1.0s break; case 'C': case 'c': WDTCR = (1 << WDE) | (1 << WDP2) | (1 << WDP1) | (1 << WDP0); //2.0s break; } USART0_TransmitString("\rDone!\r"); } void Sleep(){ sei(); set_sleep_mode(SLEEP_MODE_IDLE); sleep_enable(); sleep_cpu(); } void WakeUp(){ sleep_disable(); cli(); } int main(void){ ConfigureXMEMMode(); ConfigureUSARTInterfaces(); ConfigureWatchdogTimer(); USART0_TransmitString("ATMEGA128 TO USART0"); USART1_TransmitString("ATMEGA128 TO USART1"); Sleep(); while(1); } ISR(USART0_RX_vect){ WakeUp(); new_user_read_char = UDR0; USART1_TransmitByte(UDR0); Sleep(); }
问题根源与修复方案
1. 看门狗触发MCU复位,导致main重复执行
你在ConfigureMasterWD和ConfigureSlaveWD中设置了WDE位(看门狗复位使能),但没有添加喂狗逻辑。看门狗超时后会直接触发MCU复位,导致main函数从头执行,因此ConfigureWatchdogTimer会反复运行。
- 修复:
- 如果需要看门狗用于睡眠唤醒而非复位,将
WDE替换为WDIE(看门狗中断使能):// 示例:配置30ms看门狗中断(无复位) WDTCR = (1<<WDIE) | (0<<WDP2)|(0<<WDP1)|(1<<WDP0); - 如果必须保留看门狗复位功能,需在主循环中定期喂狗:
// 喂狗操作 WDTCR |= (1<<WDCE)|(1<<WDE); WDTCR = (1<<WDE) | 你的分频配置;
- 如果需要看门狗用于睡眠唤醒而非复位,将
2. EEPROM读取逻辑错误
在ConfigureWatchdogTimer中,切换EEPROM地址后未重新触发读操作,导致EEDR始终是上一个地址的数据:
- 修复:每次切换地址后,重新设置
EECR |= (1<<EERE)触发读取:void ConfigureWatchdogTimer(void){ while(EECR & (1 << EEWE)); EEARH = 0x00; EEARL = 0x00; EECR |= (1 << EERE); // 触发读 if(EEDR == 0xFF){ EEARL = 0x01; EECR |= (1 << EERE); // 重新触发读地址0x01 if(EEDR == 0xFF){ ConfigureMasterWD(); } } while(EECR & (1 << EEWE)); EEARH = 0x00; EEARL = 0x02; EECR |= (1 << EERE); // 触发读 if(EEDR == 0xFF){ EEARL = 0x03; EECR |= (1 << EERE); // 重新触发读地址0x03 if (EEDR == 0xFF){ ConfigureSlaveWD(); } } }
3. 中断服务程序(ISR)异常
new_user_read_char未声明,会导致编译错误或运行异常;重复读取
UDR0,第一次读取后数据已被清空,第二次读取的是无效值;ISR中调用
Sleep()的逻辑不合理,容易导致中断嵌套或睡眠状态混乱。修复:
- 全局声明
unsigned char new_user_read_char; - 修改ISR逻辑,避免重复读取,并将睡眠逻辑放回主循环:
ISR(USART0_RX_vect){ new_user_read_char = UDR0; // 仅读取一次 USART1_TransmitByte(new_user_read_char); } - 调整
main和Sleep函数:void Sleep(){ set_sleep_mode(SLEEP_MODE_IDLE); sleep_enable(); sleep_cpu(); sleep_disable(); // 唤醒后自动退出睡眠 } int main(void){ ConfigureXMEMMode(); ConfigureUSARTInterfaces(); ConfigureWatchdogTimer(); USART0_TransmitString("ATMEGA128 TO USART0"); USART1_TransmitString("ATMEGA128 TO USART1"); sei(); // 全局开中断 while(1){ Sleep(); // 每次唤醒后重新进入睡眠 } }
- 全局声明
内容的提问来源于stack exchange,提问作者STT
相关产品推荐
相关产品推荐

