Proteus仿真ATmega328P调用USART_RX_vect中断出现无效操作码错误
ATmega328P(Arduino NANO)蓝牙控制伺服电机/水泵代码仿真故障排查
问题说明
我为ATmega328P(Arduino NANO)编写了通过HC-10蓝牙模块控制伺服电机和水泵的代码,编译器编译无错误,但仿真无法运行。之前调用不带inline关键字的函数时也出现过同样问题,现在调用USART_RX_vect中断再次出现该问题。我是嵌入式开发新手,希望得到帮助。
原代码
#include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> enum { SERVO_MAX_RIGHT = 500, SERVO_MAX_LEFT = 248, SERVO_MID = 375, BAUD = 9600, FOSC = 16000000, MYUBRR = FOSC/16/BAUD-1, BUT_L = 97, // 左转按键ASCII码 BUT_R = 100, // 右转按键ASCII码 BUT_M = 115, // 水泵启停按键ASCII码 }; struct button { unsigned char leftOn; unsigned char rightOn; unsigned char moveOn; unsigned char pressed; } button; static inline void portInit() { DDRC &= ~(1<<DDC0); // 设置按键引脚为输入 DDRC &= ~(1<<DDC1); DDRC &= ~(1<<DDC2); PORTC |= (1<<PORTC0); // 按键引脚启用上拉电阻 PORTC |= (1<<PORTC1); PORTC |= (1<<PORTC2); DDRB |= (1<<PB0); // 设置水泵引脚为输出 PORTB &= ~(1<<PB0); // 水泵初始状态关闭 DDRB |= (1<<PB1); // 设置伺服电机引脚为输出 PORTB &= ~(1<<PB1); // 伺服引脚初始电平低 } static inline void timerInit() { unsigned char sreg; TCCR1A |= (1<<COM1A1); // 配置非反相PWM模式 TCCR1A &= ~(1<<COM1A0); TCCR1A &= ~(1<<WGM10); // 配置Fast PWM模式(ICR1为TOP) TCCR1A |= (1<<WGM11); TCCR1B |= (1<<WGM12); TCCR1B |= (1<<WGM13); TCCR1B |= (1<<CS10); // 设置预分频器为64 TCCR1B |= (1<<CS11); TCCR1B &= ~(1<<CS12); sreg = SREG; // 保存全局中断状态 cli(); ICR1 = 5000; // 设置PWM周期(20ms,对应50Hz) SREG = sreg; // 恢复全局中断状态 } static inline void uartInit(unsigned int ubrr) { UBRR0H = (unsigned char) (ubrr>>8); // 设置波特率高字节 UBRR0L = (unsigned char)ubrr; // 设置波特率低字节 UCSR0B = 0b10011000; // 启用RX、TX及RX中断 UCSR0C = ~(1<<USBS0); // 设置1个停止位 UCSR0C = ~(1<<UCSZ02); // 配置8位数据位(此处错误,直接赋值会覆盖之前设置) UCSR0C = (1<<UCSZ01); UCSR0C = (1<<UCSZ00); } static inline void uart_send(char* str) { while(*str) { while(UCSR0A & (1<<UDRE0)) { // 逻辑错误:应该等待UDRE0置位,当前条件写反 UDR0 = *str; str++; } } } static inline void uart_boatControl(unsigned char read) { uint8_t val; if(read) { val = UDR0; switch(val) { case BUT_L: OCR1A = SERVO_MAX_LEFT; break; case BUT_R: OCR1A = SERVO_MAX_RIGHT; break; case BUT_M: PORTB = (1<<PB0); // 错误:直接赋值会覆盖PB1引脚状态 break; default: OCR1A = SERVO_MID; PORTB = ~(1<<PB0); // 错误:直接赋值会翻转所有引脚,包括PB1 } } } ISR(USART_RX_vect){ button.pressed = UCSR0A & (1<<RXC0); // 冗余:ISR触发时RXC0必然置位 uart_boatControl(button.pressed); } int main(void) { portInit(); timerInit(); OCR1A = SERVO_MID; // 伺服初始位置居中 _delay_ms(500); uartInit(MYUBRR); while(1) { button.leftOn = (!(PINC&(1<<PC0))); button.rightOn = (!(PINC&(1<<PC1))); button.moveOn = (!(PINC&(1<<PC2))); if (button.leftOn) { OCR1A = SERVO_MAX_LEFT; } else if (button.rightOn) { OCR1A = SERVO_MAX_RIGHT; } else if (button.moveOn) { PORTB |= (1<<PB0); } else { OCR1A = SERVO_MID; PORTB &= ~(1<<PB0); } } return 0; }
关键问题及修复方案
1. 全局中断未启用
USART接收中断需要全局中断开启才能触发,主函数中初始化UART后需添加sei();开启全局中断。
2. UCSR0C寄存器配置错误
多次直接赋值UCSR0C会覆盖之前的配置,正确做法是用位操作组合配置:
UCSR0C &= ~(1<<USBS0); // 设置1个停止位 UCSR0C |= (1<<UCSZ01) | (1<<UCSZ00); // 配置8位数据位
3. uart_send函数逻辑错误
等待发送缓冲区为空的条件写反了,正确写法:
while(!(UCSR0A & (1<<UDRE0))); // 等待发送缓冲区为空 UDR0 = *str; str++;
4. 中断函数冗余判断
USART_RX_vect触发时,必然有数据接收,无需判断RXC0位,直接读取UDR0即可,同时避免主循环和中断的资源竞争:
ISR(USART_RX_vect){ uint8_t val = UDR0; switch(val) { case BUT_L: OCR1A = SERVO_MAX_LEFT; break; case BUT_R: OCR1A = SERVO_MAX_RIGHT; break; case BUT_M: PORTB ^= (1<<PB0); // 改用翻转操作,不影响其他引脚 break; default: OCR1A = SERVO_MID; PORTB &= ~(1<<PB0); // 仅关闭水泵,不影响伺服引脚 } }
5. PORTB直接赋值的风险
直接赋值PORTB会修改所有引脚状态,应该用位操作|=或&=来单独控制PB0(水泵),避免影响PB1(伺服)的PWM输出。
6. inline函数的误解
inline只是编译器的优化建议,不会导致仿真失败,之前的问题本质是代码逻辑错误,和是否加inline无关。
修正后的完整代码
#include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> enum { SERVO_MAX_RIGHT = 500, SERVO_MAX_LEFT = 248, SERVO_MID = 375, BAUD = 9600, FOSC = 16000000, MYUBRR = FOSC/16/BAUD-1, BUT_L = 97, // 左转按键ASCII码 BUT_R = 100, // 右转按键ASCII码 BUT_M = 115, // 水泵启停按键ASCII码 }; struct button { unsigned char leftOn; unsigned char rightOn; unsigned char moveOn; } button; static inline void portInit() { DDRC &= ~(1<<DDC0) & ~(1<<DDC1) & ~(1<<DDC2); // 批量设置按键引脚为输入 PORTC |= (1<<PORTC0) | (1<<PORTC1) | (1<<PORTC2); // 批量启用上拉电阻 DDRB |= (1<<PB0) | (1<<PB1); // 批量设置水泵、伺服引脚为输出 PORTB &= ~(1<<PB0); // 水泵初始关闭 } static inline void timerInit() { unsigned char sreg = SREG; cli(); TCCR1A = (1<<COM1A1) | (1<<WGM11); // 配置非反相PWM + Fast PWM模式 TCCR1B = (1<<WGM12) | (1<<WGM13) | (1<<CS10) | (1<<CS11); // Fast PWM + 预分频64 ICR1 = 5000; // 设置20ms周期 SREG = sreg; } static inline void uartInit(unsigned int ubrr) { UBRR0H = (unsigned char)(ubrr >> 8); UBRR0L = (unsigned char)ubrr; UCSR0B = (1<<RXEN0) | (1<<TXEN0) | (1<<RXCIE0); // 启用RX、TX、RX中断 UCSR0C = (1<<UCSZ01) | (1<<UCSZ00); // 8位数据位,1个停止位(默认) } static inline void uart_send(char* str) { while(*str) { while(!(UCSR0A & (1<<UDRE0))); // 等待发送缓冲区就绪 UDR0 = *str++; } } ISR(USART_RX_vect){ uint8_t val = UDR0; switch(val) { case BUT_L: OCR1A = SERVO_MAX_LEFT; break; case BUT_R: OCR1A = SERVO_MAX_RIGHT; break; case BUT_M: PORTB ^= (1<<PB0); // 翻转水泵状态 break; default: OCR1A = SERVO_MID; PORTB &= ~(1<<PB0); // 关闭水泵 } } int main(void) { portInit(); timerInit(); OCR1A = SERVO_MID; _delay_ms(500); uartInit(MYUBRR); sei(); // 开启全局中断 while(1) { button.leftOn = !(PINC & (1<<PC0)); button.rightOn = !(PINC & (1<<PC1)); button.moveOn = !(PINC & (1<<PC2)); if (button.leftOn) { OCR1A = SERVO_MAX_LEFT; } else if (button.rightOn) { OCR1A = SERVO_MAX_RIGHT; } else if (button.moveOn) { PORTB |= (1<<PB0); } else { // 仅在无按键操作时才恢复默认状态,避免和中断冲突 if((OCR1A != SERVO_MID) || (PORTB & (1<<PB0))) { OCR1A = SERVO_MID; PORTB &= ~(1<<PB0); } } } return 0; }
内容的提问来源于stack exchange,提问作者vpos
相关产品推荐
相关产品推荐

