Parallax Propeller Book - Spin Program Tricks

In this Chapter we will discuss some simple but very useful Spin programming "tricks" that can be incorporated into many Objects.

Endless Loop

When passing parameters to many Methods, quite often, especially when testing we only want to loop around certain number of times, just to verify that the code functions properly.

However, once the code is fully developed we may want to loop that method indefinitely, without the need to rewrite the tested code.

This programming trick will allow for a loop to "loop endlessly" if the passed parameter is ZERO for the number of repetitions.

For this example we will modify the original object: BlinkMRXmethod.spin


PUB Blink (port, rate, reps)                       ' Define parameters used in Blink
  dira[port]~~                                     ' Set [port] to output
  outa[port]~                                      ' Set [post] LO
  repeat reps * 2               ' Set loop to number of repetitions (reps * 2)
    waitcnt(rate/2 + cnt)                          ' Wait for rate/2
    !outa[port]                                    ' Toggle [port]

First we will move the reps * 2 from the repeat statement to a new line above repeat .

Next we will add another statement at the end of the loop:

while reps := --reps #> -1


'' BlinkMRXmethod0.spin
PUB Blink (port, rate, reps)                       ' Define parameters used in Blink
  dira[port]~~                                     ' Set [port] to output
  outa[port]~                                      ' Set [post] LO
  reps := reps * 2                         ' double the repetition rate
  repeat                                   ' repeat code below
    waitcnt(rate/2 + cnt)                          ' Wait for rate/2
    !outa[port]                                    ' Toggle [port]
  while reps := --reps #> -1             'While not 0 (make min -1)

Remember we said we’d like to change PUB Blink to allow for an infinite loop as well as a finite loop?

Our code change achieved that in a very clever way.

The reps parameter is the number of times to toggle the port.

That means it doesn’t make much sense to set reps equal to 0… who would want to toggle a port zero times?

So, we’ll make 0 an exception case, that will mean: “toggle the port infinitely.”

We changed the loop from repeat reps * 2 to repeat..while.

The while is at the end of the loop, three lines below repeat.

This is another form of REPEAT loop structure called a “conditional one-to-many loop.”

It executes the statement block within it at least once, and iterates again and again as long as the “while” condition is true.

In this case it repeats while reps := --reps #> -1 is TRUE (ie: non-zero).

This condition is another compound expression.

The double-minus, ‘--‘ preceding reps is the Pre-Decrement operator; it decrements reps by 1, before its value is used by the expression.

The #> is the Limit Minimum operator; it takes the value on its left and returns either that value, or the number on its right, whichever is greater.

So each time this expression is evaluated, reps is decremented by 1, that result is limited to -1 or higher, and that final result is assigned back into reps.

This has a clever effect that we’ll explain next.

If Blink was called with reps set to 2, the loop would execute two times, just like we want.

After the first iteration, the while reps := -- reps #> -1 would decrement reps, making it 1, then would limit it to -1 or higher (still 1) and store that value in reps.

Since the result, 1, is non-zero (TRUE) the loop would execute again.

After the second iteration, the WHILE statement would decrement reps, making it 0, would limit that to -1 or higher (still 0) and store that in reps.

Since 0 is FALSE, the WHILE condition terminates the loop.

That works for all normal reps values, but what about when Toggle is called with a reps of 0?

After the first iteration, the while reps := -- reps #> -1 would decrement reps, making it -1, then would limit it to -1 or higher (still -1) and store that value in reps.

Since the result, -1, is non-zero (TRUE) the loop would execute again.

After the second iteration, the WHILE statement decrements reps, making it -2, limits that to -1 or higher (it is changed to -1) and stores that in reps.

Once again, since the result, -1, is non-zero (TRUE) the loop would execute again.

So, if reps started out as 0, the loop iterates endlessly!

And if reps started out as greater than 0, it loops only for that number of times!


  1. Load the CallBlinkMRXmethod.spin into the Propeller Tool.
  2. Change the OBJ definition from Blinker: "BlinkMRXmethod" to Blinker: "BlinkMRXmethod0" .
  3. Change the CON definition from repsC = 9 to repsC = 0.
  4. Load and Run the application and verify that after the push-button press the P26 LED toggles "forever".


'' CallBlinkMRXmethod0.spin
CON

  _clkmode = xtal1 + pll16x         ' System clock → 80 MHz
  _xinfreq = 5_000_000              ' External Crystal frequency 5Mhz

  ledA = 27                         ' assign port for monitor LED
  button = 10                       ' assign port for push button

  ledB = 26                         ' assign port for ledB
  rateC = (80_000_000/3)            ' assign rate constant (number of clock ticks) 3Hz
  repsC = 0                         ' assign reps constant (repetitions)
                                    ' ZERO repsC = repeat indefinitely                              

OBJ
  Blinker   :       "BlinkMRXmethod0"            ' assign alias to called object
  
PUB Main
  repeat
    outa[ledA] := dira[ledA] := 1                 ' Set [ledA] to OUTPUT HI
    repeat until ina[button]                      ' Wait untill [button] is pressed
    outa[ledA] := 0                               ' Set [ledA] LO
    Blinker.Blink(ledB, rateC, repsC)             ' Call Blink method in Bliker alias = 								     '	"BlinkMRXmethod0" and pass parameters
    waitcnt(clkfreq/2*3 + cnt)                    ' Wait before repeating loop 1.5 seconds
    

Pseudo-Real Numbers

The Propeller is a 32-bit device and can naturally handle whole numbers as signed integers (-2,147,483,648 to 2,147,483,647) both in constants or in run-time math expressions.

