I am trying to measure the frequency of a squared signal (10Hz-20kHz), using pic16F15243 and displaying the value in Hz.
The problem is that I cant get accurate measurements the higher the frequency goes (20kHz I get 25kHz).
The code is below:
// 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;
}
I have tried different baud rates with different clock speeds and this was the closest I got.
Any hints on how I can get readings with acceptable accuracy would be appreciated.
CodePudding user response:
You are capturing the number of clock counts between input pulses, and that is necessarily an integer value. At high frequencies the resolution will be very low. The difference between 5 and 6 counts being around 5KHz in this case.
If you wanted say 1% precision at the highest frequency you wanted to measure you would need a clock frequency of fmax x 100, and 1% precision is still pretty poor - you would need very fast clocks to get good precision and given that it is only a 16 bit timer it would limit your lower frequency limit.
A better method for high frequencies is to count the number of pulses over a fixed period of time - then the resolution is determined by the measurement period rather than the timer clock rate. The disadvantage then of course is poor resolution at low frequencies.
A solution that would work for high and low frequencies is to count pulses until the timer count is greater than say 32767 (half full scale - somewhat arbitrarily, but clearly avoiding overflow, and providing a fair degree of precision). Then:
frequency = 125000 x pulse_count
/ TMR0_COUNT
So for example for 20KHz, TMR0 whould reach 32768 after 5243 pulses; 125000 x 32768 / 5243 = 20000 Hz.
At the lower frequency of 10Hz, the counter will reach 50000 after 4 pulses (so well within the 16-bit range of TMR0), and 125000 * 4 / 50000 = 10Hz.
So here instead of resetting the counter to zero on the input interrupt, you would increment the pulse count then if the counter value is greater than 32767 (or whatever you choose as sufficient precision), you would capture both its value and the pulse count and then reset both to zero.
Something like (illustrative - I have not tested or even compiled this):
static volatile uint32_t timer_counts = 0 ;
static volatile uint32_t pulse_counts = 0 ;
__interrupt() INTERRUPT_Interrupt_Manager (void)
{
static pulse_count = 0 ;
pulse_count ;
int tmr_count = (TMR0H<<8)|(TMR0L) ;
if( tmr_count > 0x7FFFu )
{
timer_counts = tmr_count ;
pulse_counts = pulse_count ;
pulse_count = 0 ;
TMR0H=0;
TMR0L=0;
}
INTF=0;
}
Then in main()
:
for(;;)
{
// Capture timer and pulse counts atomically
INTE = 0 ;
uint32_t tcount = timer_counts ;
uint32_t pcount = pulse_counts ;
INTE = 1 ;
uint32_t f = 125000u * tcount / pcount ;
printf( "Frequency = %lu\n", f ) ;
__delay_ms(1000);
}
There are issues with your code such as the non-atomic access of value
and the fact that it was not declared volatile
. The above code addresses those issues.
CodePudding user response:
I think your problem is the integer math:
value=8*value;
value=1000000/value;
This is effectively doing
value = 125000/value;
If value is 5 you get 25000; if value is 6 you get 20833. The error in this computation is very large for small values of value
.
To reduce this error you need to accumulate pulses for a longer period of time.