On the Positron Compiler's forum recently (www.protoncompiler.com) , there has been discussions about using DDS (Direct Digital Synthesis) to create a decent sinewave signal from a PIC microcontroller. So I have created the program below in order to demonstrate how easy it is to create it when written with the Positron8 compiler.
The program uses the compiler's 24-bit, Long, type variables where it can, so that it makes the program both smaller and faster to operate. It also uses the CCP1 peripheral operating as a high frequency PWM (250 KHz), and acting as an 8-bit DAC so the sine waveforms are quite good.
The demo is written for a PIC18F26K22 device operating at 64MHz, using an external 16MHz crystal, but it can easily be transferred to any 18F or enhanced 14-bit core device. And with a few changes, also on to a standard 14-bit core device.
'
' /\\\\\\\\\
' /\\\///////\\\
' \/\\\ \/\\\ /\\\ /\\\
' \/\\\\\\\\\\\/ /\\\\\ /\\\\\\\\\\ /\\\\\\\\ /\\\\\\\\\\\ /\\\\\\\\\\\ /\\\\\\\\\
' \/\\\//////\\\ /\\\///\\\ \/\\\////// /\\\/////\\\ \////\\\//// \////\\\//// \////////\\\
' \/\\\ \//\\\ /\\\ \//\\\ \/\\\\\\\\\\ /\\\\\\\\\\\ \/\\\ \/\\\ /\\\\\\\\\\
' \/\\\ \//\\\ \//\\\ /\\\ \////////\\\ \//\\/////// \/\\\ /\\ \/\\\ /\\ /\\\/////\\\
' \/\\\ \//\\\ \///\\\\\/ /\\\\\\\\\\ \//\\\\\\\\\\ \//\\\\\ \//\\\\\ \//\\\\\\\\/\\
' \/// \/// \///// \////////// \////////// \///// \///// \////////\//
' Let's find out together what makes a PIC Tick!
'
' DDS sinewave generator with a PWM acting as an 8-bit DAC.
' This demo is created for standard 18F devices that contain a CCP peripheral.
'
' In tests, the program can output sine wave frequencies from a few 10s of Hz to over 32 KHz,
' but for higher frequencies, the low pass filter will need to be a lot better than the simple circuit shown below.
'
' Written for the Positron8 compiler by Les Johnson.
'
' A simple low pass filter circuit, as shown below, can be used to get a sine waveform out.
' The filter's components will need to be changed for different frequency sine waves.
'
' | 1k
' PORTC.2 |----/\/\/\-----o------------------> Output
' | |
' | __|__
' | _____ 1nf to 100nF
' | |
' | |
' | -----
' --- Gnd
' -
'
Device = 18F26K22 ' Tell the compiler what device it will compile for
Declare Xtal = 64 ' Tell the compiler what frequency the device is operating at (in MHz)
'-----------------------------------------------------------------------------------------
' A preprocessor meta-macro to write to the 8-bit PWM peripheral
' Input : pDutyCycle holds the 8-bit duty cycle for the PWM (0 to 255)
' Output : None
' Notes : None
'
$define PWM1_Write(pDutyCycle) '
CCP1CON = CCP1CON & 0b11001111 '
WREG = pDutyCycle << 4 '
WREG = WREG & 0b00110000 '
CCP1CON = CCP1CON | WREG '
CCPR1L = pDutyCycle >> 2
'-----------------------------------------------------------------------------------------
' The main program starts here
' Output a sine wave signal from the CCP1 pin (PORTC.2)
'
Main:
Setup() ' Setup the program
DDS_SineWave(5000, 1000) ' Output a 5 KHz sinewave for approx 1000 ms
'-----------------------------------------------------------------------------------------
' Generate a sinewave signal from the CCP1 peripheral pin acting as an 8-bit PWM DAC
' Input : pFreq holds the frequency to generate
' : pDuration holds the approximate time (in ms) for the waveform to be outputted
' Output : None
' Notes : DDS values calculated by:
' cDivisor = (Device MIPS * 1000000) / 128
' DDS value = (Frequency(in Hz) * 128) * (65536 / cDivisor)
'
Proc DDS_SineWave(pFreq As Word, pDuration As Dword)
Dim dAccum As Dword Access ' Accumulator for the DDS
Dim dTemp As Dword Access ' A temporary variable
Dim bDuty As Byte Access ' Holds the 8-bit duty cycle value for the PWM DAC
Symbol cDivisor = (((_xtal / 4) * 1000000) / 128) ' Create a divisor based upon the device's operating frequency
Symbol cMult = (65536.0 / cDivisor) ' Create a floating point multiplier constant
'
' 256 element, 8-bit sinewave data table
'
Dim SineTable As Flash8 = {$80, $83, $86, $89, $8C, $8F, $92, $95,
$98, $9C, $9F, $A2, $A5, $A8, $AB, $AE,
$B0, $B3, $B6, $B9, $BC, $BF, $C1, $C4,
$C7, $C9, $CC, $CE, $D1, $D3, $D5, $D8,
$DA, $DC, $DE, $E0, $E2, $E4, $E6, $E8,
$EA, $EC, $ED, $EF, $F0, $F2, $F3, $F5,
$F6, $F7, $F8, $F9, $FA, $FB, $FC, $FC,
$FD, $FE, $FE, $FF, $FF, $FF, $FF, $FF,
$FF, $FF, $FF, $FF, $FF, $FF, $FE, $FE,
$FD, $FC, $FC, $FB, $FA, $F9, $F8, $F7,
$F6, $F5, $F3, $F2, $F0, $EF, $ED, $EC,
$EA, $E8, $E6, $E4, $E2, $E0, $DE, $DC,
$DA, $D8, $D5, $D3, $D1, $CE, $CC, $C9,
$C7, $C4, $C1, $BF, $BC, $B9, $B6, $B3,
$B0, $AE, $AB, $A8, $A5, $A2, $9F, $9C,
$98, $95, $92, $8F, $8C, $89, $86, $83,
$7F, $7C, $79, $76, $73, $70, $6D, $6A,
$67, $63, $60, $5D, $5A, $57, $54, $51,
$4F, $4C, $49, $46, $43, $40, $3E, $3B,
$38, $36, $33, $31, $2E, $2C, $2A, $27,
$25, $23, $21, $1F, $1D, $1B, $19, $17,
$15, $13, $12, $10, $0F, $0D, $0C, $0A,
$09, $08, $07, $06, $05, $04, $03, $03,
$02, $01, $01, $00, $00, $00, $00, $00,
$00, $00, $00, $00, $00, $00, $01, $01,
$02, $03, $03, $04, $05, $06, $07, $08,
$09, $0A, $0C, $0D, $0F, $10, $12, $13,
$15, $17, $19, $1B, $1D, $1F, $21, $23,
$25, $27, $2A, $2C, $2E, $31, $33, $36,
$38, $3B, $3E, $40, $43, $46, $49, $4C,
$4F, $51, $54, $57, $5A, $5D, $60, $63,
$67, $6A, $6D, $70, $73, $76, $79, $7C}
dAccum = pFreq * 128 ' \
dTemp = dAccum * cMult ' / Calculate the value for the DDS, for the frequency required
pDuration = pDuration * 241 ' Calculate loop duration in approx ms(for use with device operating at 64MHz)
PinOutput PORTC.2 ' Make the PWM peripheral's pin an output
dAccum.Long = 0 ' Reset the accumulator
Do ' Create a loop
Repeat : Until PIR1bits_TMR2IF = 1 ' \ Synchronise to the start of the PWM cycle
PIR1bits_TMR2IF = 0 ' /
dAccum.Long = dAccum.Long + dTemp.Long ' Accumulation of the frequency
bDuty = CRead8 SineTable[dAccum.Byte2] ' Divide by 65536, and read the table's data element
PWM1_Write(bDuty) ' Write the duty cycle value to the PWM peripheral
Dec pDuration ' Decrement the duration counter
If pDuration = 0 Then ' Has the duration reached 0?
PinInput PORTC.2 ' Yes. So make the PWM peripheral's pin an input
ExitProc ' Exit the procedure
EndIf
Loop
EndProc
'-----------------------------------------------------------------------------------------
' Configure CCP1 as PWM with a resolution of 8-bits (0 - 255)
' Input : None
' Output : None
' Notes : Keeps the CCP1 pin as an input, so no signal is available yet
' : With a 64MHz oscillator, the PWM operates at 250 KHz
'
Proc PWM1_Init()
T2CON = 0b00000100 ' \
PR2 = 63 ' / Setup Timer2 to give a 250 KHz frequency for the 8-bit PWM
CCPR1L = 0 ' Clear the PWM duty SFR
CCP1CON = 0b00001100 ' Configure CCP1 as PWM
PinInput PORTC.2 ' Make the PWM peripheral's pin an input
EndProc
'-----------------------------------------------------------------------------------------
' Setup the program
' Input : None
' Output : None
' Notes : None
'
Proc Setup()
PWM1_Init() ' Setup CCP1 as an 8-bit PWM
EndProc
'-----------------------------------------------------------------------------------------
' Configure the fuses to operate at 64MHz using an external 16MHz crystal, on a PIC18F26K22 device
'
Config_Start
FOSC = HSHP ' HS oscillator (high power > 16 MHz)
PLLCFG = On ' Oscillator multiplied by 4
PRICLKEN = On ' Primary clock enabled
FCMEN = Off ' Fail-Safe Clock Monitor disabled
IESO = Off ' Internal/External Oscillator Switchover mode disabled
PWRTEN = On ' Power up timer enabled
BOREN = SBORDIS ' Brown-out Reset enabled in hardware only (SBOREN is disabled)
BORV = 190 ' Brown Out Reset Voltage set to 1.90 V nominal
WDTEN = Off ' Watch dog timer is always disabled. SWDTEN has no effect
WDTPS = 128 ' Watchdog Timer Postscale 1:128
CCP2MX = PORTC1 ' CCP2 input/output is multiplexed with RC1
PBADEN = Off ' PORTB<5:0> pins are configured as digital I/O on Reset
CCP3MX = PORTB5 ' P3A/CCP3 input/output is multiplexed with RB5
HFOFST = On ' HFINTOSC output and ready status are not delayed by the oscillator stable status
T3CMX = PORTC0 ' Timer3 Clock Input (T3CKI) is on RC0
P2BMX = PORTB5 ' ECCP2 B (P2B) is on RB5
MCLRE = EXTMCLR ' MCLR pin enabled, RE3 input pin disabled
STVREN = Off ' Stack full/underflow will not cause Reset
LVP = Off ' Single-Supply ICSP disabled
XINST = Off ' Instruction set extension and Indexed Addressing mode disabled
Debug = Off ' Debug Disabled
Cp0 = Off ' Block 0 (000800-001FFF) not code-protected
CP1 = Off ' Block 1 (002000-003FFF) not code-protected
CP2 = Off ' Block 2 (004000-005FFF) not code-protected
CP3 = Off ' Block 3 (006000-007FFF) not code-protected
CPB = Off ' Boot block (000000-0007FF) not code-protected
CPD = Off ' Data EEPROM not code-protected
WRT0 = Off ' Block 0 (000800-001FFF) not write-protected
WRT1 = Off ' Block 1 (002000-003FFF) not write-protected
WRT2 = Off ' Block 2 (004000-005FFF) not write-protected
WRT3 = Off ' Block 3 (006000-007FFF) not write-protected
WRTC = Off ' Configuration registers (300000-3000FF) not write-protected
WRTB = Off ' Boot Block (000000-0007FF) not write-protected
WRTD = Off ' Data EEPROM not write-protected
EBTR0 = Off ' Block 0 (000800-001FFF) not protected from table reads executed in other blocks
EBTR1 = Off ' Block 1 (002000-003FFF) not protected from table reads executed in other blocks
EBTR2 = Off ' Block 2 (004000-005FFF) not protected from table reads executed in other blocks
EBTR3 = Off ' Block 3 (006000-007FFF) not protected from table reads executed in other blocks
EBTRB = Off ' Boot Block (000000-0007FF) not protected from table reads executed in other blocks
Config_End
The above code listing and a Protues 8.14 simulation project can be downloaded from here: DDS_SineWave - 8-bit PWM.zip
A screenshot of the above program operating in the Proteus simulator is shown below. It is producing a 5 KHz sine wave through a very crude low pass filter: