ATtiny85多路PCINT引脚变化中断最优处理方案咨询
关于ATtiny85 PCINT中断处理方案的解答
方案合理性判断
你计划将大量业务逻辑写入PCINT中断服务函数的方案属于不合理的设计,核心原因如下:
- 中断服务函数的核心设计原则是尽可能短平快,ATtiny85本身硬件资源有限,中断触发时会暂停所有低优先级任务和主程序逻辑,如果你在中断里执行耗时操作(比如DALI总线通信的transmit操作、多分支逻辑处理),会导致后续其他中断(比如你要开启的Timer0中断)被延迟响应,甚至出现事件丢失的情况,比如旋转编码器跳沿漏检导致计数错误、串口接收丢比特。
- 你用到的
dali.transmit这类通信函数大概率自带阻塞等待逻辑,放在中断里会直接让整个系统卡在中断上下文,严重时会触发看门狗复位或者完全失去响应。 - 你本身的需求是主程序休眠靠中断唤醒,完全不需要把业务逻辑塞到中断里就能实现。
最优实现方案
你可以用「中断仅打标记、主循环处理业务」的架构实现需求,完全兼容休眠唤醒的逻辑:
- 首先保留你现有的引脚变化检测逻辑,中断里只做事件标记,不做业务处理:
先定义一组volatile修饰的事件标记变量:
volatile uint8_t event_flags = 0; #define EVENT_ENCODER_CHANGE 0x01 #define EVENT_KEY_PRESS 0x02 #define EVENT_UART_RX_START 0x04
修改ISR逻辑,检测到对应引脚变化就给对应的标记位置位,其余操作都不做:
ISR (PCINT0_vect) { uint8_t changedbits; changedbits = PINB ^ portbhistory; portbhistory = PINB; if(changedbits & (1 << PB0)) event_flags |= EVENT_UART_RX_START; if(changedbits & (1 << PB1)) event_flags |= EVENT_ENCODER_CHANGE; if(changedbits & (1 << PB2)) event_flags |= EVENT_KEY_PRESS; }
如果Timer0开启这类外设配置操作需要极快响应,也可以放在中断里执行,但DALI通信这类耗时业务绝对不能放在中断中。
- 主循环里处理业务逻辑,无事件时直接进入休眠:
int main(void) { // 你原来的GPIO、中断初始化代码保留 DDRB &= ~((1 << DDB0) | (1 << DDB1) | (1 << DDB2)); PORTB |= ((1 << PORTB0) | (1 << PORTB1) | (1 << PORTB2)); PCICR |= (1 << PCIE0); PCMSK0 |= (1 << PCINT0); sei(); while(1) { // 先判断有没有待处理事件 if (event_flags) { // 临时关中断防止标记位在处理时被改写,处理完成后再开中断 cli(); uint8_t local_flags = event_flags; event_flags = 0; sei(); if (local_flags & EVENT_UART_RX_START) { // 放你之前写的Timer0配置代码 TCNT0 = 0; OCR0A = SERIAL_BIT_TIME; position = 0; TIMSK |= 1 << OCIE0A; TIFR |= 1 << OCF0A; PCMSK &= ~(1 << SERIAL_RECEIVE); } if (local_flags & EVENT_KEY_PRESS) { // 放按键处理+DALI通信代码 if (lightState) { dali.transmit(ADDRESS, OFF); lightState = 0; } else { dali.transmit(ADDRESS, ON); lightState = 1; } } if (local_flags & EVENT_ENCODER_CHANGE) { // 放旋转编码器计数逻辑 } } // 没有事件时进入休眠,等待PCINT唤醒 sleep_mode(); } }
- 额外注意:所有在中断和主程序里都要用到的变量,一定要加
volatile修饰,防止编译器优化导致值不同步。
内容的提问来源于stack exchange,提问作者jonas
相关产品推荐
相关产品推荐

