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

如何在PIC12F675中检测GPIO周期性变化——区分LED常亮与闪烁

问题:区分设备LED的常亮与闪烁状态(基于PIC12F675轮询方案)

我现在用PIC12F675搭配MPLAB X IDE做项目,通过光敏电阻监测一台设备的LED状态来判断它的运行情况。目前电路已经实现了基础功能:设备LED点亮时,单片机能收到输入信号,同步点亮自身的LED。现在用的是轮询方式,没有开启中断。设备通电后,它的LED只有两种状态:每秒约2次的闪烁,或者常亮,不会出现熄灭状态。

我现在卡在了定时监测的逻辑上:需要在1秒的监测窗口内,判断输入引脚(带下拉电阻)是否出现过低电平——如果有,就判定为闪烁状态;如果全程都是高电平,就是常亮状态。我C语言经验不多,只会做些单片机简单任务,想知道用C语言的哪些功能或逻辑能解决这个问题,还有具体怎么区分这两种状态?

以下是我的现有代码:

// PIC12F675 Configuration Bit Settings
// 'C' source line config statements
// CONFIG
#pragma config FOSC = INTRCIO // Oscillator Selection bits (INTOSC oscillator: CLKOUT function on GP4/OSC2/CLKOUT pin, I/O function on GP5/OSC1/CLKIN)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT enabled)
#pragma config PWRTE = ON // Power-Up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = OFF // GP3/MCLR pin function select (GP3/MCLR pin function is MCLR)
#pragma config BOREN = OFF // Brown-out Detect Enable bit (BOD enabled)
#pragma config CP = OFF // Code Protection bit (Program Memory code protection is disabled)
#pragma config CPD = OFF // Data Code Protection bit (Data memory code protection is disabled)
#define _XTAL_FREQ 4000000
#include <xc.h>
#include <stdlib.h>
#include <htc.h>
void init_ports(void) {
ANSEL = 0x00; // Set ports as digital IO not analog input.
ADCON0 = 0x00; // Shut off ADC.
CMCON = 0x07; // Shut off comparator.
VRCON = 0x00; // Shut off the voltage reference.
TRISIO = 0xA; // All GPIO pins output except 1&3.
GPIO = 0x00; // Make all pins LOW
T0IE = 0; // Disable timer interrupt.
GPIE = 0; // GPIO port change interrupt
GIE = 0; // Global interrupt for recognition of interrupts.
}
void main(void) {
init_ports();
/* Wait 1 second for power up. */
__delay_ms(1000);
while(1) {
// Check for input on GPIO 1
if (GPIO & 0b000010) {
// Turn on the LED on GPIO 4
GP4 = 1;
} else{
// Turn off the LED on GPIO 4
GP4 = 0;
}
}
}

解决方案

先明确核心逻辑:要区分常亮和闪烁,本质就是在固定时间窗口(比如1秒)内检测输入信号是否发生过变化——设备LED常亮时,你的输入引脚会一直是高电平;闪烁时,会周期性出现低电平(对应设备LED短暂熄灭)。下面给你两种适合你当前水平的实现方案:

方案1:纯软件轮询(简单易上手)

这个方案不用配置定时器,靠软件延时累计出1秒的监测窗口,期间持续检查输入状态,记录是否出现过低电平。

修改后的代码:

// PIC12F675 Configuration Bit Settings
// 'C' source line config statements
// CONFIG
#pragma config FOSC = INTRCIO // Oscillator Selection bits (INTOSC oscillator: CLKOUT function on GP4/OSC2/CLKOUT pin, I/O function on GP5/OSC1/CLKIN)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT enabled)
#pragma config PWRTE = ON // Power-Up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = OFF // GP3/MCLR pin function select (GP3/MCLR pin function is MCLR)
#pragma config BOREN = OFF // Brown-out Detect Enable bit (BOD enabled)
#pragma config CP = OFF // Code Protection bit (Program Memory code protection is disabled)
#pragma config CPD = OFF // Data Code Protection bit (Data memory code protection is disabled)
#define _XTAL_FREQ 4000000
#include <xc.h>
#include <stdlib.h>
#include <htc.h>

void init_ports(void) {
    ANSEL = 0x00; // Set ports as digital IO not analog input.
    ADCON0 = 0x00; // Shut off ADC.
    CMCON = 0x07; // Shut off comparator.
    VRCON = 0x00; // Shut off the voltage reference.
    TRISIO = 0xA; // All GPIO pins output except 1&3.
    GPIO = 0x00; // Make all pins LOW
    T0IE = 0; // Disable timer interrupt.
    GPIE = 0; // GPIO port change interrupt
    GIE = 0; // Global interrupt for recognition of interrupts.
}

