Using PWMDIV options with the PICAXE-28X2 and 40X2

hippy

Senior Member
Introduction

On PICAXE-28X2 and 40X2 parts any new 'pwmout' command will reset 'pwmdiv' divider settings used in previous 'pwmout' commands.

This issue only applies if:
  1. You are using a 28X2 or 40X2 part,
  2. You are using multiple 'pwmout' commands on different pins,
  3. You are using 'pwmdiv' dividers within these 'pwmout' commands.
The issue is that a 'pwmdiv' divider is only applied to the last 'pwmout' command used.

Any 'pwmdiv' divider previously used on any other pin is reset to the default (1) via the latest 'pwmout' command.

This issue can be worked around by manually resetting the 'pwmdiv' settings on the other pins using 'pokesfr' commands.

Detailed Description

On the 28X2 and 40X2 any change to the 'pwmdiv' settings of a particular 'pwmout' channel affects the 'pwmdiv' settings of other 'pwmout' channels. When using multiple 'pwmout' channels and 'pwmdiv' settings the outcome may not always be as anticipated.

This is an issue which only affects the PICAXE-28X2 and 40X2 parts, only when using multiple 'pwmout' channels. Whether there may be an unexpected outcome or not depends on which 'pwmout' channels are used, the 'pwmdiv' settings required and the order any changes to 'pwmdiv' settings are applied.

On 28X2 and 40X2 devices the following pins can be used for 'pwmout' -
  • B.0
  • B.5
  • C.1 - Shares timer with C.2
  • C.2 - Shares timer with C.1
The 28X2 and 40X2 have two independent 'pwmout' channels on B.0 and B.5 which are each controlled by separate timers. They also have two additional 'pwmout' channels on C.1 and C.2 controlled by a single, shared timer. The 'pwmdiv' setting is applied to the timer which drives the 'pwmout' channel.

Due to the way the firmware interacts with the internal chip control registers to provide 'pwmout' only the last 'pwmdiv' applied takes affect and other 'pwmout' channels are reset to what is effectively 'pwmdiv1'.

To give a clarifying example -

Code:
pwmout pwmdiv4, B.0, [i]period[/i], [i]duty[/i]
pwmout pwmdiv4, B.5, [i]period[/i], [i]duty[/i]
B.0 is initialised with the desired 'pwmdiv4' but after B.5 is initialised B.0 is reset to 'pwmdiv1'; so the outcome is as if the code executed were -

Code:
pwmout          B.0, [i]period[/i], [i]duty[/i]
pwmout pwmdiv4, B.5, [i]period[/i], [i]duty[/i]
If 'pwmout' channels C.1 or C.2 are active they too will be reset to 'pwmdiv1'.

When using C.1 and C.2 for 'pwmout', because these use the same timer, a change to one will always affect the other, so for -

Code:
pwmout pwmdiv4, C.1, [i]period[/i], [i]duty[/i]
pwmout pwmdiv4, C.2, [i]period[/i], [i]duty[/i]
The outcome is as would be expected; C.1 was using 'pwmdiv4', and setting C.2 to 'pwmdiv4' sets C.1 to the same, which is what it was already set to.

In addition though, a change to C.1 or C.2 will reset B.0 and B.5 to 'pwmdiv1'.

When using multiple 'pwmout' channels and altering 'pwmdiv', the final outcome will depend upon which 'pwmout' channels are being used, which 'pwmdiv' settings are applied, and the order in which 'pwmdiv' settings are applied.

Unfortunately it is not possible to track what 'pwmdiv' settings have been used within the firmware, nor can the compiler determine where 'pwmdiv' settings for timers would need to be adjusted automatically.

There is however a solution and that is to manually reset the timers for 'pwmout' channels to the desired 'pwmdiv' value when necessary. This can be achieved using the 'peeksfr' and 'pokesfr' commands to access the internal registers which hold the 'pwmdiv' settings for the timer which drives the 'pwmout' channel. The code required to do this for each 'pwmout' channel is detailed later.

For example, to achieve setting both B.0 and B.5 to 'pwmdiv4', where the setting of B.5 will have reset B.0 to 'pwmdiv1', force the 'pwmdiv' setting for B.0 after setting B.5 -

Code:
pwmout pwmdiv4, B.0, [i]period[/i], [i]duty[/i]
pwmout pwmdiv4, B.5, [i]period[/i], [i]duty[/i]
peeksfr $4A, b0
bit0 = 1
bit1 = 0
pokesfr $4A, b0
The code required to force each 'pwmout' channel to a specific 'pwmdiv' setting is detailed below -

