ATtiny816/826外部中断引发MCU运行变慢问题求助
问题背景
基于ATtiny816开发5路霍尔传感器转速表,通过上升沿中断检测脉冲。用晶体管模拟不同频率方波测试时,MCU会逐渐出现运行变慢现象,故障出现时间从数分钟到数小时不等。已确认:
- 内部32K时钟下故障出现更快,切换为20MHz时钟后延迟出现
- 更换ATtiny826芯片后问题依旧
- 电源、输入波形均正常,怀疑MCU陷入中断异常
测试输入的中断间隔:PB5-120ms、PB4-60ms、PB3-40ms、PB2-30ms、PB1-24ms
测试代码
/* * Attiny816 VQFN TeST.c * * Created: 01/11/2022 10:11:41 * Author : */ #define F_CPU 32768UL #include <avr/io.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <avr/interrupt.h> #include <avr/wdt.h> #include <util/delay.h> #include <avr/eeprom.h> #include <avr/pgmspace.h> #define USART0_BAUD_RATE(BAUD_RATE) ((float)(F_CPU * 64 / (16 * (float)BAUD_RATE)) + 0.5) void USART0_init(void); void USART0_sendChar(char c); void USART0_sendString(char *str); int USART0_printChar(char c, FILE *stream); void reset_screen(void); FILE USART_stream = FDEV_SETUP_STREAM(USART0_printChar, NULL, _FDEV_SETUP_WRIT); uint16_t speed_read(uint8_t wejscie_TACHO); int main(void) { CPU_CCP = CCP_IOREG_gc; CLKCTRL_MCLKCTRLA = CLKCTRL_CLKSEL_OSCULP32K_gc; /*32.768 kHz */ CPU_CCP = CCP_IOREG_gc; CLKCTRL_MCLKCTRLB = 0 << CLKCTRL_PEN_bp; /* prescaler DISABLED */ /* --- ALARM LED'S --- */ PORTC.DIRSET = PIN3_bm; // F1 ALARM LED PORTC.DIRSET = PIN2_bm; // F2 ALARM LED PORTC.DIRSET = PIN1_bm; // F3 ALARM LED PORTC.DIRSET = PIN0_bm; // F4 ALARM LED PORTB.DIRSET = PIN0_bm; // F5 ALARM LED /* --- USART --- */ USART0_init(); printf( "%cc", 27 ); /* --- TACHO --- */ PORTB.PIN5CTRL = PORT_ISC_INTDISABLE_gc; //TACHO_1_DIS PORTB.PIN4CTRL = PORT_ISC_INTDISABLE_gc; //TACHO_2_DIS PORTB.PIN3CTRL = PORT_ISC_INTDISABLE_gc; //TACHO_3_DIS PORTB.PIN2CTRL = PORT_ISC_INTDISABLE_gc; //TACHO_4_DIS PORTB.PIN1CTRL = PORT_ISC_INTDISABLE_gc; //TACHO_5_DIS sei(); /* Replace with your application code */ while (1) { printf("\n\r Speed 1 - %u[rpm]", speed_read(1)); printf("\n\r Speed 2 - %u[rpm]", speed_read(2)); printf("\n\r Speed 3 - %u[rpm]", speed_read(3)); printf("\n\r Speed 4 - %u[rpm]", speed_read(4)); printf("\n\r Speed 5 - %u[rpm]", speed_read(5)); USART0_sendString("\n\r"); } } ISR( PORTB_PORT_vect ) { switch ( PORTB_INTFLAGS ) { case (32): // TACHO_1 - PB5 (2^(5-1) = 32) ////tacho_now[1] = TCA0.SINGLE.CNT; PORTB.INTFLAGS |= PORT_INT5_bm; /* clear interrupt flag */ break; case (16): // TACHO_2 - PB4 ////tacho_now[2] = TCA0.SINGLE.CNT; // tacho_cnt; PORTB.INTFLAGS |= PORT_INT4_bm; /* clear interrupt flag */ break; case (8): // TACHO_3 - PB3 ////tacho_now[3] = TCA0.SINGLE.CNT; PORTB.INTFLAGS |= PORT_INT3_bm; /* clear interrupt flag */ break; case (4): // TACHO_4 - PB2 //tacho_now[4] = TCA0.SINGLE.CNT; PORTB.INTFLAGS |= PORT_INT2_bm; /* clear interrupt flag */ break; case (2): // TACHO_5 - PB1 ////tacho_now[5] = TCA0.SINGLE.CNT; PORTB.INTFLAGS |= PORT_INT1_bm; /* clear interrupt flag */ break; default: break; } } void USART0_init(void) { PORTMUX.CTRLB = PORTMUX_USART0_ALTERNATE_gc; PORTA.DIRSET = PIN1_bm; // PA1 - TXD jako wyjscie USART0.BAUD = (uint16_t)USART0_BAUD_RATE( 1200 ); // set the Baud Rate USART0.CTRLB |= USART_TXEN_bm; // enable the Transmitter // Set frame format USART0.CTRLC = USART_CMODE_ASYNCHRONOUS_gc // Asynchronous Mode | USART_CHSIZE_8BIT_gc // Character size: 8 bit | USART_PMODE_DISABLED_gc // No Parity | USART_SBMODE_1BIT_gc; // 1 stop bit stdout = &USART_stream; _delay_ms(250); } void USART0_sendChar(char c) { while ( !(USART0.STATUS & USART_DREIF_bm) ) { ; // do nothing } USART0.TXDATAL = c; } void USART0_sendString(char *str) { for(size_t i = 0; i < strlen(str); i++) { USART0_sendChar(str[i]); } } int USART0_printChar(char c, FILE *stream) { USART0_sendChar(c); return 0; } uint16_t speed_read(uint8_t wejscie_TACHO) { uint32_t speed = 0; /* --- disable all TACHO input --- */ PORTB.PIN5CTRL = PORT_ISC_INTDISABLE_gc; //TACHO_1_DIS PORTB.PIN4CTRL = PORT_ISC_INTDISABLE_gc; //TACHO_2_DIS PORTB.PIN3CTRL = PORT_ISC_INTDISABLE_gc; //TACHO_3_DIS PORTB.PIN2CTRL = PORT_ISC_INTDISABLE_gc; //TACHO_4_DIS PORTB.PIN1CTRL = PORT_ISC_INTDISABLE_gc; //TACHO_5_DIS //_delay_ms(100); /* --- enable [wejscie_TACHO] only --- */ switch ( wejscie_TACHO ) { case (1): speed = 500; PORTC.OUTTGL = PIN3_bm;// _delay_ms(200); PORTB.PIN5CTRL = PORT_ISC_RISING_gc; break; case (2): speed = 1000; PORTC.OUTTGL = PIN2_bm; //_delay_ms(200); PORTB.PIN4CTRL = PORT_ISC_RISING_gc; break; case (3): speed = 1500; PORTC.OUTTGL = PIN1_bm; //_delay_ms(200); PORTB.PIN3CTRL = PORT_ISC_RISING_gc; break; case (4): speed = 2000; PORTC.OUTTGL = PIN0_bm; //_delay_ms(200); PORTB.PIN2CTRL = PORT_ISC_RISING_gc; break; case (5): speed = 2500; PORTB.OUTTGL = PIN0_bm; //_delay_ms(200); PORTB.PIN1CTRL = PORT_ISC_RISING_gc; break; default: break; } _delay_ms(250); return speed; }
问题分析
中断标志位清除逻辑错误
中断服务函数中使用PORTB.INTFLAGS |= PORT_INTx_bm清除标志位,这是错误操作。AVR架构中,中断标志位需通过写入1清除,但该写法可能导致标志位无法正确清除,甚至误触发其他中断,长期积累会导致中断异常堆积,拖慢系统运行。中断频繁切换的竞态条件
speed_read函数每次先禁用所有中断,再单独启用一路,此过程中若有中断触发,会导致标志位被置位但无法处理,引发时序混乱。同时主循环每次仅处理一路中断,其余路的中断请求被丢弃,进一步加剧异常概率。32K时钟下波特率精度问题
32768Hz时钟搭配1200波特率时,计算出的波特率寄存器值存在约0.7%误差,长期运行会导致USART输出异常,间接影响系统表现。主循环延迟累积
每次speed_read调用包含250ms延迟,5次调用累计1250ms,加上USART输出时间,主循环周期过长,中断无法及时响应,导致请求堆积。
解决方案
1. 修正中断标志位清除方式
改为先读取所有触发的中断标志,一次性清除后再逐个处理:
ISR( PORTB_PORT_vect ) { uint8_t int_flags = PORTB_INTFLAGS; PORTB_INTFLAGS = int_flags; // 一次性清除所有触发的中断标志 if (int_flags & PORT_INT5_bm) { // PB5中断处理逻辑 } if (int_flags & PORT_INT4_bm) { // PB4中断处理逻辑 } if (int_flags & PORT_INT3_bm) { // PB3中断处理逻辑 } if (int_flags & PORT_INT2_bm) { // PB2中断处理逻辑 } if (int_flags & PORT_INT1_bm) { // PB1中断处理逻辑 } }
2. 优化中断管理
保持所有5路中断始终启用,在中断服务函数中记录每一路的脉冲时间戳,主循环通过计算时间差获取转速,避免频繁切换中断配置:
- 定义数组存储每路的上次中断时间(使用定时器获取)
- 中断服务函数中更新对应通道的时间戳
- 主循环计算时间差并转换为转速
3. 调整USART配置(32K时钟场景)
更换为更匹配的波特率(如9600),或使用同步模式降低误差,也可改用整数运算计算波特率寄存器值,避免浮点数误差。
4. 优化主循环时序
移除speed_read中的_delay_ms(250),改用定时器定时更新转速,避免主循环被阻塞,确保中断及时响应。
额外建议
- 启用看门狗定时器,系统异常时自动复位MCU,提升可靠性
- 精简中断服务函数,避免复杂操作,降低中断阻塞风险
- 用示波器监控时钟信号和中断引脚波形,排查是否存在时钟漂移或干扰
内容的提问来源于stack exchange,提问作者Robert