void main(void) {
    init_ports();
    __delay_ms(1000); // 上电等待
    
    // 定义监测相关变量
    unsigned char blink_detected = 0;
    unsigned int timer_count = 0;
    const unsigned int CHECK_WINDOW_MS = 1000; // 1秒监测窗口

    while(1) {
        blink_detected = 0;
        timer_count = 0;
        
        // 开始1秒的监测周期
        while(timer_count < CHECK_WINDOW_MS) {
            // 检查是否出现设备LED熄灭的瞬间(输入低电平)
            if (!(GPIO & 0b000010)) {
                blink_detected = 1; // 标记检测到闪烁
            }
            
            // 保留你原来的同步LED逻辑
            GP4 = (GPIO & 0b000010) ? 1 : 0;
            
            // 延时1ms,累计计时
            __delay_ms(1);
            timer_count++;
        }
        
        // 监测周期结束,判断状态
        if (blink_detected) {
            // 这里写闪烁状态的处理逻辑,比如点亮另一个LED或者记录状态
            // 示例:GP5 = 1;
        } else {
            // 这里写常亮状态的处理逻辑
            // 示例:GP5 = 0;
        }
    }
}

方案说明

  • 用__delay_ms(1)循环1000次凑出1秒窗口,期间只要检测到一次输入低电平,就把blink_detected置1
  • 优点是完全不用改硬件配置,代码逻辑简单,对你当前的需求来说足够用;缺点是延时过程中单片机只能干这一件事,没法并行处理其他任务

方案2:用Timer0轮询计时(更精准,扩展性强)

如果不想让单片机被延时阻塞,可以用PIC内置的Timer0来计时,轮询它的溢出标志来累计时间,这样在计时的同时还能同步处理LED控制。

修改后的代码:

// PIC12F675 Configuration Bit Settings
// 'C' source line config statements
// CONFIG
#pragma config FOSC = INTRCIO // Oscillator Selection bits (INTOSC oscillator: CLKOUT function on GP4/OSC2/CLKOUT pin, I/O function on GP5/OSC1/CLKIN)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT enabled)
#pragma config PWRTE = ON // Power-Up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = OFF // GP3/MCLR pin function select (GP3/MCLR pin function is MCLR)
#pragma config BOREN = OFF // Brown-out Detect Enable bit (BOD enabled)
#pragma config CP = OFF // Code Protection bit (Program Memory code protection is disabled)
#pragma config CPD = OFF // Data Code Protection bit (Data memory code protection is disabled)
#define _XTAL_FREQ 4000000
#include <xc.h>
#include <stdlib.h>
#include <htc.h>

void init_ports(void) {
    ANSEL = 0x00; // Set ports as digital IO not analog input.
    ADCON0 = 0x00; // Shut off ADC.
    CMCON = 0x07; // Shut off comparator.
    VRCON = 0x00; // Shut off the voltage reference.
    TRISIO = 0xA; // All GPIO pins output except 1&3.
    GPIO = 0x00; // Make all pins LOW
    T0IE = 0; // Disable timer interrupt.
    GPIE = 0; // GPIO port change interrupt
    GIE = 0; // Global interrupt for recognition of interrupts.
    
    // 配置Timer0:分频1:256,使用内部时钟
    OPTION_REG = 0b00000111; // PS2-PS0=111→分频256; T0CS=0→内部时钟; PSA=0→分频给Timer0
    TMR0 = 0; // 初始化计数器
}

void main(void) {
    init_ports();
    __delay_ms(1000); // 上电等待
    
    // 定义监测相关变量
    unsigned char blink_detected = 0;
    unsigned char overflow_count = 0;
    // 4MHz时钟+256分频下,Timer0溢出一次约65.5ms,1秒需要约15次溢出(15*65.5≈982ms,接近1秒)
    const unsigned char OVERFLOWS_FOR_1S = 15;

    while(1) {
        blink_detected = 0;
        overflow_count = 0;
        TMR0 = 0;
        T0IF = 0; // 清除溢出标志
        
        // 开始1秒监测周期
        while(overflow_count < OVERFLOWS_FOR_1S) {
            // 检查是否出现设备LED熄灭的瞬间
            if (!(GPIO & 0b000010)) {
                blink_detected = 1;
            }
            
            // 同步控制自身LED
            GP4 = (GPIO & 0b000010) ? 1 : 0;
            
            // 检查Timer0是否溢出
            if (T0IF) {
                overflow_count++;
                T0IF = 0; // 清除标志
                TMR0 = 0; // 重置计数器
            }
        }
        
        // 状态判断处理
        if (blink_detected) {
            // 闪烁状态逻辑
            // 示例:GP5 = 1;
        } else {
            // 常亮状态逻辑
            // 示例:GP5 = 0;
        }
    }
}

方案说明

  • 利用Timer0的硬件计时能力,通过统计溢出次数来实现1秒窗口,不会阻塞单片机的其他操作
  • 计算溢出次数的逻辑:4MHz时钟下,每个指令周期是1us,Timer0是8位计数器,加上256分频,溢出一次的时间是256*256=65536us≈65.5ms,所以1秒需要约15次溢出

关键注意点

  1. 电路逻辑匹配:确保你的光敏电阻电路和代码逻辑一致——设备LED亮时GP1为高,熄灭时为低,如果实际电路相反,把判断条件里的!去掉即可
  2. 窗口时长调整:如果觉得1秒太长,改成500ms也可以(设备LED每秒闪2次,500ms内肯定会有一次熄灭)
  3. 延时准确性:__delay_ms的精度依赖于你定义的_XTAL_FREQ,你当前的4000000是正确的,对应4MHz时钟

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:38:31