PIC18F67K22中uint8_t类型sec变量赋值低数值时程序崩溃求助
PIC18F67K22时钟程序sec变量异常问题
- 毫秒定时器每毫秒触发标志,每1000毫秒重置ms变量功能正常;
- 尝试每10秒(调试用)重置sec变量时,程序直接停止运行;
- 将sec赋值为小于10的数值就会导致崩溃,赋值大于10则正常,执行
sec--操作也会触发崩溃。
问题定位在CLK_ISR中断函数中,以下是最小复现代码:
main.c
#include "fuses.h" #include <stdbool.h> #include <stdio.h> #include <xc.h> #include "clock.h" #include "interrupt.h" #include "LCDSerialTerminalMaster.h" uint8_t prevSec = 0; void main(void) { // disable comparators CM1CONbits.CON = 0; CM2CONbits.CON = 0; CM3CONbits.CON = 0; // disable analog input ADCON0bits.ADON = false; ANCON0 = 0; ANCON1 = 0; ANCON2 = 0; INT_Init(); CLK_Init(); LCD_Init(); while(1) { if(CLK_GetSec() != prevSec) { prevSec = CLK_GetSec(); LCD_Write(); } } }
interrupt.c
#include <stdbool.h> #include "interrupt.h" #include "clock.h" #include "pin.h" void INT_Init(void) { // Enable Interrupt Priority Vectors IPEN = 1; // RCI - high priority UART1_RC_IP = 1; UART2_RC_IP = 1; UART1_TX_IP = 0; UART2_TX_IP = 0; MASTER_SYNC_IP = 1; INT_Toggle(true); } void INT_Toggle(bool toggle) { INT_ToggleHigh(toggle); INT_ToggleLow(toggle); } void INT_ToggleHigh(bool toggle) { INTCONbits.GIEH = toggle; } void INT_ToggleLow(bool toggle) { INTCONbits.GIEL = toggle; } void __interrupt() INT_InterruptManagerHigh(void) { if(PIR4bits.CCP4IF) CLK_ISR(); }
clock.c
#include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <xc.h> static uint8_t hour = 0; static uint8_t min = 0; static uint8_t sec = 0; static volatile uint16_t ms = 0; void CLK_Init(void) { T3CONbits.TMR3CS = 0b00; // Source = Fosc/4 T3CONbits.SOSCEN = 0b1; // SOSC disabled T3CONbits.T3CKPS = 0b00; // Prescaler 1:1 CCP4CONbits.CCP4M = 0b1011; // Compare Mode: Special Event Trigger, reset TMR3 on CCP4 match CCPTMRS1bits.C4TSEL = 0b01; // TMR3/TMR4 // set compare value to 5000 CCPR4L = 136; CCPR4H = 19; // Disable TMR3 interrupt. PIE2bits.TMR3IE = 0; // clear CCP4 interrupt. PIR4bits.CCP4IF = 0; // enable CCP4 interrupt. PIE4bits.CCP4IE = 1; IPR4bits.CCP4IP = 1; // Start the Timer by writing to TMRxON bit. T3CONbits.TMR3ON = 1; } void CLK_GetTime(char *buffer) { sprintf(buffer, "%02hhu:%02hhu:%02hhu", hour, min, sec); } uint8_t CLK_GetSec(void) { return sec; } void CLK_ISR(void) { PIR4bits.CCP4IF = 0; ms++; if(ms == 1000) { ms = 0; sec++; if(sec == 10) { sec = 0; min++; if(min == 60) { min = 0; hour++; if(hour == 24) hour = 0; } } } }
问题根源分析
- 变量未加
volatile修饰:sec、min、hour是中断函数会修改的变量,但未声明为volatile,编译器会对这些变量做缓存优化,导致主程序读取到旧值,引发逻辑混乱。 - 无中断保护的变量读写:主程序读取
sec时,若中断触发修改该变量,可能出现读取不完整的情况,尤其在编译器优化后,这种异步访问会导致系统行为异常。 - 定时器配置误差:当前CCPR4配置值对应2.5ms触发一次中断(而非预期的1ms),导致
ms计数逻辑错误,间接引发时间更新逻辑混乱。 - 高频LCD操作阻塞:当
sec频繁变化时,LCD_Write()被高频调用,若该函数耗时较长,会阻塞中断响应,最终导致系统崩溃。
修复方案
1. 为中断变量添加volatile修饰
修改clock.c中的变量声明,确保编译器不会缓存这些异步修改的变量:
static volatile uint8_t hour = 0; static volatile uint8_t min = 0; static volatile uint8_t sec = 0; static volatile uint16_t ms = 0;
2. 读取变量时添加中断保护
在读取中断修改的变量前,临时关闭高优先级中断,避免读写冲突:
uint8_t CLK_GetSec(void) { uint8_t temp_sec; INTCONbits.GIEH = 0; // 关闭高优先级中断 temp_sec = sec; INTCONbits.GIEH = 1; // 恢复中断 return temp_sec; } void CLK_GetTime(char *buffer) { uint8_t temp_hour, temp_min, temp_sec; INTCONbits.GIEH = 0; temp_hour = hour; temp_min = min; temp_sec = sec; INTCONbits.GIEH = 1; sprintf(buffer, "%02hhu:%02hhu:%02hhu", temp_hour, temp_min, temp_sec); }
3. 修正定时器CCPR配置
假设系统时钟为8MHz,要实现1ms触发一次中断,CCPR4应设置为2000(对应0x07D0):
// 替换原CCPR赋值代码 CCPR4L = 0xD0; CCPR4H = 0x07;
4. 优化LCD更新逻辑
如果LCD_Write()耗时较长,可将显示数据存入缓冲区,在主循环中分批处理,避免长时间阻塞中断响应。
内容的提问来源于stack exchange,提问作者Wesley Kienhuis
相关产品推荐
相关产品推荐

