Using Counters in SPIN
Each counter controls two pins, each COG has two counters, each Propeller has eight COGs, how many HATs were going to St Ives?
While the Parallax Propeller keeps hardware features to a minimum, it makes room for counters. Lots of them, in fact. The Propeller has two counters in each of its 8 COGs, making a total of 16. Each of these counters can control 2 IO pins- so that's all of the Propeller's 32 IO pins covered.
Anatomy of a Counter
Each counter has a set of registers that govern how it behaves, they are:
PHSx
- where your counter value is accumilated. It can be read/written like any other variable.FRQx
- the value that's accumilated into PHSx.CTRx
- governs how the counter behaves and which IO pins it's associated with.
Each of these registers has an A and B counterpart- ie: PHSA/PHSB to differentiate the two counters on each COG. The most complicated and important registers are CTRA and CTRB. These are the control registers for each counter and determine whether the counter is enabled, which pins it's associated with and how it behaves.
Comparatively FRQA/FRQB and PHSA/PHSB are very simple, FRQx is simply a number that can be added to the value of PHSx under certain conditions as defined in CTRx.
Understanding the Control Register
As all registers on the Propeller consist of 32 bits, the control register is simply a series of 32 1s or 0s which are grouped together to configure the 4 options available: Mode, PLL Divider, Pin B and Pin A.
Mode
The table below shows the available values for Mode, which occupies bits 30 to 26 of the CTRx register:
You can set these bits easily in SPIN, like so:
CTRA[30..26] = %11111
Or if you're more familiar with bit shifting, like so:
CTRA := %11111 << 26
PLL Divider
The PLL divider only affects the modes marked PLL in the counter modes table. The PLL is given the output of PHSx[31]
( bit 31 of the Phase register ) as its clock source, and multiplies it by 16. The PLL divider settings allow you to take the resulting clock and divide it by 1, 2, 4, 8, 16, 32, 64 or 128.
%000
= Input Clock * 16 / 128 or 1/8 Input Clock%001
= Input Clock * 16 / 64 or 1/4 Input Clock%010
= Input Clock * 16 / 32 or 1/2 Input Clock%011
= Input Clock 16 / 16 or Input Clock 1%100
= Input Clock 16 / 8 or Input Clock 2%101
= Input Clock 16 / 4 or Input Clock 4%110
= Input Clock 16 / 2 or Input Clock 8%111
= Input Clock 16 / 1 or Input Clock 16
Pins are then toggled at the output frequency of the PLL.
There are some caveats with this approach, however; the PLL can only operate at frequencies from 4Mhz to 8Mhz and will deliver output frequencies of 500kHz to 128Mhz. The absolute minimum input frequency is 250Hz. You cannot, therefore, use it to blink an LED every second at 1Hz.
Like the Mode, you can set the Divider like so:
CTRA[25..23] := %111
Or like so:
CTRA := %111 << 23
However, if you set it like this, make sure you also set the Mode at the same time since you'll overwrite it with zeros otherwise!
CTRA := %111 << 23 ' Will reset Mode
CTRA := (%11111 << 26) | (%111 << 23) ' Will preserve Mode
Pin A and B
In some cases you don't need to set Pin A or B, but if you're using some of the LOGIC modes that the counter supports then you'll almost cerainly want to set at least one. A logic mode like A == B
will, for example only increment the value of PHSx
by adding to it the value of FRQx
if Pin A is equal to Pin B.
When choosing your pins, refer to the Counter Modes table to figure out which ones you need- the second counter mode involves no pin states at all, and neither do %10000
"never", or $11111
"always" which will either never increment PHSx
or increment it on every clock cycle respectively.
CTRA[5..0] := 0 ' Set pin A to 0
CTRA[14..9] := 1 ' Set pin B to 1
When using counter modes that output something to the pin, you must make sure that you configure that pin as an output using the direction register. This applies for each COG, they must have their pins configured as outputs individually:
DIRA[0] := 1 ' Set pin 0 to an output
DIRA[1] := 1 ' Set pin 1 to an output
If you fail to do this, your counter will run absolutely fine, but the output pin(s) will never change!
Putting it all together
Now we're going to assemble what we've learned into a working example- once again we'll use blinking LEDs since they're reliable and readily available.
This example will set up CTRA
and CTRB
with the same settings. This should cause our LEDs to both blink around once a second. Not exactly once a second, but close enough for a demonstration:
CON
_CLKMODE = xtal1 + pll16x
_XINFREQ = 6_000_000
LED_A = 0
LED_B = 1
LED_C = 2
{{
(2^32) / 96_000_000 = 44.7392426667
This means that adding 44 to PHSx every clock cycle
will toggle bit 31 approximately every 0.5 seconds
}}
PUB Main
DIRA[LED_A] := 1
DIRA[LED_B] := 1
DIRA[LED_C] := 1
PHSA := 0
PHSB := 0
' Mode Divider Pin B Pin A
CTRA := (%00100 << 26) + (%000 << 23) + (0 << 9) + LED_A
FRQA := 22
' Mode Divider Pin B Pin A
CTRB := (%00100 << 26) + (%000 << 23) + (0 << 9) + LED_B
FRQB := 22
repeat ' Actual 1sec blinking LED for comparison
OUTA[LED_C] := !OUTA[LED_C]
waitcnt(clkfreq + cnt)
So, what's going on here? Well, our clock frequency is 96Mhz, or 96,000,000. This happens to fit into a 32bit integer about 44 times. This means that if we add 44 to our PHSA
register every clock tick, it will overflow approximately once every second and loop back around.
Size of 32bit Clock Ticks/Second
(2^32) / 95_000_000 = 44.7392426667
Note that the actual number we need to accumilate every clock tick to overflow every second is 44.7392426667- we can't represent this floating point number in our 32bit FRQx
registers, so we just have to go with a whole number instead.
The actual LED, or output pin, is driven by bit 31 of PHSA
. This is the most significant bit, and it will be 0 during the first half of the accumilation, and 1 during the second. This means if you filled it at a rate of 44 per clock tick it would be 0 for about 0.5 seconds, and 1 for about 0.5 seconds giving us a blink frequency of 0.5 seconds.
To blink once every 1 second, we can divide our overflow rate by 2, 44 / 2 = 22
. This isn't very accurate, if you connect a third LED up to pin A2 you will see that the counter LED gets very slowly out of phase with the LED using waitcnt
.
Phase Shift
What if we wanted our LEDs to blink slightly out of phase with each other? The PHSx
register isn't called "phase" by accident! If you give it an initial value then it will place the counter out of phase with another counter using the same settings. Try setting PHSA
to CLKFREQ * 22
and see what happens!
Others Modes And Methods
Let's try another counter mode- differential $00101
- which will turn on either one pin, or the other. I'll also use this opportunity to demonstrate the alternative method of setting these registers:
CON
_CLKMODE = xtal1 + pll16x
_XINFREQ = 6_000_000
LED_A = 0
LED_B = 1
PUB Main
DIRA[LED_A] := 1
DIRA[LED_B] := 1
PHSA := 0
CTRA[30..26] := %00101 ' Mode
CTRA[5..0] := 0 ' Pin A
CTRA[14..9] := 1 ' Pin B
CTRA[25..23] := %000 ' Divider
FRQA := 45
repeat ' Keep COG alive
Further reading
Search above to find more great tutorials and guides.