will #macro cause stack probs

johnlong

Senior Member
Hi All
Working on the following code for a remote sensor unit link up to confirm 1 if the data between the two units is being sent correctly and 2 the sensors on the remote are working correctly.
I have as the following code will show used a number selection to inact the sub routines in the remote unit and a macro to determine action.
My concern is will I evently get a stack problem with the macro directing the responce to either obtain or option.
If so would it then be better to use GOTO rather than GOSUB
Code:
#picaxe 28x2
#no_data
#no_table

 Symbol Qualifier= 254 
symbol fail=b25
symbol want=b26
symbol lamp=w7
symbol dry=w8
symbol rh=w9

    '#rem
    '***************Remote unit routine****************
#MACRO Talk_Talk
    if w5=w6 then:fail=0:serout B.0,T9600,("Y"):goto obtain:endif
    if w5<>w6:then:if fail<2 then:goto option:else:serout B.0,T9600,("N"):fail=0:goto obtain:endif:endif 
    #endm
    hintsetup %00000100
    
    
    pause 1000
   main:
       
       enablebod 'low power
     sleep 0 'woken up via pin B.2 attached to AUX on DRF sets hint and hint2 flags
     disablebod 'power up
    Serin B.1, T9600,(Qualifier,"Hello") 'call from master DRF unit
        if hint2flag=1 then
        high B.7
        pause 50
        serout B.0,T9600,(Qualifier,"ACK")
        gosub obtain
        endif
    goto main
    end
obtain:
        serin B.1,T9600,want 'b5 from master
        if want=$FF then 'end data request from master
            goto main
        endif 
    
