PDA

View Full Version : Low freq PWM problem



ultiblade
- 29th January 2010, 10:35
Hi All,

I'm kind of stuck at the moment.

I need a pwm signal with a low frequency. Darrel has made the awesome slow pwm module but i can't use because i need timer1 to do a continues pulse count. So i'm trying to code my own, but i can't seem to figure it out.

I'm using timer0 to to make the frequency, with Darrels DT_INTS-14. This works perfect. Preloading timer0 with 190 give about 118Hz where i need 120Hz so thats ok. On every interrupt the PORTC.0 pin is made high.

I'm using timer2 to make the dutycycle. I thought if i have a frequency of 120Hz the period is about 8333uS. If i need 256 dutycycle steps i need a interrupt at 8333uS/256 steps = 32,6uS. Timer2 is setup (presc. 1:1, postsc. 1:1, pr2=64) to generate an interrupt every 32,5uS.

If i need a 50% dutycycle, simply count 128x timer2 interrupts and that make the output low. Could be quite simple I thought, but i was wrong. On my scope i see a nice 117Hz stable signal with a dutycycle of 1 or so. If i alter the dutycycle var i sometimes see a dutycycle that ramps down at every cycle ?

Here's my code :


'==== Set fuses =========================================
'Fuse are set in .inc file

'==== Set includes ======================================
INCLUDE "DT_INTS-14.bas" 'DTints Interrupt System
include "ReEnterPBP.bas" 'Enable PBP int reenter

'==== Dec's for OSC =====================================
DEFINE OSC 8 'Int OSC @ 8 MHz, for PBP
OSCCON = %01110001 'Int OSC @ 8 MHz

'==== Set defines =======================================
OPTION_REG = %00000111 'Set TMR0 precsaler to 1:256, source Fosc/4
WPU = %00000000 'Weak pull-ups disabled

INTCON = %11100000 'Global + Perhip. INT enabled, TMR0 int enabled
PIE1 = %00000010 'TMR2 to PR2 match INT enabled
PIE2 = %00000000 'Set TMR1 overflow interrupt
PIR1 = %00000000
PIR2 = %00000000
PCON = %00000000
IOC = %00000000 'Interrupt On Change disabled
PCON = %00000000 'Ultra lowpower + BOR disabled

ANSEL = %00000000 'Select analog inputs: none
ANSELH = %00000000 'Select analog inputs: none
ADCON0 = %00000000 'A/D Module is OFF
ADCON1 = %00000000 'A/D control register
CM1CON0 = %00000000 'Comparator1 Module is OFF
CM2CON0 = %00000000 'Comparator2 Module is OFF
VRCON = %00000000 'Voltage ref. disabled

TRISA = %00000000 'Set Input/Output (0 to 5)
PORTA = %00000000 'Ports High/Low (0 to 5)
TRISB = %00000000 'Set Input/Output (4 to 7)
PORTB = %00000000 'Ports High/Low (4 to 7)
TRISC = %00000000 'Set Input/Output (0 to 7)
PORTC = %00000000 'Ports High/Low (0 to 7)

T2CON = %00000100 'Timer2 is On,
T1CON = %00000000 'Timer1 is off
CCP1CON = %00000000 'CCP engine is off

'==== Set Constants =====================================
'none

'==== Setup DTints interrupt handler ====================
ASM
INT_LIST macro ;Setup IntSource
INT_Handler TMR0_INT, _intTMR0, PBP, yes ;Enable TMR0 interrupts
INT_Handler TMR2_INT, _intTMR2, PBP, yes ;Enable TMR0 interrupts
endm
INT_CREATE ;Create interrupt processor
ENDASM

'==== Dec's for pin aliasing INPUT'S ====================
'none

'==== Dec's for pin aliasing OUTPUT'S ===================
gi1 var PORTC.0 'Injector 1 output pin

