PICAXE Macros

hippy

Technical Support
Staff member
Introduction

Macros are a useful tool which PICAXE Editor 6 supports. Macros are quite simple to use once familiar with them and how they work but many people will not be familiar with their use, not clear on how to use them, when or where to use them.

This document is intended to be a brief introduction to using macros from a PICAXE perspective.

If there are things which are not clear, or additional information would help, please post to this thread and I will address those and update the document as apropriate. Once it seems most things have been covered we can then consider making it a more formal part of the official PICAXE documentation.

A Handy Tip

When exploring how macros work it can be useful to see what the code actually written looks like once the macros have been expanded into final PICAXE Basic code.

Using PICAXE Editor 6 this can be done by choosing the "Options" menu, selecting the "Diagnostics" page, and ticking the "Display Pre-Processor Output" box under "Pre-Processor".

After clicking the "OK" button, whenever a Syntax Check is performed a Wordpad instance will appear which shows what the actual macro generated code the PICAXE compilers are seeing, what you macros expand to.

To prevent this output appearing, simply untick the "Display Pre-Processor Output" box.

If you are not entirely sure what your macros are generating it can be useful to turn the option on, Syntax Check again, and turn it off once happy with the results.

Reserved Posts

A couple of posts below the main posting have been reserved in case the document posts reach their size limit. Just ignore those for now, and maybe forever.
 
Last edited:

hippy

Technical Support
Staff member
The Basics

The main purpose of macros is to reduce the amount of typing needed in a program, to make that code more readable, more maintainable or more aesthetically pleasing.

Macros are basically substitutions; whatever is written within a macro is expanded in full whenever that macro is invoked, or "used". So -
Code:
#Macro SayHello
  SerTxd( "Hello", CR, LF )
#EndMacro

Do
  SayHello
Loop
Is exactly the same as -
Code:
Do
  SerTxd( "Hello", CR, LF )
Loop
Macro Parameters

Macros can have parameters specified so you can pass in values to be used when doing the macro expanded substitution so -
Code:
#Macro Say( text )
  SerTxd( text, CR, LF )
#EndMacro

Do
  Say( "Hello" )
  Say( "And Goodbye" )
Loop
Is exactly the same as -
Code:
Do
  SerTxd( "Hello", CR, LF )
  SerTxd( "And Goodbye", CR, LF )
Loop
Parameters do not have to be specified for macros, as in our first example above, but one or more parameters can be used for each macro. Those parameters can be variables, numbers or text strings, even expressions. Whatever is specified as a parameter will be substituted wherever the parameter name appears within the macro -
Code:
#Macro Assign( var, expression )
  Let var = expression
#EndMacro

