Can someone please explain propeller PLL-based PWM theory to me?

I know the general idea of PWM - a means to approximate an analog signal by toggling an output pin in a carefully governed pattern.
But I am finding it difficult to understand how to correctly write some PASM code to make a Cog generate a variable duty cycle PWM square wave using the built-in PLL counter system.
I've had a good read through of the relevant sections in both the Propeller manual and the datasheet, which both give lengthy descriptions of what all the registers DO, but I'm damned if I can find a real-world example of how to use them in a practical way.
For example, how do you make the Cog output a square wave of 35% duty cycle at 128KHz (rising edge to rising edge)? And then how do you change the duty cycle to 71%? These numbers are arbitrary of course but I can find no clues in the docs to suggest how this is done.
I don't want code, I just need a layman's terms explanation of how to set up the Prop to do this. My brain is starting to hurt and I'm getting datasheet blindness. Is a kind soul willing to write a few paragraphs to help me out or does anyone know a novice-level tutorial that covers all the salient topics?
I've gotten pretty far through the feature set of the Propeller up to now and this is one of the last obscure features (apart from VGA output!!!) I want to understand, and it seems to me that the manual/datasheet could REALLY use a decent set of code examples and a better procedural plain-text description of how to really use the PLL and (in particular) PWM capabilities of the Prop platform.
Any and all help gratefully received, thanks for reading.
Adam.
But I am finding it difficult to understand how to correctly write some PASM code to make a Cog generate a variable duty cycle PWM square wave using the built-in PLL counter system.
I've had a good read through of the relevant sections in both the Propeller manual and the datasheet, which both give lengthy descriptions of what all the registers DO, but I'm damned if I can find a real-world example of how to use them in a practical way.
For example, how do you make the Cog output a square wave of 35% duty cycle at 128KHz (rising edge to rising edge)? And then how do you change the duty cycle to 71%? These numbers are arbitrary of course but I can find no clues in the docs to suggest how this is done.
I don't want code, I just need a layman's terms explanation of how to set up the Prop to do this. My brain is starting to hurt and I'm getting datasheet blindness. Is a kind soul willing to write a few paragraphs to help me out or does anyone know a novice-level tutorial that covers all the salient topics?
I've gotten pretty far through the feature set of the Propeller up to now and this is one of the last obscure features (apart from VGA output!!!) I want to understand, and it seems to me that the manual/datasheet could REALLY use a decent set of code examples and a better procedural plain-text description of how to really use the PLL and (in particular) PWM capabilities of the Prop platform.
Any and all help gratefully received, thanks for reading.
Adam.
Comments
The point is that there is no PWM-mode for the counters. For PWM you also need the COG to do some stuff. It is waiting for one period (1/125kHz in your case) and after that has to update the PHSA register.
With 0% duty you'd choose a value for PHSA with which the counter does not reach $8000_0000 in your period - that value of course depends on the FREQA and PLL settings. With 100% you'd directly set PHSA to $8000_0000.
With the PLL setup and FREQA you can influence the resolution of your PWM.
Hope this helps at least a bit ;o)
here:
http://forums.parallax.com/showthread.php?89958-Propeller-Education-Kit-Labs-Tools-and-Applications
you can download the "Propeller Education Kit Labs" and starting page 121 you will find some info and examples how to use the counters. To get PWM with fixed duty cycle you will need a cog with some code in addition to a counter, see page 157.
Best regards Christof
So I'd have to pick a value for FRQA that over repeated additions, causes that carry bit to toggle at the duty cycle I want?
Is there actually any benefit to using the PLL counter functionality to generate variable duty cycle PWM, when I'd have to make the Cog do such a lot of supporting math? Wouldn't it just be more sensible to do something like...
(pseudocode...)
[PHP]:loop
set pin high
sleep for (35/100) * pwm_frequency //35% duty cycle
set pin low
sleep for (65/100) * pwm_frequency
jmp loop[/PHP]
http://www.parallaxsemiconductor.com/an001
Sigma Delta Analog to Digital Conversion
http://www.parallaxsemiconductor.com/an008
Propeller Education Kit Labs Tools and Applications
http://forums.parallax.com/showthread.php?89958-Propeller-Education-Kit-Labs-Tools-and-Applications
Find the "Transmit Square Wave Frequencies" section.
In the end your approach also eats up a whole COG as a resource, so why not do it with a counter having 12,5ns accuracy?
Actually the code is pretty easy:
1. setup the counter and calculate the clock-ticks once that you have to wait to match your PWM frequency
2. optionally do some other tasks here
3. fetch the duty cycle from HUB-RAM and calculate the next value to be moved to PHSA in 5.
4. waitcnt ' this will also calculate the next cnt-value to wait for
5. update PHSA
6. goto 2
For 3.: 80_000_000 / 125 000 gives you the range you have to subtract from $8000_0000. (0-640 in this case) So, 0 means 100% duty and 640 means 0% duty.
The range itself is the value you have to use in the waitcnt to add up.
A range of 640 means that at 125kHz you have a resolution of a little bit more than 8 bits.
I'll do some experimenting this evening and reappear with my results and code if successful.
My code below is not even remotely sensible and it doesn't work at all. I get random patterns out of it. The hardware I'm using to try this is "known good", so no worries on that score.
Please can someone have a look at this code and tell me how to make it work. I can't even work out the blasted PWM frequency for this thing and I'm beginning to lose my mind with frustration.
I just want it to output ANY duty cycle that isn't 50%.
[PHP]CON
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000
PUB Main
cognew(@PWM_SETUP, 0)
DAT
org 0
PWM_SETUP mov dira, #0
or dira, LED
mov FRQA, #256
mov PHSA, #0
mov CTRA, CTRMODE_VAL 'starts the PLL counter *now*'
mov Time, cnt
add Time, #9
:loop waitcnt Time, Delay
mov PHSA, #0
jmp #:loop
Delay long $8000_0000 / 256
LED long |< 0
CTRMODE_VAL long %0_00100_000_00000000_000000_000_000000
Time res 1[/PHP]
Going rapidly insane.
You have to call it with an address pointing to a variable that holds the duty.
org 0 entry1 mov dira, #%11100000 mov ctra, ctraval1 mov time1, period1 add time1, cnt mov frqa, #1 loop1 rdlong phsa, par waitcnt time1, period1 jmp #loop1 ctraval1 long %00100 << 26 + PIN_B period1 long 65536 time1 res 1
- SPIN arithmetic is signed, meaning $8000_0000 / 256 is not what you really want, try $8000_0000 / -256 or $8000_0000 >> 8
- Delay is setup for a frequency of N Hz, your counter for N/2 Hz, so the output gets reset before it can even change to 1
Regarding frequency calculations check the latest counter AppNote. For NCOs it comes down to frqx/232*clkfreq (page 6). In your example we end up with 256/232*80M = 4.76Hz. Have a look at this and see if you can make it work for you:CON _clkmode = XTAL1|PLL16X _xinfreq = 5_000_000 VAR long parameter PUB main | n ' Our [COLOR="red"]chosen frequency[/COLOR] (PASM) is about 1kHz (78125 cycles @80MHz). ' ' The theory here is that we initialise the counter phsx register to ' a negative value which causes the output to go high. Then the counter ' simply counts up (undoing the "damage"). So let's say we told it to ' go back by 40000. This means we have high for 40000 cycles and low for ' the remaining 38125 cycles, then the update will force high again. ' ' Which also means going back by 78125 will hold the output high for the ' whole period and 0 will give you constant low output. cognew(@entry, @parameter) waitcnt(8192 + cnt) ' startup delay repeat parameter from 0 to 78125 step 625 waitcnt(clkfreq/10 + cnt) repeat parameter from 78125 to 0 step 625 waitcnt(clkfreq/10 + cnt) CON pin = 16 DAT org 0 entry mov dira, diraval ' drive output movs ctra, #pin movi ctra, #%0_00100_000 ' NCO mov frqa, #1 [COLOR="red"]rdlong period, #0 ' get clkfreq shr period, #10{/1024} ' ~1kHz[/COLOR] mov time, cnt add time, period :loop rdlong value, par ' get update waitcnt time, period ' sync to period neg phsa, value ' (re)start count at -phsa jmp #:loop ' repeat diraval long |< pin time res 1 value res 1 period res 1 fit DAT
CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 PUB Main cognew(@PWM_SETUP, 0) DAT org 0 PWM_SETUP mov dira, LED mov FRQA, #1 mov CTRA, CTRMODE_VAL 'starts the PLL counter *now*' mov Time, cnt add Time, #9 :loop waitcnt Time, Delay neg PHSA, PulsWidth jmp #:loop Delay long 8000 '10kHz PWM frequency (80MHz / 10kHz) PulsWidth long 1000 '1/8 pulswidth (range 0..8000) LED long |< 0 CTRMODE_VAL long %0_00100_000_00000000_000000_000_000000 Time res 1
Basically you have an Assembly loop with a fixed frequency (10kHz here) and at the begin of every loop a one-shot pulse from a counter is started. The PulsWidth register defines the length of the pulse and the Delay register defines the length of the PWM periode.Andy
Edit: You can also do a PWM in Spin only. This makes it much simpler to pass the values (just use global variables):
CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 PIN = 0 VAR long periode, pulswidth long stack[8] PUB Main periode := clkfreq / 10_000 cognew(PWM, @stack) repeat pulswidth += 1 'increase pulswidth from 0 to 100% if pulswidth > periode pulswidth := 0 waitcnt(clkfreq/2000 + cnt) PRI PWM : time ctra := %00100<<26 + PIN frqa := 1 dira[PIN] := 1 time := cnt repeat phsa := -pulswidth time += periode waitcnt( time ) '
First of all I'd move the phsa:=-pulswidth directly behind the waitcnt and maybe let the "-" operation been done by the code that sets pulswidth. My expectation is that due to the many SPIN-operations you have in between waitcnt and and setting the phsa (repeat, -pulswith, :=) you introduce some unlinearity in the low duty-cycle range. At least with the fact that the SPIN-COG always has to synchronize with the HUB-timing you will loose some precision, right?
And of course the SPIN is more limited in the max. PWM-frequency. I'm not sure if the frequency of 128kHz can be achieved by SPIN.
For sure you can not reach 128kHz PWM frequency, I would not go over 20kHz with Spin.
The ideal PWM frequency depends on the application, and the resolution you want.
Andy
I'll put my findings on my blog when I get all this working, I bet there are others out there who are having similar trouble as I did.
It seems obvious now, I didn't even get close to this from the info in the datasheet/manual. Perhaps it's an area that would benefit from some clarification for slow-witted folk like myself.
Anyway, I'm going to adapt this code to generate audio waveforms through an op-amp into a speaker!