23 ints of 24; generate the virtual pulse periods using TMR1
The schematic above shows how to connect the 12v AC mains sensing input
to GP2 and the 2 PWM sinewave push-pull outputs are GP0 and GP1.
The code below is fully tested in hardware and I fine tuned the TMR1
"virtual pulse" period to remove interrupt latency, so it produces a
VERY nicely locked 60Hz sinewave. Below is the result; the 50Hz is shown
as the top waveform on PIC pin GP2 (filter values as seen above), and
below it is one of the 60Hz sinewave outputs (filtered with 39k and 0.1uF
for ease of display).
This circuit generates a much nicer looking sinewave than my mains! :)
I tested my 50Hz mains frequency which was running about 200 PPM fast, and then
tested the 60Hz sine output was also running about 200 PPM fast, so that's perfect.
(Not that it has much choice seeing that it's mains-locked.)
//=============================================================================
void interrupt()
{
//-----------------------------------------------------
// This is a TMR1 overflow interrupt for 23 of 24 cases.
// on 1 of 24 cases it acts as a GP2 interrupt on \ edge.
// This is used to generate exactly 24 virtual pulses,
// synchronised to the 50Hz mains input (GP2 \ edge).
//-----------------------------------------------------
if(!vpulse) // if it is last vpulse0
{
// just leave TMR1 free running, this allows easy first sync!
vpulse = 23;
}
else // else is vpulse1-23
{
TMR1L = (256 - (PERLO - 3 - 16)); // load TMR1 for 1 vpulse period
TMR1H = (256 - (PERHI + 1));
vpulse--; // sequence the next virtual pulse
INTCON.INTF = 0; // clear GP2 \ edge int flag
}
step++; // sequence the next 60Hz PWM step
PIR1.TMR1IF = 0;
}
//=============================================================================
The full C Source code is here.
Making multiple sinewaves of highly accurate frequency
New 21st Feb 2011.
This is a based on a form of DDS (Direct Digital Synthesis) which is
a modern name used as a "catch all" phrase to describe pretty much any
form of making frequencies digitally (ie using a microcontroller).
The actual mechanism is a "binary divided accumulator" which differs
from the "Bresenham" systems above by being faster, but cruder.
With a Bresenham system the frequency is generated by a ratio of
the value added to the accumulator compared to the Bresenham overflow
value of the accumulator. As both values are adjustable the Bresenham
system is superior as it can replicate any ratio of the 2 values with
absolute average frequency accuracy.
A binary divided accumulator DDS (generally just called "DDS") still
works as a ratio division of the value added to the accumulator compared
to the accumulator overflow, but the accumulator overflow is fixed
as a binary value. This adds speed as the Bresenham compare is no longer
needed, but is inferior as a frequency generator as you can only really
set one of the 2 values. For this reason a DDS system needs a higher
resolution accumulator, and the value added to the accumulator must
be chosen to give as close as possible to the desired output
frequency but will generally never give the exact output frequency,
just get very close to it.
The DDS system below generates two combined (added) sinewaves as a
single waveform, to generate DTMF with 2 combined sinewaves of very high accuracy
frequencies. Commercial DTMF generator ICs will have a freq error in the region
0.2% to 0.7%. The code below that uses a 24bit accumulator will produce
DTMF sinewaves better than 0.00001% frequency accuracy for each sine.
The algorithm basics.
1. Wait for sync with the PWM module (or interrupt)
2. Add the constant period to the accumulator
3. Use a high byte from the accumulator to reference position in a binary waveform table, load it in PWM
4. Repeat!
Note! The waveform table must be size in a round binary number; 32, 64, 128 or 256 etc.
Implementation.
The accumulator cycle is based on the PIC PWM module period, for
speed and simplicity. Then once per PWM period the addition calc is done
and a new value loaded from the sine table into the PWM duty so that
the output of the PWM module generates a sinewave of exact and
selectable frequency.
The code below makes a reasonably accurate single sinewave (typically less than 0.1% freq error)
// 16bit DDS algorithm to make one sinewave of accurate freq using
// 16 MHz xtal (4 MIPS on PIC 16F)
#define BDA_697Hz 365 // constant to make 697 Hz
unsigned int wave absolute 0x15; // 16bit accumulator for the sinewave
unsigned char wave_1 absolute 0x16; // overload for fast access to byte 1
const unsigned char sine64[64] = {
50,54,59,64,68,73,77,81,85,88,91,93,95,97,98,99,99,99,98,97,95,93,91,88,85,81,77,73,68,64,59,54,
50,45,40,35,31,26,22,18,14,11,8,6,4,2,1,0,0,0,1,2,4,6,8,11,14,18,22,26,31,35,40,45};
// loop and generate dual sinewave DTMF tone
PR2 = (128-1); // PWM at period = 128
while(1)
{
while(!PIR1.TMR2IF); // sync to start of PWM cycle
PIR1.TMR2IF = 0;
// calc the sinewave, and load into PWM module CCPR2L
wave += BDA_697Hz; // zero error Accumulation
CCPR2L = sine64[wave_1 & 0x3F]; // Binary Divide output (/256) and keep 6 bits
}
The code below makes an extremely accurate freq sinewave;
// 24bit DDS algorithm to make one sinewave of exact freq
// 8 MHz xtal (8 MIPS on PIC 18F PLL)
#define BDA_697Hz 46775 // constant to make exactly 697 Hz
unsigned long wave absolute 0x15; // 32bit accumulator for the sinewave
unsigned char wave_2 absolute 0x17; // overload for fast access to byte 2
const unsigned char sine64[64] = {
50,54,59,64,68,73,77,81,85,88,91,93,95,97,98,99,99,99,98,97,95,93,91,88,85,81,77,73,68,64,59,54,
50,45,40,35,31,26,22,18,14,11,8,6,4,2,1,0,0,0,1,2,4,6,8,11,14,18,22,26,31,35,40,45};
// loop and generate sinewave with PWM module
PR2 = (128-1); // PWM at period = 128
while(1)
{
while(!PIR1.TMR2IF); // sync to start of PWM cycle
PIR1.TMR2IF = 0;
// calc the sinewave, and load into PWM module CCPR2L
wave += BDA_697Hz; // zero error Accumulation
CCPR2L = sine64[wave_2 & 0x3F]; // Binary Divide output (/65536) and keep 6 bits
}
The code below makes dual simultaneous accurate sinewaves (for DTMF);
// 24bit DDS algorithm for dual simultaneous sinewaves on one PWM module
// this is an example for DTMF generation
// 8 MHz xtal (8 MIPS on PIC 18F PLL)
#define Frow0 46775 // 697 Hz DTMF rows
#define Frow1 51674 // 770 Hz
#define Frow2 57177 // 852 Hz
#define Frow3 63149 // 941 Hz
#define Fcol0 81135 // 1209 Hz DTMF columns
#define Fcol1 89657 // 1336 Hz
#define Fcol2 99120 // 1477 Hz
#define Fcol3 109589 // 1633 Hz
unsigned long waveA absolute 0x15; // 32bit accumulator for the sinewaves
unsigned char waveA_2 absolute 0x17; // overload for fast access to byte 2
unsigned long waveB absolute 0x19;
unsigned char waveB_2 absolute 0x1B;
unsigned char pwm;
const unsigned char sine64[64] = {
50,54,59,64,68,73,77,81,85,88,91,93,95,97,98,99,99,99,98,97,95,93,91,88,85,81,77,73,68,64,59,54,
50,45,40,35,31,26,22,18,14,11,8,6,4,2,1,0,0,0,1,2,4,6,8,11,14,18,22,26,31,35,40,45};
// loop and generate dual sinewave DTMF tone
PR2 = (128-1); // PWM at period = 128
while(1)
{
while(!PIR1.TMR2IF); // sync to start of PWM cycle
PIR1.TMR2IF = 0;
// calc the A sinewave,
waveA += Frow0; // zero error Accumulation
pwm = sine64[waveA_2 & 0x3F]; // Binary Divide output (/65536) and keep 6 bits
// calc the B sinewave, and ADD the 2 waves together
waveB += Fcol0;
pwm += sine64[waveB_2 & 0x3F];
pwm = (pwm >> 1); // scale 0-200 back to 0-100 for PWM
CCPR2L = pwm; // load added sinewaves into PWM module
}
Below is improved DTMF code that includes "twist" to make the higher freq sinewave 28% larger;
// 24bit DDS algorithm for dual simultaneous sinewaves on one PWM module
// this is an example for DTMF generation
// 8 MHz xtal (8 MIPS on PIC 18F PLL)
#define Frow0 46775 // 697 Hz DTMF rows
#define Frow1 51674 // 770 Hz
#define Frow2 57177 // 852 Hz
#define Frow3 63149 // 941 Hz
#define Fcol0 81135 // 1209 Hz DTMF columns
#define Fcol1 89657 // 1336 Hz
#define Fcol2 99120 // 1477 Hz
#define Fcol3 109589 // 1633 Hz
unsigned long waveA absolute 0x15; // 32bit accumulator for the sinewaves
unsigned char waveA_2 absolute 0x17; // overload for fast access to byte 2
unsigned long waveB absolute 0x19;
unsigned char waveB_2 absolute 0x1B;
unsigned char pwm;
// This uses dual sinewaves of 28% different amplitudes, to match the spec for "twist" in
// telephone DTMF to ensure the higher freq has a higher amplitude, to cope with line losses.
const unsigned char sine64low[64] = {
39,42,46,50,53,57,60,63,66,68,71,72,74,75,76,77,77,77,76,75,74,72,71,68,66,63,60,57,53,50,46,42
39,35,31,27,24,20,17,14,11,9,6,5,3,2,1,0,0,0,1,2,3,5,6,9,11,14,17,20,24,27,31,35};
const unsigned char sine64high[64] = {
50,54,59,64,68,73,77,81,85,88,91,93,95,97,98,99,99,99,98,97,95,93,91,88,85,81,77,73,68,64,59,54,
50,45,40,35,31,26,22,18,14,11,8,6,4,2,1,0,0,0,1,2,4,6,8,11,14,18,22,26,31,35,40,45};
// loop and generate dual sinewave DTMF tone
PR2 = (128-1); // PWM at period = 128
while(1)
{
while(!PIR1.TMR2IF); // sync to start of PWM cycle
PIR1.TMR2IF = 0;
// calc the A sinewave,
waveA += Frow0; // zero error Accumulation
pwm = sine64low[waveA_2 & 0x3F]; // Binary Divide output (/65536) and keep 6 bits
// calc the B sinewave, and ADD the 2 waves together
waveB += Fcol0;
pwm += sine64high[waveB_2 & 0x3F];
pwm = (pwm >> 1); // scale 0-200 back to 0-100 for PWM
CCPR2L = pwm; // load added sinewaves into PWM module
}
Calculating the constant to make a desired frequency;
Each freq you want to generate needs a costant. This is a simple ratio that
is determined by the PWM cycle frequency, the desired output frequency and
the number of samples in the waveform table. It is also dependant on the
size of the accumulator, ie 16bit, 24bit etc.
A 16 bit system uses the high byte of a 16bit accumulator. So it is a factor of 256.
Assuming a waveform table of 64 entries, the formula to make an output frequency
is this;
BDA_period = Fout * 64 * 256 / Fpwm
So for our PIC 16F example with 16MHz xtal (4 MIPS) and PWM period of 128;
Fpwm = 4000000 / 128 = 31250
To generate the 697 Hz sinewave as seen in example above;
BDA_period = 697 * 64 * 256 / 31250
BDA_period = 365
And for our PIC 18F example with 8MHz xtal PLL (8 MIPS) and PWM period of 128;
Fpwm = 8000000 / 128 = 62500
It uses a 24bit system, where the byte2 is used for the binary table, so is 65536;
To generate the 697 Hz sinewave, with the 24bit system, as seen in example above;
BDA_period = 697 * 64 * 65536 / 62500
BDA_period = 46775
Freqency accuracy;
Using a constant of about 46000 and max error is 1/2 a count, means the max error
is 1 part in 92000 or about 11 parts per million. This is better than the
frequency accuracy of the PIC xtal, so this 24bit produced sinewave will have an
output frequency accuracy around as good as the xtal itself. So this can be
considered a "perfect" frequency xtal-locked sinewave. Also, with the DTMF
example above this is the worst freq of the 8, so the other sine
frequencies range up to twice this accuracy.
And the nature of the system means that BOTH of the simultaneously
generated sinewaves will each have this individual accuracy.
Note! I have not provided a 32bit example, but you can see how easy
it would be from the above examples. A 32bit system will have a sinewave
frequency accuracy typically better than 50 parts per billion, a level
of accuracy so much higher than the xtal accuracy it is not necessary.
However it may be useful in some frequency conversion situations.
Speed issues and variable overloading;
The 3 code examples above use variable overloading to provide instant access
to the higher byte in the multi-byte accumulator. Luckily the MikroC for PIC
compiler allows this feature.
If your C compiler does not allow 2 variables to be assigned to the same
byte in PIC ram you can use a /256 or /65536 operation instead.
So for the 16bit system you would replace this;
CCPR2L = sine64[wave_1 & 0x3F]; // Binary Divide output (/256) and keep 6 bits
With this;
CCPR2L = sine64[(wave/256) & 0x3F]; // Binary Divide output (/256) and keep 6 bits
And for the 24bit system you would replace this;
CCPR2L = sine64[wave_2 & 0x3F]; // Binary Divide output (/65536) and keep 6 bits
With this;
CCPR2L = sine64[(wave/65536) & 0x3F]; // Binary Divide output (/65536) and keep 6 bits
Depending how well your C compiler optimises the divide it may be
as fast as the variable overloading, but most compilers are not that good as they
will use a Div32/32 function which is slower and will use a lot of ROM.
A flawless looking 697 Hz sinewave, 2.6v p/p, made from the PIC PWM module and
filtered simply through a 2k7 resistor and 0.1uF capacitor. I tested with
my good frequency meter and it said 697.00 Hz.
Accurate sine DTMF project with C source code;
I have done a PIC 18F project that generates all the DTMF codes with sinewaves,
using a SmartGLCD module, you can see it on
this tutorials page.
Continued...
If you liked this page please see my continued page;
High accuracy PIC timing systems.
History
I put my original "Zero-error 1 second Timer" up on my web page in
June 2001 and it has been used by a huge amount of people making
PIC clocks and for other timing uses to make one timing frequency
from another. Since then it practically become the "standard" way of
making a 1 second clock period in a PIC interrupt. :)
This web page was updated in June 2009 to add all the C-code
examples and the advanced techniques.
Tools
If using large numbers with assembler, you need to convert large decimal
numbers (like 1000000) to a 24-bit hex value for the PIC to use,
so I created a simple decimal-hex-binary converter;
Click here to download HexCon 1.0
- end -
[Back to Home Page]