However, for real numbers (those with both integer and fraction components) the compiler supports floating-point format (single-precision, IEEE-754 compliant) for constants, and there are library objects that allow for run-time floating-point math operations.

For handling real numbers, there are many possible techniques.

One technique is to use integer math in a way that accommodates your real values as well as the run-time expressions involved.

We call this pseudo-real numbers.

Having 32-bit integers built in to the Propeller provides us with a lot of “elbow room” for calculations.

For example, perhaps we have an equation to multiply and divide values that have 2-digit fractions, like the following:

A = B * C / D

For our example, let’s use A = 7.6 * 38.75 / 12.5 which evaluates to 23.56.

To solve this at run time, we can adjust all the equation’s values upward by 2 digits to make them all integers, perform the math and then treat the rightmost 2 digits of the result as being the fractional portion.

Multiplying each value by 100 achieves this.

Here’s the algebraic proof:

A = (B* 100) * (C * 100) / (D * 100)

A = (7.6 * 100) * (38.75 * 100) / (12.5 * 100)

A = 760 * 3875 / 1250

A = 2356

Since we multiplied all the original values by 100, we know that the final value is really 2356/100=23.56, but for most purposes we can keep it in integer form knowing that the rightmost two digits are really to the right of the decimal point.

The above solution works as long as each of the original values and each of the intermediate results never exceed the signed integer boundaries:

-2,147,483,648 to 2,147,483,647.


Floating-Point Numbers

In many cases, expressions involving real numbers can be solved without using floating-point values and methods, such as with the pseudo-real number technique.

Since solutions like the one above tend to execute much faster and consume less memory, it is recommended that you think carefully about whether or not you really need floating-point support before you actually use it.

If you can afford the extra execution time and memory usage, floating-point support may be the best solution.

The Propeller Tool supports floating-point constants directly.

The Propeller chip supports floating-point run-time expressions through the use of objects; ie: at run time the Spin Interpreter can only directly process integer-based expressions.


The example presented next includes code that uses both the pseudo-real number technique as well as floating-point numbers.


'' RealNumbersPST.spin   
CON
  _clkmode = xtal1 + pll16x     'Set clock mode to 80Mhz (5*16) 
  _xinfreq = 5_000_000          'external crystal 5Mhz   

  iB    =  760                  'Integer constants
  iC    = 3875
  iD    = 1250
  
  B     =  7.6                  'Floating-point constants
  C     = 38.75
  D     = 12.5

  K     = 100.0                 'Real-to-Pseudo-Real multiplier

OBJ
  pst   :       "Parallax Serial Terminal" 
  F     :       "FloatMath"
  FS    :       "FloatString"

PUB Math         
  ''Send test messages to Parallax Serial Terminal.
  pst.Start(115_200)              'Start PST and set communication speed

  waitcnt(clkfreq*4 + cnt)        'wait for program load and give time to Enable PST
  pst.Clear                                         
  waitcnt(clkfreq + cnt)          'wait for one second
  
  {Integer constants (real numbers * 100) to do fast integer math}
  pst.Str(string("Pseudo-Real Number Result: "))
  pst.Dec(iB*iC/iD)

  {Floating-point constants using FloatMath and FloatString objects}
  pst.NewLine       
  pst.Str(string("Floating-Point Number Result: "))
  pst.Str(FS.FloatToString(F.FDiv(F.FMul(B, C), D)))

  {Floating-point constants translated to pseudo-real for fast math}
  pst.NewLine       
  pst.Str(string("Another Pseudo-Real Number Result: "))
  pst.Dec(trunc(B*K)*trunc(C*K)/trunc(D*K))

Compile and download RealNumbersPST.spin.

It will display the following on a PST display:

Pseudo-Real Number Result: 2356

Floating-Point Number Result: 23.56

Another Pseudo-Real Number Result: 2356


The pseudo-real results, of course, each represent the value 23.56 but the entire value is shifted upwards by two digits to maintain integer math compatibility.

With some additional code we could output it as 23.56 for display purposes.

The constants iB, iC, and iD are standard integer constants as we have seen before, but their values are really pseudo-real numbers representing the values in our example equation.

The constants B, C, D, and K, are floating-point constants (real numbers).

The compiler automatically recognizes them as such and stores them in 32-bit single-precision floating-point format.

They can be used in other compile time floating-point expressions directly but at run time they should only be used with floating-point methods such as those found in the FloatMath and FloatString objects.

The statement Term.Dec(iB*iC/iD) uses the pre-translated pseudo-real constants as suggested by the Pseudo-Real Numbers technique, above.

This is evaluated about 1.6 times faster than with the floating-point technique and takes much less code space.

The statement Term.Str(FS.FloatToString(F.FDiv(F.FMul(B, C), D))) calls FloatMath’s FMul method to multiply the floating-point values B and C, then calls FloatMath’s FDiv method to divide that result by the floating-point value D, translates the result to a string using FloatString’s FloatToString method and displays that on the PST.

The statement Term.Dec(trunc(B*K)*trunc(C*K)/trunc(D*K)) uses compile time expressions inside of TRUNC directives to shift the floating-point constants B, C, and D upwards by two digits and truncate the values to integers.

The resulting expression is equivalent to that of the first pseudo-real number equation Term.Dec(iB*iC/iD) but has the added benefit of allowing its component values to be defined in floating-point terms.

The TRUNC directive truncates fully resolved floating-point expressions to their integer form at compile time.

It is required here since floating-point constant values can not be used directly by run-time expressions.


Under Construction
Currently this page is still under development, so please check back periodically for new links to pages as we add them to this list.

Links to related Webpages

Click the link in the list below to navigate to a detailed webpage about the listed subject.


Parallax Propeller Book - Table of Content