'==== Dec's for variables ===============================
dutycycle var byte 'Holds the needed dutycycle
dutycount var byte 'Counter for dutycycle

'==== Initialise variables ==============================
gistate = 0
dutycycle = 0
dutycount = 0

'==== Wait for hardware settle ==========================
pause 500

'==== Enable interrupt processing =======================
TMR0 = 190 'Preload Timer0 for interrupt @ 120Hz
PR2 = 64 'Preload PR2 for dutycycle interrupt
dutycycle = 128 'Set duty cycle to 50%

@ INT_ENABLE TMR0_INT ;Activate TMR0 INT
@ INT_ENABLE TMR2_INT ;Activate TMR2 INT

'==== Main program loop =================================
main:
@ nop ;Do nothing
goto main

'==== The End ===========================================
theend: ' Endless loop
goto theend ' Endless loop

'Routines and Handlers
'==== TMR0 Interrupt handler ============================
intTMR0: 'TMR0 INTERRUPT HANDLER
TMR0 = 190 'Reload TMR0
gi1 = 1 'Activate injector 2
@ INT_RETURN ;Jump back to previous process

'==== TMR2 Interrupt handler ============================
intTMR2: 'TMR0 INTERRUPT HANDLER
PR2 = 64 'Reload PR2

if dutycount = dutycycle then 'If dutycycle is met,
gi1 = 0 'Deactivate injector 1
dutycount = 0 'Reset the dutycounter
else
dutycount = dutycount + 1 'Add 1 to dutycounter
endif
@ INT_RETURN ;Jump back to previous process


Anyone have an idea ?

Best regards, UB

ultiblade
- 29th January 2010, 14:46
I just had another idea. I need a base frequency of 120Hz. On a 8Mhz intosc, if i make the frequency 256 x 120Hz = 30720Hz and count 256 interrupts and then fire a pin i should have 120Hz, right ?

If i setup timer0 with a 1:2 prescaler and preload it with 224 it should have 30,303 KHz which is acceptable. However i can't seem to go any higher than 10Khz.

The code below toggle's the pin on every interrupt, so i should see a 15Khz signal, instead i'm getting a 5Khz signal.



'==== Dec's for OSC =====================================
DEFINE OSC 8 'Int OSC @ 8 MHz, for PBP
OSCCON = %01110001 'Int OSC @ 8 MHz

'==== Set includes ======================================
INCLUDE "DT_INTS-14.bas" 'DTints Interrupt System
include "ReEnterPBP.bas" 'Enable PBP int reenter

'==== Set defines =======================================
OPTION_REG = %00000000 'Set TMR0 precsaler to 1:256, source Fosc/4
WPU = %00000000 'Weak pull-ups disabled

INTCON = %00100000 'Global + Perhip. INT enabled, TMR0 int enabled
'PIE1 = %00000000 'TMR2 to PR2 match INT enabled
'PIE2 = %00000000 'Set TMR1 overflow interrupt
'PIR1 = %00000000
'PIR2 = %00000000
'PCON = %00000000
'IOC = %00000000 'Interrupt On Change disabled
'PCON = %00000000 'Ultra lowpower + BOR disabled

ANSEL = %00000000 'Select analog inputs: none
ANSELH = %00000000 'Select analog inputs: none
ADCON0 = %00000000 'A/D Module is OFF
ADCON1 = %00000000 'A/D control register
CM1CON0 = %00000000 'Comparator1 Module is OFF
CM2CON0 = %00000000 'Comparator2 Module is OFF
VRCON = %00000000 'Voltage ref. disabled

TRISA = %00000000 'Set Input/Output (0 to 5)
PORTA = %00000000 'Ports High/Low (0 to 5)
TRISB = %00000000 'Set Input/Output (4 to 7)
PORTB = %00000000 'Ports High/Low (4 to 7)
TRISC = %00000000 'Set Input/Output (0 to 7)
PORTC = %00000000 'Ports High/Low (0 to 7)

