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

如何实现PIC16F1827按键即时切换LED闪烁模式?

解决PIC16F1827按键即时切换LED闪烁模式的问题

嘿,我一眼就看出你代码里的问题了——那些pat1()到pat4()函数里的__delay_ms()都是阻塞式的!一旦主循环进入某个模式函数,就会卡在里面把所有延时执行完,哪怕中断已经检测到按键按下更新了sw_data,也得等当前模式的所有闪烁步骤走完才能切换。

要实现按键按下即时响应切换,核心就是把阻塞式的延时改成非阻塞的状态机+定时器驱动,让主循环能一直“盯着”按键状态,同时LED闪烁的节奏由定时器计数来控制,不会卡住程序。

下面是修改后的完整代码,我会在后面解释关键改动:

#include <stdio.h>
#include <stdlib.h>
#include <xc.h>
#define _XTAL_FREQ 4000000

/* CONFIG 配置请自行补充 */

void internal_osc();
void io_int();
void tmr0_int();
void sw_scan();

// 新增:定义闪烁模式的状态枚举,把每个模式的步骤拆成单独状态
typedef enum {
    PAT_IDLE,
    PAT1_STEP1, PAT1_STEP2, PAT1_STEP3, PAT1_STEP4,
    PAT2_STEP1, PAT2_STEP2, PAT2_STEP3, PAT2_STEP4,
    PAT3_STEP1, PAT3_STEP2, PAT3_STEP3, PAT3_STEP4,
    PAT4_STEP1, PAT4_STEP2, PAT4_STEP3, PAT4_STEP4
} PatternState;

PatternState current_pat_state = PAT_IDLE;
char tmr0_cnt;
char sw_data = 1; // 默认上电进入模式1
char SwStatus;
char cnt;
// 新增:用于非阻塞延时的计数变量,和TMR0中断同步
char blink_delay_cnt = 0;
const char BLINK_DELAY_TICKS = 15; // 和你原来的cnt>=15对应,约500ms

void main(void){
    internal_osc();
    io_int();
    tmr0_int();
    
    while(1){
        // 主循环先检查当前模式,切换模式时重置状态和延时计数器
        switch(sw_data){
            case 1 : 
                if(current_pat_state != PAT1_STEP1){
                    current_pat_state = PAT1_STEP1;
                    blink_delay_cnt = 0;
                }
                break;
            case 2 : 
                if(current_pat_state != PAT2_STEP1){
                    current_pat_state = PAT2_STEP1;
                    blink_delay_cnt = 0;
                }
                break;
            case 3 : 
                if(current_pat_state != PAT3_STEP1){
                    current_pat_state = PAT3_STEP1;
                    blink_delay_cnt = 0;
                }
                break;
            case 4 : 
                if(current_pat_state != PAT4_STEP1){
                    current_pat_state = PAT4_STEP1;
                    blink_delay_cnt = 0;
                }
                break;
            default : 
                PORTB = 0x00;
                current_pat_state = PAT_IDLE;
                break;
        }

        // 只有当延时计数到达设定值时,才切换闪烁状态
        if(blink_delay_cnt >= BLINK_DELAY_TICKS){
            blink_delay_cnt = 0;
            switch(current_pat_state){
                // 模式1的步骤流转
                case PAT1_STEP1:
                    PORTB = 0x01;
                    current_pat_state = PAT1_STEP2;
                    break;
                case PAT1_STEP2:
                    PORTB = 0x02;
                    current_pat_state = PAT1_STEP3;
                    break;
                case PAT1_STEP3:
                    PORTB = 0x04;
                    current_pat_state = PAT1_STEP4;
                    break;
                case PAT1_STEP4:
                    PORTB = 0x08;
                    current_pat_state = PAT1_STEP1;
                    break;
                // 模式2的步骤流转
                case PAT2_STEP1:
                    PORTB = 0x01;
                    current_pat_state = PAT2_STEP2;
                    break;
                case PAT2_STEP2:
                    PORTB = 0x03;
                    current_pat_state = PAT2_STEP3;
                    break;
                case PAT2_STEP3:
                    PORTB = 0x07;
                    current_pat_state = PAT2_STEP4;
                    break;
                case PAT2_STEP4:
                    PORTB = 0x0f;
                    current_pat_state = PAT2_STEP1;
                    break;
                // 模式3的步骤流转
                case PAT3_STEP1:
                    PORTB = 0x0e;
                    current_pat_state = PAT3_STEP2;
                    break;
                case PAT3_STEP2:
                    PORTB = 0x0d;
                    current_pat_state = PAT3_STEP3;
                    break;
                case PAT3_STEP3:
                    PORTB = 0x0b;
                    current_pat_state = PAT3_STEP4;
                    break;
                case PAT3_STEP4:
                    PORTB = 0x07;
                    current_pat_state = PAT3_STEP1;
                    break;
                // 模式4的步骤流转
                case PAT4_STEP1:
                    PORTB = 0x0e;
                    current_pat_state = PAT4_STEP2;
                    break;
                case PAT4_STEP2:
                    PORTB = 0x0c;
                    current_pat_state = PAT4_STEP3;
                    break;
                case PAT4_STEP3:
                    PORTB = 0x08;
                    current_pat_state = PAT4_STEP4;
                    break;
                case PAT4_STEP4:
                    PORTB = 0x00;
                    current_pat_state = PAT4_STEP1;
                    break;
                default:
                    PORTB = 0x00;
                    break;
            }
        }
    }
}

