PDA

View Full Version : SPWM_INT - Multiple Software PWM Question



RossWaddell
- 15th January 2013, 20:12
(I'm starting a new thread as none of the ones I searched which referenced 'SPWM_INT' seemed appropriate)

I've implemented Darrel's excellent SPWM_INT include file together with ADCIN (reading a trim pot) to adjust the duty cycle for one pin (see my thread here (http://www.picbasic.co.uk/forum/showthread.php?t=17581)). The problem is that (a) using 8-bit resolution (0-255) makes the LED brightness adjustment a little coarse, and (b) there's a delay in changing the duty cycle no matter how low I adjust the ADC_SAMPLEUS value. I'm willing to live with (b) but I'd like to move to 10-bit resolution to address (a).

Is it just a matter of changing
DEFINE SPWM_RES 256 to
DEFINE SPWM_RES 1024 and then altering
DEFINE ADC_BITS 8 to
DEFINE ADC_BITS 10 and
ADCON1.7 = 1 for right-justified values? Will that give me the finer control of LED brightness that I'm after?

BTW, I'm using a PIC16F1825 with 16Mhz internal oscillator setting.

Darrel's page (below) doesn't have the frequency range definitions like his earlier Multi-SPWM page, but I think this won't work with DC motors as the frequency needs to be greater than 16,000 Hz. Is that right?

DT_INTS-14 (SPWM_INT - Multiple Software PWM)
http://www.darreltaylor.com/DT_INTS-14/SPWM.html

RossWaddell
- 15th January 2013, 21:38
(I guess I really have 2 issues here: finer LED brightness control and doing PWM for a motor (since I also want to 'fade in/out' the motor RPM by changing the duty cycle). If this should be a separate thread, please let me know)..

For the motor application, I think I can configure the registers for the duty cycle (CCP1CON.4 & .5, CCPR1L) but I can't wrap my head around the frequency calculation (with 10-bit resolution at FOSC 32Mhz, I think I need PR2 = 255 with a timer prescale of 1 to get 31.25kHz judging from Table 24-5 in the PIC16F1825 datasheet). But what if I want to aim for 16-20kHz?

Darrel Taylor
- 15th January 2013, 22:29
SPWM_INT has a maximum resolution of 8-bits (0-255), as detailed on the page you linked to.

For using PWM with the CCP module, mister-e's PicMultiCalc is very useful for figuring out what frequencies and dutycycles you can get.
http://www.picbasic.co.uk/forum/content.php?r=159-New-PIC-Utility.-PICMultiCalc

RossWaddell
- 15th January 2013, 23:41
Thank Darrel. I mis-read "PWM resolution is no longer limited to 8-bit (256)" on your web page to mean higher resolutions were possible - I guess you mean lower resolutions are now possible.

I just grabbed Mr E's MultiCalc, but it doesn't seem to output results for a 1:1 prescaler (which I think would give me 10-bit resolution for 32Mhz & 16kHz). Also, when you say it will give me available duty cycles does that mean I can't just use 0-1023 (10-bit) or 0-255 (8-bit) when incrementing the CCPRIL & CCP1CON.4 & 5 registers?

Darrel Taylor
- 16th January 2013, 02:48
No, it's not very often that you get a full 0-1023 resolution with hardware PWM.

And you can't get 16Khz from Timer2 with a 1:1 prescaler, which is why PicMultiCalc didn't show it.
With a 32Mhz OSC, the number of timer ticks required is ...

32,000,000 / 4 / 16,000 = 500

But Timer2 is only an 8-bit counter, so the max is 256 ticks till it overflows.
The next available prescaler is 1:4. So divide 500 / 4 = 125
Timer2 overflows at PR2 + 1, so subtract 1 to make it 124. This is the PR2 value for 16Khz.

The maximum dutycycle value is (PR2 + 1) * 4 because of the CCPxCON.4,5 bits.
(124 + 1) * 4 = 500
So 500 is the "Always On" point (100%), and anything above that is 100% too.

If your control value is 0-1023, it must be scaled to 0-500.

500 is slightly less than a 9-bit value, which is 1-bit better than the 8-bit resolution of SPWM_INT.

At 32kHz, you can get 0-1000 dutycycle, because a 1:1 Timer2 prescaler will give 32Khz when PR2 = 249.

RossWaddell
- 16th January 2013, 19:51
If I switch to 16Mhz OSC I can get 10-bit resolution at 16kHz:

6820

So here's the thing: if I set up my ADCIN to use 10-bit then presumably I can get 0-1023 values from my trim pot but since 1000 is the max duty cycle anything above that changes nothing. Will it cause a problem if I pass that in to the duty cycle registers (e.g. 1012) or do I need to set a programmatic limit at 1000? Same applies to 8-bit - max duty cycle is 250 but 8-bit resolution on ADCIN would give me 0-255, so anything greater than 250 does nothing.

HenrikOlsson
- 16th January 2013, 21:13
As Darrel wrote earlier, anything above the highest value (1000 in this case) will cause 100% dutycycle - all that's happening is that you get a flat spot at the very top of the otherwise linear curve. Only you know if that will be an actual problem for you.
If you want to scale your 0-1023 ADC input value to 0-1000 dutycycle value you could do something like DutyCycle=ADCValue**64063 or, for 8bit resolution, DutyCycle=ADCValue**64251.

/Henrik.

RossWaddell
- 16th January 2013, 21:31
Got it - thanks Henrik (and Darrel). I've been playing around with Mr E's multi-calc with various OSC & freq values - I never know there were upper limits to duty cycles before and that they depended on the prescaler as well. All of this is hidden if using pbp's PWM or HPWM, but I want to get into the down-and-dirty of PWM registers to really understand this stuff (besides, it's fun). For the motor spinning up aspect, I had read that it's not good to do something like this:



For dutyval = 0 to 255
HPWM 1, dutyval, 16000
Next dutyval

because the HPWM command changes more than just the duty cycle register values. Plus, on my PIC16F1824 I want to use CCP3 & CCP4 which you can't do with pbp's HPWM.

HenrikOlsson
- 16th January 2013, 22:19
Hi,
Basically the PWM generator consists of a counter, a dutycycle register and a comparator.
The PWM period (therefor frequency) is the time it takes for the counter to count from 0 to whatever value makes it roll over back to 0.

* When the PWM period starts (counter=0) the output is set.
* When the counter value equals that of the dutycycle register (that's the comparators job) the output is reset.
* The counter continues counting up, rolls over and the cycle repeats.

Now, to make it easy, concider the counter rolling over at 99 making it a 100 "step" counter. If you set the dutycycle value to 50 the output will turn on and stay on untill the counter hits 50 then turn off. The counter continues counting up to 99, rolls over and the cycle repeats. The output is on for 50 "ticks" and off for 50 "ticks" - you have 50% dutycycle.

If the counter is "ticking" at 1MHz the PWM period would be 100us (f=10kHz). Now, if you want to change the PWM frequency you can do one of two things.
A) Change the frequency at which the counter "ticks".
B) Change at which value the counter rolls over (this is the PR2 value as described earlier).

Changing the "counter roll-over value" so that the counter rolls over at 49 instead of 99 will cause the PWM frequency to double but it'll also cause your resolution to be cut in half since there's now only 50 "steps". So when you previously set the dutycycle value to 50 to get 50% you'd now have to set it to 25.

What makes it a little bit tricky to understand in the PIC is how you can have PR2 set to 200 and still be able to set your dutycycle value to 600 (for example). This is because of those additional two bits of the counter (and the dutycycle register and comparator) so when you set PR2 to 200 the counter actually counts from 0 to 800 (rolling when hitting 801).

As for your HPWM example (and I'm not sure about this) I think that the HPWM command sets everything up every time so to speak. Even if all you're actually doing is changing the dutycycle it most likely does some other stuff behind the scenes (you MAY wanted to change the frequency or the PWM MAY not have been running) so there may be glitches in the output when trying to change the dutycyle by rapidly "calling" HPWM like that.

Well, enough rambling, time to hit the sack for me.

/Henrik.

RossWaddell
- 20th January 2013, 22:50
Any idea why I get this ASM warning when compiling my code? I've included the snippet of code which I think is applicable, and the full code too.

6824



' ************************************************** *************
' Set up registers for PWM on CCP3 & 4
' ************************************************** *************

; Use Mister E's MultiCalc to calculate PR2 value & hence Prescaler setting
; for T2CON:

; http://www.picbasic.co.uk/forum/content.php?r=159-New-PIC-Utility.-PICMultiCalc

; Can also use this web-based site:
; http://www.micro-examples.com/public/microex-navig/doc/097-pwm-calculator.html

CCP3CON = %00001100 ; Use CCP3 in PWM mode
CCP4CON = %00001100 ; Use CCP4 in PWM mode

;CCPTMRS = %00000000 ; Use Timer2 for all CCPs in PWM mode
; (The default appears to be Timer2, so no need
; to set it explicitly)

T2CON = %00000100 ; Timer2 on with 1:1 prescaler
PR2 = 249 ; For 16Mhz OSC the desired output of 16,000Hz is
; achieved with this PR2 value (10-bit resolution
; with 1:1 prescaler)

MinDuty CON 50 ; Lowest possible duty cycle at which motor spins
MaxDuty CON 1000 ; Maximum duty cycle available with PR2 = 249 and
; 1:1 prescaler (According to Darrel:
; MaxDuty = (PR2 + 1) * 4

DutyVar3 VAR WORD
DutyVar4 VAR WORD

' Spin up motors to saved value of _MotorRPM
' (Below a cycle of MinDuty, the motors don't move at all)
FOR I = (MinDuty - 10) to MotorRPM
DutyVar3 = I
CCP3CON.4 = DutyVar3.0
CCP3CON.5 = DutyVar3.1
CCPR3L = DutyVar3 >> 2

IF PortEngDir = 0 THEN
DutyVar4 = I
CCP4CON.4 = DutyVar4.0
CCP4CON.5 = DutyVar4.1
CCPR4L = DutyVar4 >> 2
ELSE
DutyVar4 = (MaxDuty - I)
CCP4CON.4 = DutyVar4.0
CCP4CON.5 = DutyVar4.1
CCPR4L = DutyVar4 >> 2
ENDIF

pause 33
NEXT I


Full PIC code:
6825

Darrel Taylor
- 20th January 2013, 23:39
Those warnings aren't coming from the section you posted.

You are trying to use a value of 500 with byte sized EEPROM locations.

But I'm pretty sure you really want WORD sized EEPROM locations.
Change this ...
' ************************************************** *************
' EEPROM Variables
' ************************************************** *************

MotorRPM_Default CON 500 ; half speed if max is 1000
EE_MotorRPM DATA MotorRPM_Default
MotorRPM VAR BYTE
READ EE_MotorRPM, MotorRPM

PortEngDir_Default CON 0
EE_PortEngDir DATA PortEngDir_Default
PortEngDir VAR BYTE
READ EE_PortEngDir, PortEngDir
... to this ...
' ************************************************** *************
' EEPROM Variables
' ************************************************** *************

MotorRPM_Default CON 500 ; half speed if max is 1000
EE_MotorRPM DATA WORD MotorRPM_Default
MotorRPM VAR WORD
READ EE_MotorRPM, WORD MotorRPM

PortEngDir_Default CON 0
EE_PortEngDir DATA WORD PortEngDir_Default
PortEngDir VAR WORD
READ EE_PortEngDir, WORD PortEngDir



There are a few other WRITE statements in your program that need to be changed too.

RossWaddell
- 21st January 2013, 01:13
You're a life saver, Darrel - thanks. Updated the corresponding WRITE statements and all compiles fine (I didn't change the 'PortEngDir' stuff as that has only 2 values: 0 & 1)

Was there anything in the warning message which told you where to look?'

Darrel Taylor
- 21st January 2013, 15:27
Woops, I changed too much. :o

When I can't tell were an error is comming from, I go to the .LST file and search for the error or warning.
Ususally, the first error found in that file is the most important one.

It helps being able to read assembly language though.

RossWaddell
- 21st January 2013, 15:33
Being able to read assembly language would be helpful - I'm going to have to add that to the list of 'to-dos'. :)

RossWaddell
- 21st January 2013, 22:14
The maximum dutycycle value is (PR2 + 1) * 4 because of the CCPxCON.4,5 bits.
(124 + 1) * 4 = 500
So 500 is the "Always On" point (100%), and anything above that is 100% too.


Is that calculation dependent somehow on the prescaler? I'd like to programmatically determine the max duty cycle available to me with PR2=249 and prescaler of 1:1 (16Mhz OSC, 16,000Hz freq). With Mister E's MultiCalc it shows me '1000' is the max duty cycle for these paramters, which also works with (PR2 + 1)*4 where PR2 = 249 but is that always the rule?

Darrel Taylor
- 21st January 2013, 22:50
The prescaler has already been factored in when calculating the PR2 value.

The (PR2+1)*4 formula holds true in all cases for the maximum dutycycle.

RossWaddell
- 22nd January 2013, 03:13
Thanks Darrel - I'll add that formula into the next round of changes once I get the basic PWM via registers sorted out.

Part of this thread was how to set PWM frequency/duty cycle via registers so I can use CCP3 & CCP4 on the PIC16F1825 (CCP1 & CCP2 are ECCP's). I have code to control DC motors via a SN754410 motor driver and while it works OK with pbp's HPWM I have to use CCP1 & CCP2 and changing the duty cycle in a loop (the motors have to spin up to the desired speed slowly) means that the HPWM command is changing more than just the duty cycle. So, wanting to learn how to do this via registers I came up with this simple test code:




DEFINE OSC 16 ; Set oscillator 16Mhz

' ************************************************** *************
' Device Fuses
' ************************************************** *************
#CONFIG
__config _CONFIG1, _FOSC_INTOSC & _WDTE_ON & _PWRTE_ON & _MCLRE_OFF & _CP_OFF & _CPD_OFF
__config _CONFIG2, _PLLEN_OFF & _STVREN_ON & _BORV_LO & _LVP_OFF
#ENDCONFIG

OSCCON = %01111000 ' 16MHz internal osc

pause 100 ' As advised by Darrel Taylor for EEPROM issue

ANSELA = 0 ; Digital only for all PortA pins
ANSELC = 0 ; Diginal only for all PortC pins

TRISA = 0
TRISC = 0

MOTOR_1_DIR VAR PORTC.0 ' Alias PORTC.0 as "MOTOR_1_DIR"
MOTOR_1_PWM VAR PORTA.2 ' Alias PORTA.2 as "MOTOR_1_PWM"

' ************************************************** *************
' Set up registers for PWM on CCP3 & 4
' ************************************************** *************

CCP3CON = %00001100 ; Use CCP3 in PWM mode
T2CON = %00000101 ; Timer2 on with 1:4 prescaler
PR2 = 62 ; For 16Mhz OSC the desired output of 15,873Hz is
; achieved with this PR2 value (8-bit resolution
; with 1:4 prescaler)

i VAR BYTE

DutyVar3 VAR BYTE

;LOW MOTOR_1_DIR
;DutyVar3 = 0

HIGH MOTOR_1_DIR
DutyVar3 = 250

FOR i = 0 to 250
; DutyVar3 = DutyVar3 + 1
DutyVar3 = DutyVar3 - 1

CCP3CON.4 = DutyVar3.0
CCP3CON.5 = DutyVar3.1
CCPR3L = DutyVar3 >> 2

pause 33
NEXT i

Main:

GOTO Main


For clockwise rotation of the DC motor, I use:



LOW MOTOR_1_DIR
DutyVar3 = 0


and inside the loop:



DutyVar3 = DutyVar3 + 1


This works! The motor spins CW slowly up to 250 (max duty cycle with prescaler of 1:4, according to Mister E's MultiCalc).

The problem is when I want to spin the motor CCW. For that, I use:



HIGH MOTOR_1_DIR
DutyVar3 = 250


and inside the loop:



DutyVar3 = DutyVar3 - 1


This almost works: the motor spins CCW slowly up to max speed but then when the loop is over (and presumably the motor is spinning its fastest) it just stops. Am I using the wrong register settings when I want to count backwards from the maximum (with the SN754110, if the motor direction pin is HIGH then the motor speed starts at the opposite end of the duty cycle range).

Aussie Barry
- 22nd January 2013, 04:52
You may need to limit the decrement counter so that it never goes below 0


If DutyVar3 > 0 then
DutyVar3 = DutyVar3 -1
else
DutyVar3 = 0
endif


Cheers
Barry
VK2XBP

RossWaddell
- 22nd January 2013, 15:59
You may need to limit the decrement counter so that it never goes below 0


If DutyVar3 > 0 then
DutyVar3 = DutyVar3 -1
else
DutyVar3 = 0
endif


Cheers
Barry
VK2XBP

Ah! You think DutyVar3 is ending with -1? I can hook up my serial LCD and see for sure, but that looks to be what's happening. Thanks Barry!

RossWaddell
- 26th January 2013, 02:19
That was it. I should have learned by now to go over my loop code in more detail.

Any ideas on combining SPWM and blinking LEDs?