T2CON = %00000100 'Timer2 is On,
T1CON = %00000000 'Timer1 is off
CCP1CON = %00000000 'CCP engine is off

'==== Setup DTints interrupt handler ====================
ASM
INT_LIST macro ;Setup IntSource
INT_Handler TMR0_INT, _intTMR0, PBP, YES ;Enable TMR0 interrupts
endm
INT_CREATE ;Create interrupt processor
ENDASM


'==== Wait for hardware settle ==========================
pause 500

'==== Enable interrupt processing =======================
@ INT_ENABLE TMR0_INT ;Activate TMR0 INT

'==== Main program loop =================================
main:
@ nop ;Do nothing
goto main

'==== The End ===========================================
theend: ' Endless loop
goto theend ' Endless loop

'Routines and Handlers
'==== TMR0 Interrupt handler ============================
intTMR0: 'TMR0 INTERRUPT HANDLER
TMR0 = 223 'Reload TMR0
toggle PORTC.0

@ INT_RETURN ;Jump back to previous process

Kamikaze47
- 3rd February 2010, 16:35
With a 8Mhz internal clock (which translates to a 2Mhz timer clock), if you set timer 0 with a no scalers, the variable TMR0 will increment every 0.5uS, and overflow every 128uS.

For a frequency of 30720Hz you want TMR0 to overflow every 32.552083uS which translates to 65.104167 increments of 0.5uS.

So if you preload TMR0 with the value 191, it will increment 65 times every time before overflowing, giving you a frequency of 30.769kHz (or equiv. of 120.19Hz PWM).

ultiblade
- 4th February 2010, 07:22
You're so right ! I don't know how i missed this. Thanks !

A couple of day's ago i found a timer calculator, i just entered the frequency and the calculations were made. However, it seems that the scrollbars that are used to set the prescalers and preload values in this calculator are refreshed after clicking calculate. I was using bad combinations every calculation.

I tried your values, and it worked like a charm, thanks for replying !

Thanks !

Mike, K8LH
- 7th February 2010, 13:41
You might also consider using the PWM module along with a small interrupt driven "helper" to link together several smaller PWM period "frames" into the much longer 120-Hz PWM period. This method produces a relatively low overhead high resolution jitter free PWM output.

I don't have PBP but perhaps you and other Forum members can muddle through this pseudo C code example.

This example uses a 12-MHz clock (Tcy = 0.3334-usecs) with PWM module settings that produce a PWM period of 333.3334-usecs (250 x 1.3334-usec 'steps'). The interrupt "helper" links together 25 of these short 333.3334-usec PWM period "frames" to produce a precise 120-Hz (8333.3334-usec) period with an output pulse width of 0 to 6250 x 1.3334-usec "steps".

Regards, Mike