option:

        select case want
    case 1 gosub light
    case 2 gosub temp
    case 3 gosub rhhum
    end select 
    
    
    light:
    w5=123
     serout B.0,T9600,(w5)
     pause 20
      serin B.1,T9600,w6
      if w5<>w6 then
          fail=fail+1
    endif
      Talk_Talk   'macro
     return 
     temp:
     w5=25
     serout B.0,T9600,(w5)
     pause 20
      serin B.1,T9600,w6
      if w5<>w6 then
          fail=fail+1
    endif 
    Talk_Talk   'macro
     return 
     rhhum:
     w5=75
     serout B.0,T9600,(w5)
     pause 20
      serin B.1,T9600,w6
      if w5<>w6 then
          fail=fail+1
    endif 
    Talk_Talk   'macro
     return
     
     '#endrem
     '*******************Master call to remote routine**********************
    #rem
    
    
    pause 1000
      MAIN:
  low B.7
   sertxd("b5 ",#b5,cr,lf)
   recall:
    serout A.1,T9600,(85,85,85,85,Qualifier,"Hello")
    pause 20
    serin[3000,recall], A.2,T9600,(Qualifier,"ACK")
    high B.7
    
      'call to measure light levels
        b5=1
    serout A.1,T9600,(b5)'tell remote unit which reading to undertake
     serin A.2,T9600,lamp 'result from remote unit
     pause 50
     serout A.1,T9600,(lamp) 'bounce result back to check for errors
     pause 50
     serin A.2,T9600,b0 'conformation or error
     if b0="N" then
        sertxd("light sensor error",cr,lf)
    endif 
    sertxd("lamp ",#lamp," b0 ",#b0," b5 ",#b5,cr,lf)
     'call to measure tempreture
     b5=2
    serout A.1,T9600,(b5)'tell remote which reading to undertake
     serin A.2,T9600,dry
     pause 10
     serout A.1,T9600,(dry)
     pause 10
     serin A.2,T9600,b0
     
     if b0="N" then
        serout B.1,N9600,("tempreture sensor error")
    endif 
    sertxd("dry ",#dry," b0 ",#b0," b5 ",#b5,cr,lf)
     'call to measure humidity
     b5=3
    serout A.1,T9600,(b5)'tell remote which reading to undertake
     serin A.2,T9600,rh
     pause 10
     serout A.1,T9600,(rh)
     pause 10
     serin A.2,T9600,b0
     
     if b0="N" then
        serout B.1,N9600,("humidity sensor error")
    endif 
    pause 100
    sertxd("rh ",#rh," b0 ",#b0," b5 ",#b5,cr,lf) 
    pause 100
      serout A.1,T9600,($FF) 
    for b10= 1 to 30
       pause 1000
    next b10
       goto main
    
none:
    serout B.1,N9600,("sensor unit failed to read")
    goto main
    #endrem
regards john
 

Technical

Technical Support
Staff member
To answer this question think "what will happen if I cut and paste the macro into the main code' - as that is what the preprocessor basically does.

So yes, if there is a chance you can 'goto' out of a gosub and hence miss the matching return, then runtime errors will occur.
 

inglewoodpete

Senior Member
It is fine to use a GoSub in macro code but, put simply, you should never use a GoTo to exit a subroutine. Those rules apply regardless of whether you use macros.

I use GoSub commands in macros but I practically never use GoTos anywhere, any time. GoTos breed bad code structure techniques and, as a result, make debugging code difficult. (I was a professional software developer in a past life.)

Getting down from my soap box, the GoTo is a legitimate command in many programming languages and I accept that many people still use it.
 

techElder

Well-known member
I will use GOTO within a block of code (such as a subroutine) but never will I allow a GOTO to exit that block of code. Typically, that means I use it as a way to branch to a LABEL from an IF/THEN statement.
 

hippy

Technical Support
Staff member
The main mesage is; that GOTO's are okay so long as they are not used to leave a GOSUB. Leaving a GOSUB must always be via a RETURN or unpredictable things and odd program behaviour can happen.

GOSUB's are absolutely fine within MACRO's; GOTO may be less so.

The danger with MACRO's and GOTO is that it's not always easy to tell that a GOTO is to within or outside any GOSUB it may be used in. One way to overcome that is to pass the label into the MACRO itself as in this example -

Code:
#Macro CheckVar(var,errorLabel)
  If w0 > 10 Then 
    Goto errorLabel
  End If
#EndMacro

MainProgram:
  Do
    Gosub IncW0
  Loop

IncW0:
  w0 = w0 + 1
  CheckVar(w0,ErrW0)
  Return
ErrW0:
  SerTxd( "W0 too big = ", #w0, CR, LF )
  Return
If one added another GOSUB, say an "incW1" routine, one could then reuse the "CheckVar" macro with a jump to "ErrW1".
 

Aries

New Member
I also use GOTOs within subroutines (my first programming language was Univac Assembler, which had jumps, but no structured IF/THENs). One of the ways I use them is to allow multiple ENTRY POINTS into the same routine, with a common return. Trivial example:

Code:
DivideBy10:
         b0 = 10
         goto CommonDivide
DivideBy16:
         b0 = 16
CommonDivide:
         w1 = w1 / b0
         return
 

hippy

Technical Support
Staff member
This mkles no sense in BASIC :eek:
Aries' code I presume ?

In a perfect purist's world that would be coded as -

Code:
DivideBy10:
         b0 = 10
         gosub CommonDivide
         return

DivideBy16:
         b0 = 16
         gosub CommonDivide
         return

CommonDivide:
         w1 = w1 / b0
         return
Turning the first GOSUB plus RETURN into a GOTO and removing the second, and relying on program execution drop-through, are optimisations I wouldn't be upset by. I have used similar myself.

It's more an issue where the code may be further apart and it's not clear what the optimisation may be and program flow gets lost. One solution for that can be to use fake #IFDEF Commands -

Code:
DivideBy10:
         b0 = 10
         #IfDef NOT_OPTIMISED
             gosub CommonDivide
             return
        #Else
             goto CommonDivide
        #EndIf 

DivideBy16:
         b0 = 16
         #IfDef NOT_OPTIMISED
             gosub CommonDivide
             return
        #Else
            ; Fall-through to CommonDivide
        #EndIf 

CommonDivide:
         w1 = w1 / b0
         return
 

johnlong

Senior Member
Hi All
Thank you for all your comments, I have managed to remove all the GOTO's from the code
as suggested by Inglewoodpete. And replaced them with sucssetion of do loops (when I first joined this forum Besquest
told me to avoid using gotos )
Thanks Technical I had it in mind that a #Macro was just a gloryfied gosub not an insertion into the code at the point of call, for me the manual is a bit vauge on it
Well Hippy you have managed to baffle me once again but boy does it make me work harder whats he done why invaluable.
Sorry all for opening "the gosub goto can of worms" again I dont think we will ever get a derfinative answer to it as its down to personal prefrence
previous code experiances diffrent interfacing (I'm luck only picaxe basic enough for my brain)
The following is linked up to the Dorji units and thumping away nicely, know for the hard work interfacing the HTU21D-F
using Phil Hornsbys HTU21D_DEMO code using it hard wired time for a bit of distance between units
Code:
#picaxe 28x2
#no_data
#no_table

 Symbol Qualifier= 254 
symbol fail=b25
symbol want=b26
symbol lamp=w7
symbol dry=w8
symbol rh=w9

    '#rem
    '***************Remote unit routine****************
#MACRO Talk_Talk(var,var1)
    if var=var1 then:fail=0:serout B.0,T9600,("Y"):endif
    if var<>var1:then:if fail=2 then:serout B.0,T9600,("N"):fail=0:endif:endif
    #endm
    hintsetup %00000100
    
    
    pause 1000
   main:
       do
       enablebod 'low power
     sleep 0 'woken up via pin B.2 attached to AUX on DRF sets hint and hint2 flags
     disablebod 'power up
    Serin B.1, T9600,(Qualifier,"Hello") 'call from master DRF unit
        if hint2flag=1 then
        high B.7
        pause 50
        serout B.0,T9600,(Qualifier,"ACK")
        gosub obtain
    endif
    loop
obtain:    do 
        serin B.1,T9600,want 'b5 from master
        if want="z" then
            exit:endif
        do 
        option:
            sertxd("case want ",#want,cr,lf)
        select case want
        case 1
    w5=123
    do
     serout B.0,T9600,(w5)
     pause 20
      serin B.1,T9600,w6
      if w5<>w6 then
          fail=fail+1
    endif
      Talk_Talk(w5,w6)   'macro controlls readings loops sends release to master for next read
      sertxd(" case light ","fail ",#fail," w6 ",#w6," w5 ",#w5,cr,lf)
      want=0
      loop until fail=0
      
    case 2
     w5=25
     do
     serout B.0,T9600,(w5)
     pause 20
      serin B.1,T9600,w6
      if w5<>w6 then
          fail=fail+1
    endif 
    Talk_Talk(w5,w6)  'macro
    sertxd(" case temp ","fail ",#fail," w6 ",#w6," w5 ",#w5,cr,lf)
    want=0
    loop until fail=0
    
    case 3
    do
     w5=75      
     serout B.0,T9600,(w5) 'reading sent
     pause 20
      serin B.1,T9600,w6 'reading result sent back
      if w5<>w6 then 'compare results
          fail=fail+1
    endif 
    Talk_Talk(w5,w6)   'macro decieds action
    sertxd(" case hum ","fail ",#fail," w6 ",#w6," w5 ",#w5,cr,lf)
    want=0 'release value for secondary loop to obtain next reading command
    loop until fail=0 'inner loop release
    end select
    loop until want=0
    sertxd("end of sensor loop ","want ",#want,cr,lf)
    pause 100
    
    loop 
    low B.7
    return
     
     '#endrem
     '*******************Master call to remote routine**********************
    '#rem
    
    
    pause 1000
      MAIN:
  low B.7
   sertxd("b5 ",#b5,cr,lf)
   recall:
    serout A.1,T9600,(85,85,85,85,Qualifier,"Hello")
    pause 20
    serin[3000,recall], A.2,T9600,(Qualifier,"ACK")
    high B.7
      'call to measure light levels
        b5=1
    serout A.1,T9600,(b5)'tell remote unit which reading to undertake
     serin A.2,T9600,lamp 'result from remote unit
     pause 50
     serout A.1,T9600,(lamp) 'bounce result back to check for errors
     pause 50
     serin A.2,T9600,b0 'master waits until conformation of value or error from slave
     if b0="N" then
        sertxd("light sensor error",cr,lf)
    endif
    
    sertxd("lamp ",#lamp," b0 ",#b0," b5 ",#b5,cr,lf)
     'call to measure tempreture
     b5=2
    serout A.1,T9600,(b5)'tell remote which reading to undertake
     serin A.2,T9600,dry
     pause 10
     serout A.1,T9600,(dry)
     pause 10
     serin A.2,T9600,b0
     if b0="N" then
        serout B.1,N9600,("tempreture sensor error")
    endif 
    sertxd("dry ",#dry," b0 ",#b0," b5 ",#b5,cr,lf)
     'call to measure humidity
     b5=3
    serout A.1,T9600,(b5)'tell remote which reading to undertake
     serin A.2,T9600,rh
     pause 10
     serout A.1,T9600,(rh)
     pause 10
     serin A.2,T9600,b0
     if b0="N" then
        serout B.1,N9600,("humidity sensor error")
    endif 
    pause 100
    b6="z"
    serout A.1,T9600,(b6)'w5 end loop in slave return to main
    pause 100
    sertxd("rh ",#rh," b0 ",#b0," b5 ",#b5,cr,lf) 
    pause 100
      serout A.1,T9600,($00) 
    for b10= 1 to 30
       pause 1000
    next b10
       goto main
    
none: 'for future development
    serout B.1,N9600,("sensor unit failed to read")
    goto main
   ' #endrem
If anybody knows of any good posted explinations or use of macros would you mind directing me to them (baby steps apprechiated)
regards john
 

hippy

Technical Support
Staff member
If anybody knows of any good posted explinations or use of macros would you mind directing me to them (baby steps apprechiated)
I think you probably already have a good grip on using MACRO even if you don't think so, probably just some loose ends to tidy up.

There is a fair amount on the forum describing how to use macros but it is a bit 'all over the place' so I have started a thread where macro related material can be collated. As that develops over time it can perhaps be given a place in the offical documentation -

https://picaxeforum.co.uk/threads/picaxe-macros.31134

As to when to use macros and how best to replace existing code with macros that can be more difficult to answer, as is what those macros should be.

Subroutines are used to provide common code so it doesn't need to be repeated. So to show hex values of a word variable it would be appropriate to have the hex number to be reported placed in 'w0' and have a routine which reported that. For example, 155 bytes on an 08M2 -
Code:
w10 = $1234
w11 = $2345
w12 = $3456

w0 = w10 : SerTxd( "w10 = ", #w0, " = $" )  : Gosub HexWord
w0 = w11 : SerTxd( "w11 = ", #w0, " = $" )  : Gosub HexWord
w0 = w12 : SerTxd( "w12 = ", #w0, " = $" )  : Gosub HexWord
End

HexWord:
  b2 = b1 / $10 : Gosub HexNibble
  b2 = b1       : Gosub HexNibble
  b2 = b0 / $10 : Gosub HexNibble
  b2 = b0       : Gosub HexNibble
  SerTxd( CR, LF )
  Return

HexNibble:
  b2 = b2 & $0F + "0"
  If b2 > "9" Then : b2 = b2 + 7 : End If
  SerTxd( b2 )
  Return
It doesn't make sense to create a macro which does everything the subroutine does when it's going to be used three times here - that just uses more program memory than necessary, but it does make sense to turn the calling code into a macro so it's more readable, so -
Code:
w0 = w10 : SerTxd( "w10 = ", #w0, " = $" )  : Gosub HexWord
w0 = w11 : SerTxd( "w11 = ", #w0, " = $" )  : Gosub HexWord
w0 = w12 : SerTxd( "w12 = ", #w0, " = $" )  : Gosub HexWord
Becomes, still 155 bytes -
Code:
#Macro ShowAsHex( varName, wordVar )
  w0 = wordVar
  SerTxd( varName, " = ", #w0, " = $" )
  Gosub HexWord
#EndMacro

ShowAsHex( "w10", w10 )
ShowAsHex( "w11", w11 )
ShowAsHex( "w12", w12 )
And if one were looking to then optimise that it would be worth changing to, 133 bytes -
Code:
#Macro ShowAsHex( varName, wordVar )
  w0 = wordVar
  SerTxd( varName )
  Gosub HexWord
#EndMacro

HexWord:
  SerTxd( " = ", #w0, " = $" )
  ...
Or even, 124 bytes -
Code:
#Macro ShowAsHex( wVarNumber, wordVar )
  w0 = wordVar
  b2 = wVarNumber
  Gosub HexWord
#EndMacro

ShowAsHex( 10, w10 )
ShowAsHex( 11, w11 )
ShowAsHex( 12, w12 )
End

HexWord:
  SerTxd( "w", #b2, " = ", #w0, " = $" )
  ...
Arguably the last is less readble or understandable than its previous. I would agree with that. What is best will depend on what one is seeking; easy understanding of code or maximally optimised code. Those are often at odds with each other.
 

johnlong

Senior Member
Hi Hippy
Have read your Macro post sterling work dispells alot of the mystery around macro's
Like you say if the macro masters contribute a good insite into macro's and there usewould be of invaluable to
other forum members. It would be good to keep the thread clean with any questions arising from it posted as new posts, this way it
would be more readable and on point
regards john
 
Top