To force B.0 to 'pwmdiv4'

peeksfr $4A, b0
bit0 = 1
bit1 = 0
pokesfr $4A, b0​

To force B.0 to 'pwmdiv16'

peeksfr $4A, b0
bit1 = 1
pokesfr $4A, b0​

To force B.5 to 'pwmdiv4'

peeksfr $51, b0
bit0 = 1
bit1 = 0
pokesfr $51, b0​

To force B.5 to 'pwmdiv16'

peeksfr $51, b0
bit1 = 1
pokesfr $51, b0​

To force C.1 and C.2 to 'pwmdiv4'

peeksfr $BA, b0
bit0 = 1
bit1 = 0
pokesfr $BA, b0​

To force C.1 and C.2 to 'pwmdiv16'

peeksfr $BA, b0
bit1 = 1
pokesfr $BA, b0​
 
Last edited:

Morganl

Senior Member
For some reason reality seem to differ slightly from above, running on 28X2 vB.3, using PE6.0.6.4:
Copied and completed the code from last section above, and finished it for running:
Code:
#picaxe 28X2
#no_table
pinsB=0 ;for sanity of logic analyser
dirsB=$FF ;tie all unused pins low.
pwmout pwmdiv4, B.0, 254, 100
pwmout pwmdiv4, B.5, 254, 50
peeksfr $51, b0
bit0 = 1
bit1 = 0
pokesfr $51, b0
do
loop
I see on the scope B.0 oscillates 4 times faster than B.5
But if i exchange B.0 and B.5 both have the slow frequency.

Is the workaround different between revisions?
Have not tested other things, post above seem to need correction.
 

hippy

Senior Member
I must admit that I cannot recall all the details from almost two years ago and would have to investigate further and verify what you are seeing.
 

hippy

Senior Member
It appears the SFR addresses which relate to B.0 and B.5 PWMOUT channels were inadvertently switched in the original post; the correct SFR should be $4A for B.0, $51 for B.5. I have updated the original post accordingly.

Many thanks for spotting this mistake which we and everyone else seem to have missed and please accept our apologies for it.

In the original version; setting B.0 as PWMDIV4, then setting B.5 as PWMDIV4 would reset B.0 to PWMDIV1, and the POKESFR was setting B.5 to PWMDIV4 rather than B.0. Thus B.0 was left at PWMDIV1 so runs four times faster than expected.

Swapping the order of the PWMOUT so B.5 first then B.0; sets B.5 as PWMDIV4, then setting B.0 as PWMDIV4 would reset B.5 to PWMDIV1, the POKESFR sets B.5 to PWMDIV4 rather than B.0 so both become the same, PWMDIV4.

With the SFR addresses now corrected everything should work as expected.
 

Morganl

Senior Member
That was quick, thanks :)
Could have been even quicker if we had a list of which function use which resource ;)
Nag, nag...

I the current code i just save/restore the configuration:
Code:
pwmout pwmdiv16, B.5 , 254 , 50
#define T4CON $51
peeksfr T4CON, b0 ; pwmdiv command resets dividers on earlier channels...
pwmout pwmdiv16, B.0 , 254 , 50
pokesfr T4CON, b0 ; ... so restore it!
I believe there should not be any problem with that?
 

hippy

Senior Member
I believe there should not be any problem with that?
I don't know. Doing it that way could overwrite any alterations the firmware makes in executing the PWMOUT command. We recommend the method described in the original post which alters only the bits which need to be changed after PWMOUT execution completes.
 

Morganl

Senior Member
I think you need to revisit the method for C.1 and C.2
Using the following program, they are not frequency divided:
Code:
pwmout pwmdiv16, C.1 , 254 , 10
pwmout pwmdiv16, C.2 , 254 , 300
pwmout pwmdiv16, B.5 , 254 , 700
pwmout pwmdiv16, B.0 , 254 , 1000
;__Set pwmdiv16 on B.5
#define T4CON $51
peeksfr T4CON, b0 : bit1 = 1 : pokesfr T4CON, b0
;__Set pwmdiv16 on C.1 & C.2
#define T2CON $BA
peeksfr T2CON, b0 : bit1 = 1 : pokesfr T2CON, b0
do:loop
 
Top