You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ATtiny816/826外部中断引发MCU运行变慢问题求助

ATtiny816/826转速表运行变慢问题排查与解决

问题背景

基于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;
}

问题分析

  1. 中断标志位清除逻辑错误
    中断服务函数中使用PORTB.INTFLAGS |= PORT_INTx_bm清除标志位,这是错误操作。AVR架构中,中断标志位需通过写入1清除,但该写法可能导致标志位无法正确清除,甚至误触发其他中断,长期积累会导致中断异常堆积,拖慢系统运行。

  2. 中断频繁切换的竞态条件
    speed_read函数每次先禁用所有中断,再单独启用一路,此过程中若有中断触发,会导致标志位被置位但无法处理,引发时序混乱。同时主循环每次仅处理一路中断,其余路的中断请求被丢弃,进一步加剧异常概率。

  3. 32K时钟下波特率精度问题
    32768Hz时钟搭配1200波特率时,计算出的波特率寄存器值存在约0.7%误差,长期运行会导致USART输出异常,间接影响系统表现。

  4. 主循环延迟累积
    每次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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 05:40:32