如何在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次溢出
关键注意点
- 电路逻辑匹配:确保你的光敏电阻电路和代码逻辑一致——设备LED亮时GP1为高,熄灭时为低,如果实际电路相反,把判断条件里的
!去掉即可 - 窗口时长调整:如果觉得1秒太长,改成500ms也可以(设备LED每秒闪2次,500ms内肯定会有一次熄灭)
- 延时准确性:
__delay_ms的精度依赖于你定义的_XTAL_FREQ,你当前的4000000是正确的,对应4MHz时钟
内容的提问来源于stack exchange,提问作者gepettichomagor5475
相关产品推荐
相关产品推荐

