lindsay.wilson.88
Joined: 11 Sep 2024 Posts: 40
|
Timer 2: determine best prescaler, postscaler, period |
Posted: Wed Sep 25, 2024 4:35 pm |
|
|
Don't know if this is useful, but I needed to figure it out so maybe someone else does as well ;-)
Suppose you want to have timer 2 interrupts running at, or as close as possible to, a frequency specified by the user (e.g. over the UART). There are three parameters which need to be chosen:
prescaler (1,4,16)
postscaler (1-16)
period (0-255)
I wrote a program which goes through each combination of prescaler and postscaler, works out the period, and then figures out which combination results in the closest frequency match. I used floats for most of the calculations because I'm lazy, so it's not particularly fast (30-40ms on a 39MHz 18F4520), but for my application it's fine as the user only configures the timer frequency occasionally.
The resulting frequency is pretty close - the error is no worse than maybe 0.2% over the entire frequency range (in my case, 150Hz to 65kHz). For greater precision, timer 0 is better since it's 16-bit, but there were various reasons I prefer timer 2, mainly the interrupt-on-match behaviour rather than interrupt-on-overflow. Long story ;-)
Header:
Code: | #include <18F4520.h>
#device ADC=10
#FUSES NOWDT //No Watch Dog Timer
#use delay(clock=39.3216MHz,crystal=9.8304MHz)
#use rs232(baud=9600,parity=N,UART1,bits=8)
#use FAST_IO(ALL) |
Main code:
Code: | #include <main.h>
#include <stdlib.h>
#include <math.h>
// Text received over UART
char uart_text[10];
// Main oscillator frequency
int32 fosc;
// Desired frequency we want timer to run at
int16 tmr2_desired_freq;
// Test values for prescaler, postscaler and period. The program runs
// through lots of combinations of each to find the best.
int8 tmr2_test_prescaler;
int8 tmr2_test_postscaler;
float tmr2_test_period;
// Actual frequency resulting from using the above test values
float tmr2_test_freq;
// Frequency error - difference between desired and actual
float tmr2_freq_error;
// Minimum frequency error so far - used to check whether the current
// test values result in a better frequency match or not
float tmr2_min_freq_error;
// The best values of prescaler, postscaler and period. These result
// in the smallest possible error
int8 tmr2_best_prescaler;
int8 tmr2_best_postscaler;
int8 tmr2_best_period;
// Timer interrupt routine - give an output pulse just to see what's happening
#int_timer2
void timerinterrupt()
{
output_high(PIN_C0);
output_low(PIN_C0);
}
void main()
{
// General setup
setup_adc_ports(NO_ANALOGS,VSS_VDD);
set_tris_c(0b10000000); // Remember to leave the UART TX as output!
setup_timer_2(T2_DIV_BY_1,100,16); // Set timer to something to start with
enable_interrupts(int_TIMER2);
enable_interrupts(GLOBAL);
fosc=39321600;
while(TRUE)
{
// Get desired frequency from the user over UART
printf("Enter desired frequency\r\n");
gets(uart_text);
tmr2_desired_freq=atol(uart_text);
printf("Desired frequency: %lu\r\n",tmr2_desired_freq);
// Set the minimum error to something very large and initialise
// the other test parameters to those that give the lowest
// frequency(i.e. "safest")in case best values aren't
// found for some reason
tmr2_min_freq_error=100000;
tmr2_best_prescaler=16;
tmr2_best_postscaler=16;
tmr2_best_period=255;
// Now go through every possible combination of prescaler and postscaler
// values. Work out the period which gives the desired frequency. Find
// the closest integer value to this. Then work out the frequency resulting
// from this integer value. Work out the error between this frequency and the
// desired frequency. if this error is less than the previous minimum error,
// update the best values.
for(tmr2_test_prescaler=1; tmr2_test_prescaler<17; tmr2_test_prescaler*=4) // 1,4,16
{
for(tmr2_test_postscaler=1; tmr2_test_postscaler<17; tmr2_test_postscaler++) // 1-16
{
// Work out the test period. Do as float to handle negative and fractional values.
tmr2_test_period=(fosc/((float)4*tmr2_test_prescaler*tmr2_test_postscaler*tmr2_desired_freq))-1;
if(tmr2_test_period<0){tmr2_test_period=0;} // If less than zero, set to zero
if(tmr2_test_period>255){tmr2_test_period=255;} // If greater than 255, set to 255
tmr2_test_period=floor(tmr2_test_period+0.5); // Round to nearest integer
// Work out the frequency obtained by using this period. Again, do as float to handle fractional values.
tmr2_test_freq=fosc/((float)4*tmr2_test_prescaler*(tmr2_test_period+1)*tmr2_test_postscaler);
// Work out the absolute error between the test and desired frequencies
tmr2_freq_error=abs(tmr2_test_freq-(float)tmr2_desired_freq);
// If the current error is less than the minimum error, we've found a possible candidate
// for the best choice of parameters. Update the minimum error and the best prescaler,
// postscaler and period values
if(tmr2_freq_error<tmr2_min_freq_error)
{
tmr2_min_freq_error=tmr2_freq_error;
tmr2_best_prescaler=tmr2_test_prescaler;
tmr2_best_postscaler=tmr2_test_postscaler;
tmr2_best_period=tmr2_test_period;
}
}
}
// By this point, we should have best choices for the prescaler, postscaler and period
// Update the actual timer with these values. Need to use switch because you can't just
// pass the prescaler value directly to the setup_timer_2 function.
switch(tmr2_best_prescaler)
{
case 1: setup_timer_2(T2_DIV_BY_1,tmr2_best_period,tmr2_best_postscaler); break;
case 4: setup_timer_2(T2_DIV_BY_4,tmr2_best_period,tmr2_best_postscaler); break;
case 16: setup_timer_2(T2_DIV_BY_16,tmr2_best_period,tmr2_best_postscaler); break;
}
// Print results
printf("Best results:\r\n");
printf("Prescaler %u\r\n",tmr2_best_prescaler);
printf("Postscaler %u\r\n",tmr2_best_postscaler);
printf("Period %u\r\n",tmr2_best_period);
}
} |
|
|