Assign( w0, 1 * 2 + 3 )
Is the equivalent of -
Code:
Let w0 = 1 * 2 + 3
Note that although one can pass in expressions, that does not always mean expressions can be passed in if the macro is not written to expect them. For example -
Code:
#Macro PrintResult( expression )
  SerTxd( "Result = ", #expression )
#EndMacro

PrintResult( 1 * 2 + 3 )
Creates a Syntax error on the "SerTxd( "Result = ", #1 * 2 + 3 )" which the macro creates, because expressions cannot be used within a SERTXD command.

That can however be resolved by having the macro assign the expression to a variable which can then be used within a SERTXD statement, for example -
Code:
#Macro PrintResult( expression )
  w0 = expression
  SerTxd( "Result = ", #w0, CR, LF )
#EndMacro

PrintResult( 1 * 2 + 3 )
PrintResult( 99 )
PrintResult( w1 )
PrintResult( "A" )
Remember of course that if you assign or alter a variable within a macro it will change that variable's value outside the macro so you need to be careful in choosing which variables are used.

More Advanced Parameter Passing

As well as using variable names, numbers and text strings as parameters, even command names can be used -
Code:
#Macro SetOutput( command )
  command C.1
#EndMacro

SetOutput( High )
SetOutput( Low  )
Also subroutine or label names and conditions can be passed as parameters. For example -
Code:
#Macro Repeat( subroutineName, var, condition )
  Do
    Gosub subroutineName
    var = var + 1
  Loop While condition
#EndMacro

Repeat( PrintValueOfW0, w0, w0 < 10 )
Is the equivalent of -
Code:
Do
  Gosub PrintValueOfW0
  w0 = w0 + 1
Loop While w0 < 10
As with passing in an expression earlier; the parameters specified have to match how those parameters are used within the macro. If they are not then a Syntax Error will be generated.

Macros Without Parameters

When a macro does not have any parameters it can be specified in one of two ways, with or without parenthesis, round brackets -
Code:
#Macro MyFirstMacro()
#Macro MyOtherMacro
When a macro without parameters is invoked however it must always be without parenthesis -
Code:
MyFirstMacro
MyOtherMacro
If the parenthesis are included that will generate a Syntax Error.

Parameter Naming

Parameter names can be whatever one wishes to use. However care should be taken in choice of names because the parameter name will override any other use of that name elsewhere. For example -
Code:
#Macro Assign( b10, equals, b11 )
  b10 = b11
#EndMacro

b10 = 10
b11 = 11
b12 = 12
b13 = 13
Assign( b12 , = , b13 )
SerTxd( "b10 = ", #b10, CR, LF )
SerTxd( "b11 = ", #b11, CR, LF )
SerTxd( "b12 = ", #b12, CR, LF )
SerTxd( "b13 = ", #b13, CR, LF )
This does not set the value of variable 'b10' to the value of variable 'b11' as the macro may at first glance appears to.

In fact 'b10' and 'b11' are simply substituted with whatever is passed into those parameters when the macro was invoked.

This can be prove particularly problematic and difficult to debug when actual variable names ( not used as parameter names ) are also used within the macro or where a parameter name may override a SYMBOL defined name.

It is advisable not to use variable names or any globally defined SYMBOL names as parameter names. If there does appear to be an issue it is always a good idea to look at the code the macro is expanding to as described earlier.

Care should also be taken not to give parameter names which can be misinterpreted. In our earlier example we had a "PrintResult(expression)" macro where 'expression' was used in a SERTXD command which isn't allowed. The 'expression' name gave the impression that an expression could be used where that was not the case.

It would have been better to have named the parameter as 'variableOrNumber' which reflected what could be used.

Unused Parameters

It is possible to include parameters in a macro definition which are not actually used within the macro itself.

It is not common to have unused parameters, or "dummy parameters" as they are also called, but they can sometimes be handy where one wishes to include something within the macro rather than in a comment along side it. For example -
Code:
#Macro Fibonacci( n, dummy )
  w0 = 1
  For w1 = 1 To n
    w0 = w0 * w1 
  Next
  SerTxd( #n, "! = ", #w0, CR, LF )
#EndMacro

Fibonacci( 3 , "The answer should be 6" )
Fibonacci( 5 , "And this should be 120" )

Fibonacci( 3 , 3*2*1     =   6 )
Fibonacci( 5 , 5*4*3*2*1 = 120 )
Or perhaps -
Code:
#Macro Assign( var, equals, expression )
  var = expression
#EndMacro

Assign( w0 , = , w1 + 1 )
Unused parameters are completely ignored, do not appear in any macro expansion, and do not use up any program memory.

Macros Are Not Subroutines

While macros can be used like subroutines it is important to note they are not subroutines, and macros are not a replacement for subroutines.

Whether to use macros or subroutines is a question which can arise and there is no simple or single answer. It very much depends on what you are doing, hoping to achieve, and what the macro or subroutine would be.

Perhaps the best way to think of it is that subroutines allow for common functionality to be handled in a single place, while macros allows code to be more cleanly written.

The main thing to remember is that macros will substitute all they contain whenever the macro is invoked. This means that complicated macros can generate a huge amount of code when they are used. This obviously becomes greater the more frequently those macros are invoked within the code.

It is usually desirable to keep macro generated code as short as possible, which can be achieved by moving common code into a subroutine as one normally would when not using macros.

It is entirely possible to use both macros and subroutines, have macros which make code easier to write which call subroutines to do the work required. For example -
Code:
#Macro DoSomethingComplex( actionNumber )
  b0 = actionNumber : SerTxd( "Doing ", #b0 ) : Gosub DoIt
#EndMacro

DoSomethingComplex( ACTION_1 )
DoSomethingComplex( ACTION_2 )
Is exactly equivalent to -
Code:
b0 = ACTION_1 : SerTxd( "Doing ", #b0 ) : Gosub DoIt
b0 = ACTION_2 : SerTxd( "Doing ", #b0 ) : Gosub DoIt
In the above example it would make sense to move the "SerTxd("Doing ",#b0)" command into the "DoIt:" subroutine, so there is only one SERTXD in the code which is in that subroutine, rather a SERTXD being generated every time the macro is invoked.

Labels Within Macros

It is possible to include labels within macros -
Code:
#Macro WaitForCommand
  Waiting:
    SerRxd #w0
    If w0 = 0 Or w0 > 9 Then Waiting
#EndMacro

Do
  WaitForCommand
  SerTxd( "Command was ", #w0, CR, LF )
Loop
The huge caveat there is; if the macro is is used twice it will generate two labels with the "Waiting:" name which will be reported as a syntax error.

It is however possible to pass label names into a macro so if one wanted to use the above macro it could be rewritten as -
Code:
#Macro WaitForCommand( labelName )
  labelName:
    SerRxd #w0
    If w0 = 0 Or w0 > 9 Then labelName
#EndMacro

Do
  WaitForCommand( Wait1 )
  SerTxd( "First command was ", #w0, CR, LF )
  WaitForCommand( Wait2 )
  SerTxd( "Second command was ", #w0, CR, LF )
Loop
 
Last edited:

lbenson

Senior Member
A very valuable use of a macro to do something not otherwise easily done (move a string into ram) is shown in hippy's code in post 28 of this thread, for instance, PokeString(80, "This is a string"):
https://picaxeforum.co.uk/threads/moving-ram-to-ram.29696/#post-307465
Code:
#Macro PokeString( toLoc, string )
  bPtr = toLoc
  b0 = 0
  Do
    Lookup b0, ( string, 0 ), b1
    If b1 <> 0 Then
      @bPtrInc = b1
      b0 = b0 + 1
   End If
   Loop Until b1 = 0
#EndMacro

PokeString( 10, "Some" )
This relies on the fact the string ("This is a string") will be terminated with a zero. DO loop in conjunction with LOOKUP walks through the passed string character by character placing the string in ram at the location specified. A similar macro using PTRINC instead of BPTRINC will (on X2 picaxes) place the string in the scratchpad.

This is the equivalent of a possible variation of the POKE command which is not implemented: POKE 80, "This is a string".
 
Last edited:

Circuit

Senior Member
Hippy, that is a most welcome document - I have already reformatted it into an eight page .PDF and printed it as an A5 booklet, stapled and sitting on my desk ready for careful reading and further thought. Thank you for this excellent narrative. It would be excellent to see an update of the PICAXE Manual with this - and many other subjects relating to the new features of Editor 6 - incorporated.
 

hippy

Technical Support
Staff member
There is one other gotcha waiting for the unwary when using macros, as I found out here :

https://picaxeforum.co.uk/threads/if-then-syntax-error-limits-macro-usage.29354/
Good point. The IF and other commands only accept a condition in the form -

<var> <comparison> <var or number>

So, end up with the lefthand side of the comparison as a number and that's a Syntax error. That's similar to the example of passing in an expression where an expression isn't allowed in the command it gets used in, and the solution is the same; move what might be a number into a variable then compare that -

Code:
#Macro IfCondition( lhs, comparison, rhs, label )
  tmpVar = lhs
  If tmpVar comparison rhs Then label
#EndMacro

IfCondition( w0 , <, 10, Below10 )
IfCondition( 20 , >, w1, Okay)
 
Top