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:

Propeller Counter CTRx Control Modes

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

That's all folks!

Search above to find more great tutorials and guides.

Plasma 2040

Swathe everything in rainbows with this all-in-one, USB-C powered controller for WS2812/Neopixel and APA102/Dotstar addressable LED strip.