基于PIC16F15243的10Hz-20kHz方波频率测量精度问题求助
问题描述
使用PIC16F15243测量10Hz-20kHz的方波信号频率,以Hz为单位显示测量值,但高频段测量精度不足:输入20kHz时,测量结果显示为25kHz。已尝试调整不同波特率与时钟频率,当前方案为最接近准确值的版本,代码如下:
// CONFIG1 #pragma config FEXTOSC = OFF // External Oscillator Mode Selection bits (Oscillator not enabled) #pragma config RSTOSC = HFINTOSC_1MHZ// Power-up Default Value for COSC bits (HFINTOSC (1 MHz)) #pragma config CLKOUTEN = OFF // Clock Out Enable bit (CLKOUT function is disabled; I/O function on RA4) #pragma config VDDAR = HI // VDD Range Analog Calibration Selection bit (Internal analog systems are calibrated for operation between VDD = 2.3V - 5.5V) // CONFIG2 #pragma config MCLRE = EXTMCLR // Master Clear Enable bit (If LVP = 0, MCLR pin is MCLR; If LVP = 1, RA3 pin function is MCLR) #pragma config PWRTS = PWRT_OFF // Power-up Timer Selection bits (PWRT is disabled) #pragma config WDTE = OFF // WDT Operating Mode bits (WDT disabled; SEN is ignored) #pragma config BOREN = OFF // Brown-out Reset Enable bits (Brown-out Reset disabled) #pragma config BORV = LO // Brown-out Reset Voltage Selection bit (Brown-out Reset Voltage (VBOR) set to 1.9V) #pragma config PPS1WAY = OFF // PPSLOCKED One-Way Set Enable bit (The PPSLOCKED bit can be set and cleared as needed (unlocking sequence is required)) #pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable bit (Stack Overflow or Underflow will cause a reset) // CONFIG3 // CONFIG4 #pragma config BBSIZE = BB512 // Boot Block Size Selection bits (512 words boot block size) #pragma config BBEN = OFF // Boot Block Enable bit (Boot Block is disabled) #pragma config SAFEN = OFF // SAF Enable bit (SAF is disabled) #pragma config WRTAPP = OFF // Application Block Write Protection bit (Application Block is not write-protected) #pragma config WRTB = OFF // Boot Block Write Protection bit (Boot Block is not write-protected) #pragma config WRTC = OFF // Configuration Registers Write Protection bit (Configuration Registers are not write-protected) #pragma config WRTSAF = OFF // Storage Area Flash (SAF) Write Protection bit (SAF is not write-protected) #pragma config LVP = OFF // Low Voltage Programming Enable bit (High Voltage on MCLR/Vpp must be used for programming) // CONFIG5 #pragma config CP = OFF // User Program Flash Memory Code Protection bit (User Program Flash Memory code protection is disabled) // #pragma config statements should precede project file includes. // Use project enums instead of #define for ON and OFF. #include <xc.h> #include <stdio.h> #define _XTAL_FREQ 32000000 unsigned long value; void osc_init() { OSCENbits.HFOEN = 1; // USE INTERNAL CLOCK OSCFRQbits.FRQ = 0b101; // 32 MHZ CLOCK } void pin_master() { TRISAbits.TRISA2 = 1; // RA5 IS INPUT ANSELAbits.ANSA2 = 0; // RA5 IS digital INPUT TRISBbits.TRISB7 = 0; // RB7 IS OUTPUT (TX UART) TRISBbits.TRISB6 = 0; RB7PPS = 0x05; // RB7 IS MAPPED AS EUSART1 TX1 } void uart_init() { // CONFIGURE BAUD RATE, ENABLE EUART UNIT, ENABLE TRANSMISSION // CONFIGURE THE DIVIDER AS 4: SYNC=0; BRG16=1; BRGH=1; // FINETUNE THE MULTIPLIER SP1BRG = 9; // CONFIGURE THE EUART CONTROLS RC1STAbits.CREN = 0; // DISABLE RX TX1STAbits.TXEN = 1; // ENABLE TX RC1STAbits.SPEN = 1; // ENABLE EUART UNIT } void putch(char data) { while( ! TX1IF) continue; TX1REG = data; } __interrupt() INTERRUPT_Interrupt_Manager (void){ value=(TMR0H<<8)|(TMR0L); TMR0H=0; TMR0L=0; INTF=0; } teim_init() { TMR0IF=0; TMR0IE=1; T0CON1 = 0b01000110; //0b01000011 T0CON0 = 0b10010001; // } inter_init(){ INTE=1; INTF=0; GIE=1; INTEDG=1; } void main(void) { osc_init(); pin_master(); uart_init(); teim_init(); inter_init(); TMR0H=0; TMR0L=0; while(1) { value=8*value; value=1000000/value; printf("Frequency = %lu\n",value); __delay_ms(1000); } return; }
精度优化建议
修正核心计算逻辑错误
当前定时器T0的时钟源为FOSC/4(32MHz/4=8MHz),分频系数1:64,因此定时器每个计数周期为1/(8MHz/64)=8μs。主循环中value=8*value; value=1000000/value;的计算完全颠倒,正确的频率计算公式应为:// 替换主循环内的计算代码 unsigned long period_us = value * 8; // 计算单周期的微秒数 unsigned long freq = 1000000 / period_us; // 频率=1/周期这是导致20kHz输入显示25kHz的直接原因。
改用多周期/固定窗口测量法提升高频精度
单周期测量在20kHz时(周期50μs),定时器仅计数6次,微小的计数误差会被放大为巨大的频率误差。建议切换为固定时间窗口内统计脉冲数的频率测量法:- 用TMR0定时100ms(或50ms),开启全局中断;
- 在外部中断(输入信号上升沿)中统计脉冲次数;
- 定时结束后,频率=脉冲数/定时时间(如100ms窗口则频率=脉冲数*10)。
这种方法在高频段能获得更多的计数样本,大幅降低相对误差。
添加volatile修饰共享变量
全局变量value被中断服务函数和主循环同时访问,必须添加volatile关键字防止编译器优化导致数据丢失:volatile unsigned long value;校准内部时钟或改用外部晶振
PIC16F15243的内部HFINTOSC默认精度为±1%~±2%,高频下会直接影响测量结果。可以:- 读取芯片内部的时钟校准值(参考 datasheet 中 CALHFxx 寄存器);
- 改用外部高精度晶体振荡器(如8MHz或32MHz),彻底解决时钟精度问题。
修复代码笔误与中断安全问题
- 函数名
teim_init()拼写错误,应改为timer_init(); - 中断中读取16位定时器寄存器时,可能出现主循环读取半字的情况,建议用临时变量缓存后再赋值:
__interrupt() INTERRUPT_Interrupt_Manager (void){ unsigned short temp_tmr = (TMR0H << 8) | TMR0L; value = temp_tmr; TMR0H = 0; TMR0L = 0; INTF = 0; }
- 函数名
内容的提问来源于stack exchange,提问作者olafur Eliasson
相关产品推荐
相关产品推荐