;************************************************* *****************
;
; // setup PWM for a 333.3 usec period (12-MHz xtal, Timer 2
; // prescale 4:1, PR2 = 249) and use 25 of these 333.3 usec
; // PWM "frames" for a precise 120-Hz (8.3334 msec) period.
;
; char frame = 1; // initialize "frame" var'
; int width = 100; // 0..6250 (1.333 usec steps)
; int pulse; // isr work variable, 0..6250
;
; void interrupt() // 120-Hz example
; { pir1.TMR2IF = 0; // clear timer 2 interrupt flag
; frame--; // decrement 'frame' number
; if(frame == 0) // if end-of-period
; { frame = 25; // reset for new period
; pulse = width; // refresh work variable
; }
; if(pulse > 250) //
; { ccpr1l = 250; // do a 100% duty cycle frame
; pulse -= 250; // update 'pulse'
; }
; else //
; { ccpr1l = pulse; // do variable duty cycle
; pulse = 0; // force remaining frames to 0%
; }
;
;
;
; void Main() //
; { cmcon0 = 7; // comparators off
; ansel = 0; // a2d convertor off
; pir1 = 0; // clear all interrupt flags
; pie1.TMR2IE = 1; // enable TMR2 interrupts
; ccpr1l = 0; // set 0% duty cycle (output off)
; t2con = 0b00000101; // '0-------' unimplemented bit
; // '-0000---' TOUTPS<3:0>, postscale 1
; // '-----1--' TMR2ON, turn Timer 2 on
; // '------01' T2CKPS<1:0>, prescale 4
; pr2 = 250-1; // 250 x 1333-nsec 'ticks' = 333 usecs
; intcon = 0b11000000; // '1-------' GIE, enable global ints
; // '-1------' PEIE, enable peripheral ints
; // '--0-----' T0IE, TMR0 ints disabled
; // '----0---' GPIE, IOC disabled
; // '-----000' T0IF/INTF/GPIF flags
; while(1) //
; { //
; } //
; }
;

ultiblade
- 17th February 2010, 07:09
Hi all,

Thanks for the helping hand and code. I used your code samples but it just didn't do the trick. My goal was to be able to have 2 low freqency PWM signals with a 90 degree phase shift.

I finally accomplished this by using a 18F1230 @ 8Mhz , DT-INTS and a few lines of code.

The code below produces a 30Hz PWM that increments it's dutycycle every interrupt until it reaches 100%, it will then decrement to 1% and start over. It uses a 'base' frequency of 60Hz (PTPER 519), and distributes the dutycycle over the 30Hz periods.



'==== Set fuses
@ __CONFIG _CONFIG1H, _OSC_INTIO2_1H & _FCMEN_OFF_1H & _IESO_OFF_1H
@ __CONFIG _CONFIG2L, _PWRT_OFF_2L & _BOR_OFF_2L & _BORV_3_2L
@ __CONFIG _CONFIG2H, _WDT_OFF_2H & _WDTPS_1_2H
@ __CONFIG _CONFIG3L, _HPOL_HIGH_3L & _LPOL_HIGH_3L & _PWMPIN_ON_3L
@ __CONFIG _CONFIG3H, _FLTAMX_RA5_3H & _T1OSCMX_HIGH_3H & _MCLRE_OFF_3H
@ __CONFIG _CONFIG4L, _STVREN_OFF_4L & _BBSIZ_BB256_4L & _XINST_OFF_4L & _DEBUG_OFF_4L
@ __CONFIG _CONFIG5L, _CP0_OFF_5L & _CP1_OFF_5L
@ __CONFIG _CONFIG5H, _CPB_OFF_5H & _CPD_OFF_5H
@ __CONFIG _CONFIG6L, _WRT0_OFF_6L & _WRT1_OFF_6L
@ __CONFIG _CONFIG6H, _WRTB_OFF_6H & _WRTC_OFF_6H & _WRTD_OFF_6H
@ __CONFIG _CONFIG7L, _EBTR0_OFF_7L & _EBTR1_OFF_7L

'==== Include DT interrupts
INCLUDE "DT_INTS-18.bas" ;Base Interrupt System
INCLUDE "ReEnterPBP-18.bas" ;Include if using PBP interrupts

'==== Dec's for OSC
OSCCON = %01110000 'Int OSC @ 8 MHz
'OSCTUNE = %00000000 'PLL disabled, center freqency
DEFINE OSC 8 'Int OSC @ 8 MHz, for PBP

'==== Initialize DT_INTS
ASM
INT_LIST macro ; IntSource, Label, Type, ResetFlag?
INT_Handler PT_INT, _pwmINT, PBP, YES
endm
INT_CREATE ;Creates the interrupt processor
ENDASM

'==== Set port associated registers
ADCON0 = %00000000 'A/D convertor disabled
ADCON1 = %00001111 'All ports configured digital
ADCON2 = %10000000 'Right justify results
CMCON = %00000000 'Comparator modules disabled
CVRCON = %00000000

PTCON0 = %00001100 'Fosc/4 Prescale 1:64, Free running mode
PWMCON0 = %00110111 'PWM0,1,2,3 enabled, independent mode
PWMCON1 = %00000001 'Special event on count upwards, Sync PWM timebase

PORTA = %00000000
PORTB = %00000000
TRISA = %01100000
TRISB = %00000100

'==== Dec's for pin aliasing INPUT'S
'none

'==== Dec's for pin aliasing OUTPUT'S
'none

'==== Reset PWM duty registers
PDC0L = 0: PDC0H = 0 'Reset duty register 0
PDC1L = 0: PDC1H = 0 'Reset duty register 1
PDC2L = 0: PDC2H = 0 'Reset duty register 2

'==== Dec's for variables
pwmFREQ var word
pwmDUTY_TGT var word
pwmDUTY var word
pwmDIR var bit
pwmTOG var bit
pwmFL var bit


'==== Initialise variables
pwmFREQ = 0
pwmDUTY_TGT = 0
pwmDUTY = 0
pwmDIR = 0
pwmTOG = 0
pwmFL = 0


'==== Wait for hardware settle
Pause 500 'Settle for 0,5 seconds

'==== Enable PWM interrupts
@ INT_ENABLE PT_INT ;Enable PWM to PTPER match interrupts

'==== SET PWM frequency and dutycycle
pwmFREQ = 519 'Makes 60,010 Hz, 100% dutycycle = 2076
pwmDUTY = 1 'Initial dutycycle
PTPERL = pwmFREQ.lowbyte 'Set PWM frequency lowbyte
PTPERH = pwmFREQ.highbyte 'Set PWM frequency lowbyte
'pwmFREQ_CO = 2076 '100% dutycycle

'Find out if desired PWM is below 50% or over.
'Double the ammount of dutycycle because
'the frequency will be half the base frequency ..
if pwmDUTY <= 1038 then
pwmDUTY_TGT = pwmDUTY * 2
pwmDIR = 0
else
pwmDUTY_TGT = (pwmDUTY - 1038) * 2
pwmDIR = 1
endif

PTCON1 = %10000000 'Start PWM output

'==== Main program loop
main_loop:
@ nop 'Do nothing

goto main_loop

'==== The End
theend: 'Endless loop
goto theend 'Endless loop

'==== Routines
pwmINT:
PWMCON1.1 = 1
if pwmDIR = 0 then
if pwmTOG = 0 then
PDC0L = 0
PDC0H = 0
PDC1L = pwmDUTY_TGT.lowbyte
PDC1H = pwmDUTY_TGT.highbyte
pwmTOG = 1
else
PDC0L = pwmDUTY_TGT.lowbyte
PDC0H = pwmDUTY_TGT.highbyte
PDC1L = 0
PDC1H = 0
pwmTOG = 0
endif
else
if pwmTOG = 0 then
PDC0L = 2076
PDC0H = 2076
PDC1L = pwmDUTY_TGT.lowbyte
PDC1H = pwmDUTY_TGT.highbyte
pwmTOG = 1
else
PDC0L = pwmDUTY_TGT.lowbyte
PDC0H = pwmDUTY_TGT.highbyte
PDC1L = 2076
PDC1H = 2076
pwmTOG = 0
endif
endif

if pwmfl = 0 then
pwmduty = pwmduty + 1
else
pwmduty = pwmduty - 1
endif

if pwmduty >= 2076 then pwmfl = 1
if pwmduty <= 0 then pwmfl = 0

if pwmDUTY < 1038 then
pwmDUTY_TGT = pwmDUTY * 2
pwmDIR = 0
else
pwmDUTY_TGT = (pwmDUTY - 1038) * 2
pwmDIR = 1
endif
PWMCON1.1 = 0
@ INT_RETURN


Problem solved, Thanks guys !
Best regards, UltiBlade