void interrupt ISR(void){
    if(INTCONbits.TMR0IF){
        INTCONbits.TMR0IF = 0;
        TMR0 = 130;
        cnt++;
        blink_delay_cnt++; // 每次中断递增闪烁延时计数器
        if(cnt>=15){
            cnt=0;
            sw_scan(); // 每约500ms扫描一次按键
        }
    }
}

void internal_osc(void){
    OSCCON = 0x6a;
}

void io_int(void){
    TRISA = 0x01;
    TRISB = 0x00;
    ANSELA = 0x00;
    ANSELB = 0x00;
}

void tmr0_int(void){
    OPTION_REG = 0x82;
    TMR0 = 130;
    INTCONbits.TMR0IE = 1;
    INTCONbits.GIE = 1;
}

void sw_scan(void){
    if(PORTAbits.RA0 == 0){
        __delay_ms(10); // 短延时消抖,只在中断里执行,不影响主循环
        if(PORTAbits.RA0 == 0){
            if(SwStatus==0){
                SwStatus = 1;
                sw_data++;
                if(sw_data>4){
                    sw_data = 1; // 模式循环,超过4回到1
                }
            }
        } 
    } else {
        SwStatus = 0;
    }
}

关键改动详解

  1. 干掉阻塞延时,用状态机替代

    • 原来的每个pat()函数是一次性跑完所有闪烁步骤,现在把每个步骤拆成单独的状态(比如PAT1_STEP1对应模式1的第一步亮LED1)。主循环每次只处理当前状态的LED输出,然后等定时器计数到了再切换下一个状态,全程不阻塞。
  2. 主循环实时响应模式切换

    • 主循环不再被模式函数卡住,每次循环都会检查sw_data。一旦检测到按键更新了模式,立刻重置当前闪烁状态和延时计数器,直接进入新模式的第一步,实现即时切换。
  3. 定时器同步闪烁节奏

    • 新增blink_delay_cnt,在TMR0中断里递增,当计数达到BLINK_DELAY_TICKS(对应你原来的500ms)时才切换闪烁状态,和原来的延时效果一致,但完全非阻塞。
  4. 优化按键逻辑

    • 给sw_data加了初始值1,上电直接进入模式1;调整了模式循环逻辑,超过4后回到1,避免进入默认的全灭状态。
    • 按键消抖的短延时只在中断里每500ms执行一次,不会影响主循环的响应速度。

这样修改后,只要中断检测到按键按下,主循环下一次迭代就会立即切换到新的闪烁模式,再也不用等当前模式的延时走完啦!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:30:00