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

Attiny85引脚错误下基于引脚变化中断+Timer1解码DCC信号故障排查

Attiny85 DCC解码故障排查与修复方案

核心问题1:Timer1预分频配置完全错误,导致计时精度不足

你的Timer1设置了预分频1024(TCCR1 = (1 << CS12)),在8MHz时钟下,单个计数周期为 1024/8000000 = 128μs。而DCC的1位时长仅116μs,比单个计数周期还短,timerVal只能是0或1,完全无法区分116μs和200μs的位间隔,这是解码失败的根本原因。

修复方案:

修改Timer1预分频为64,单个计数周期为64/8000000 = 8μs,刚好能精准测量DCC位间隔:

void setupTimer1()
{
    TCCR1 = 0;             // Reset control register
    TCNT1 = 0;             // Reset counter
    // 预分频64:CS11=1 + CS10=1
    TCCR1 = (1 << CS11) | (1 << CS10);
}

同时调整位判断的阈值为计数tick数:

const uint8_t ONE_BIT_TICKS  = 14;  // 116μs / 8μs ≈14.5
const uint8_t ZERO_BIT_TICKS = 25;  // 200μs / 8μs =25

// ISR中的判断逻辑修改为:
if( timerVal > (ONE_BIT_TICKS - 2) && timerVal < (ONE_BIT_TICKS + 2) )
{
    addBitToBuffer(1); // 先入缓冲区,主循环处理
}
else if( timerVal > (ZERO_BIT_TICKS - 3) && timerVal < (ZERO_BIT_TICKS + 3) )
{
    addBitToBuffer(0);
}

核心问题2:引脚变化中断标志位未清除,导致重复触发

Attiny85的引脚变化中断触发后,GIFR寄存器的PCIF位会置1,必须软件写1清除,否则会持续触发中断,导致ISR反复执行、丢比特或误判。

修复方案:

在ISR开头添加标志位清除代码:

ISR(PCINT0_vect)
{
    // 清除引脚变化中断标志
    GIFR |= (1 << PCIF);
    
    static uint8_t lastPinState = 1;
    // ... 剩余代码
}

核心问题3:ISR耗时过长,导致丢中断

你在ISR中直接调用dccBitHandler,该函数包含预amble计数、数据包解析等大量逻辑,执行时间远超DCC位间隔(116μs),会导致下一个中断到来时无法响应,直接丢比特。

修复方案:

ISR只做最小化操作:计时、位判断,将有效位存入环形缓冲区,主循环中再处理解码逻辑:

// 环形缓冲区定义
#define BIT_BUFFER_SIZE 32
volatile uint8_t bitBuffer[BIT_BUFFER_SIZE];
volatile uint8_t bitBufferHead = 0;
volatile uint8_t bitBufferTail = 0;

// 向缓冲区添加位
static void addBitToBuffer(uint8_t bit)
{
    uint8_t nextHead = (bitBufferHead + 1) % BIT_BUFFER_SIZE;
    if(nextHead != bitBufferTail)
    {
        bitBuffer[bitBufferHead] = bit;
        bitBufferHead = nextHead;
    }
}

// ISR简化为:
ISR(PCINT0_vect)
{
    GIFR |= (1 << PCIF);
    static uint8_t lastPinState = 1;
    uint8_t pinState = (PINB & (1 << PB4)) ? 1 : 0;

    if (lastPinState == 1 && pinState == 0)
    {
        uint8_t timerVal = TCNT1;
        TCNT1 = 0;

        uint8_t bit = 2; // 无效位标记
        if( timerVal > (ONE_BIT_TICKS - 2) && timerVal < (ONE_BIT_TICKS + 2) )
        {
            bit = 1;
        }
        else if( timerVal > (ZERO_BIT_TICKS - 3) && timerVal < (ZERO_BIT_TICKS + 3) )
        {
            bit = 0;
        }
        
        if(bit != 2)
        {
            addBitToBuffer(bit);
        }
    }
    lastPinState = pinState;
}

// 主循环处理缓冲区
void loop()
{
    while(bitBufferHead != bitBufferTail)
    {
        uint8_t bit = bitBuffer[bitBufferTail];
        bitBufferTail = (bitBufferTail + 1) % BIT_BUFFER_SIZE;
        dccBitHandler(bit);
    }
    updateDcc();
}

其他优化点

  1. 配置PB4输入上拉:避免噪声干扰导致误触发中断
void setup()
{
    // PB4设为输入,启用内部上拉
    DDRB &= ~(1 << PB4);
    PORTB |= (1 << PB4);
    
    setupPinChangeInterrupt();
    setupTimer1();
    // ... 其他初始化
}
  1. 处理Timer1溢出:防止信号中断时计数溢出导致的错误
void setupTimer1()
{
    TCCR1 = 0;
    TCNT1 = 0;
    TCCR1 = (1 << CS11) | (1 << CS10);
    // 启用Timer1溢出中断
    TIMSK |= (1 << TOIE1);
}

volatile bool timerOverflow = false;
ISR(TIMER1_OVF_vect)
{
    timerOverflow = true;
}

// ISR中添加溢出判断
if (lastPinState == 1 && pinState == 0)
{
    if(timerOverflow)
    {
        timerOverflow = false;
        TCNT1 = 0;
        lastPinState = pinState;
        return;
    }
    
    uint8_t timerVal = TCNT1;
    TCNT1 = 0;
    timerOverflow = false;
    // ... 位判断逻辑
}

内容的提问来源于stack exchange,提问作者bas knippels

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 17:09:49