Writing Maintainable Picaxe Code

Tom2000

Senior Member
The Problem with Picaxe BASIC

When writing a large microcontroller program, it's very important that you write your code as clearly as possible. You should be able to figure out, without a great deal of effort, exactly what it's supposed to do, and precisely how it's supposed to do it. If not, you won't be able to maintain your program, and will invariably introduce bugs that are hard to find.

The problem is particularly acute when you go back to one of your own programs a year after you've written it, or if you try to figure out how someone else's program works.

Picaxe BASIC, as it stands, isn't suited for large programs. Not even one little bit. With processors having limited program memory, this usually doesn't create insurmountable problems; you won't be writing large programs. But with the new breed supporting programs of several thousand lines, readability and maintainability become important issues.

So, when I write a large micro program, I invariably choose the C language. C, like most modern high-level languages, is well suited for large programs for a number of reasons. In part, because it supports long, descriptive function ("subroutine") and variable names. Also, it supports parameter passing and local variables in its functions. (And, for microcontroller purposes, C is a good language because it generates fairly compact and efficient machine code, as does Picaxe BASIC.)

Local variables are particularly important. A local variable is used solely within its function, and isn't visible to the rest of the program. A local variable may even share the name as another local does in another function, but these locals are completely independent of one another. When programming a Picaxe, however, no local variables means that it's just too easy to inadvertently change the value of a variable used elsewhere in your program.

These (and other) features contribute to C's ease of programming, readability, and maintainability.

Here's an example of a C function that takes a parameter at call ("mode") and uses a local variable ("wrk"):

Code:
void ShowBusVoltage(byte mode)
{

  //  Display the power bus voltage for 47k + 10k voltage divider, 
  //  5.0 v Aref

  long wrk;
  
  HideCurs();
  Clear();
  SendStr(PwrBusStr,0x82);
  CursorTo(0xc4);
  wrk = (long) analogRead(PwrBus) * 50 * 57 / 1024; 
  if ((wrk / 1000) > 0)
  {
    Display(wrk / 1000 + '0');
    wrk = wrk % 1000;
  }
  Display(wrk / 100 + '0');
  wrk = wrk % 100;
  Display('.');      
  Display(wrk / 10 + '0');
  SendStr(VDCStr,0);
  Mode = mode;
}
Both the value contained in "wrk," and even the name "wrk" are unique to this function. I could place a variable called "wrk" in another function, the the two values and names would be totally independent of one another.

Commands followed by parentheses, such as 'Clear()' or 'SendStr(PwrBusStr,0x82),' are equivalent to Picaxe Basic's subroutine calls: Clear() would be equivalent to 'gosub Clear.'


My last attempt at writing a large 28X1 program was an utter disaster. I wasn't (and am still not) comfortable with attempting major tasks in the Picaxe environment. I found myself writing code that I couldn't understand even the next day.

Plus, no local variables killed me. With several levels of subroutine calls, I found it just about impossible to keep track of my use of the byte and word registers. It was just too easy to write code that changed the value of, say, b0 in one subroutine, that was used elsewhere in the program for entirely different purposes. Voila! Instant untraceable bug!

The program was becoming impossible to write, understand, and debug. So I gave up, switched to a processor that I could program in C, and all was sweetness and light. (Almost a year later, I have no trouble at all reading and understanding that C program. Even better, I recently ran across some Delphi Object Pascal code I wrote in the last century, and had no problem following the code that I wrote back in the stone age.)

A year after my aborted attempt at a large Picaxe program, I'm once again trying to write a large 28X1 program. Going in, though, I realized that if I tried to write it "the Picaxe way," I'd be doomed to failure once again. So even before I started designing my program, I put a lot of thought into solving some of the shortcomings of Picaxe BASIC.

I think I've been able to address, at least partially, the local variable problem and the descriptive variable name problem. And some planned usage of the different types of Picaxe memory goes a long way toward solving some other problems, too.


Trying to Fix Some Problems

There's no true way of deriving local variables in Picaxe Basic. The solution is to be found in intelligent usage of the memory already present in the 28X1.

We have four different types of memory available to us in the X1 series:

a. EEPROM memory

b. register variables: example - "b0" "w0"

c. scratchpad RAM

d. Table ROM

(and, technically a fifth type, program memory, but I don't include it since it's not something we can really use, other than the Table ROM that resides in program memory.)

Solving the local variable problem means that you set some rules for yourself while you're designing your program, and you stick to them. You must know your rules well, and never violate them. Not even once. Not even a little bit.

My own rules are these:

1. Use EEPROM and Table memory for all program constants. These are things like display strings and fixed constant data.

2. Use scratchpad RAM for all, or almost all, global variable data. Stuff you put in here will be any more-or-less permanent variable storage that will have a long life within your program, but must be changed. Also, by the use of pointers to scratchpad RAM, you can easily derive array data types.

When using scratchpad RAM, try to use your own pointers for most stuff. Use @ptr, @ptrinc, and @ptrdec only when using these facilities will be clear, unambiguous, and where these pointer operations can't be corrupted by other code in your program. A more maintainable method would be to manage your own pointers stored as register variables, and use the get and put commands.

When you assign scratchpad RAM, use fixed, descriptive addresses declared with 'symbol' statements. And, when declaring a block of scratchpad RAM, specify both a start and end address for that block:

Code:
  symbol FreqBCD      = 0  ' 10 bytes. 10 digit unpacked BCD freq
                           ' Example:  12,345,678.90 Hz
    symbol FreqLast   = 9  
    
  symbol ModifiedFreq = 10 ' 10 bytes. Holds modified display frequency
  
    symbol ModLast    = 19 
                            
  symbol FreqBuff     = 20 ' 10 bytes. Unpacked BCD freq work register
  
    symbol BuffLast   = 29 
  
  symbol FreqBuff1    = 30 ' 10 bytes. Extra 10-byte work register
  
    symbol Buff1Last  = 39
    
  symbol TuningWord   = 40 ' 5 bytes:  Addr 40 = Least Sig Byte
                           ' sent to AD9851 LSByte first, LSBit first
    symbol TWLast     = 44
It's much easier to figure out what 'for b0 = FreqBuff to BuffLast' means than 'for b0 = 20 to 29.' Also, if you change a scratchpad block assignment, using descriptive address tags within your program rather than absolute addresses means that all you have to do is change the assignment within the symbol declaration block in your program's preamble. You won't have to try to find and update every instance of 'for b0 = 20 to 29' in your listing.

(continued in next post)
 
Last edited:

Tom2000

Senior Member
3. Use register variables only for parameter passing/return, loop counters, and as local variables.

For the most part, this requires that you declare (for yourself... you can't declare these blocks in Picaxe Basic) blocks of registers, and how they will be used.

A block declaration might look like this:

b0 & b1: byte parameter passing

b2, b3: word parameter passing or loop counters

b4 - b10: Level One subroutines. (These are the subroutines called only from Main)

b11 - b17: Level Two subroutines. (These are called from Level One subroutines.)

b18 - b23: Level Three subroutines. (These are called from Level Two subroutines.)

b24 - b27: Interrupt routines.

(Try to structure your program so you never call subroutines more than three deep.)

This scheme won't solve the local variable problem, but will let your avoid inadvertently changing a register value by mistake. It will save you many hours of chasing evanescent bugs.

4. Derive descriptive variable names by the use of 'symbol'-declared aliases

This is perhaps the most important step toward improving Picaxe program readability.

Let's start with an example of a subroutine written "the Picaxe way," then try to modify it so we can read and understand its operation.

Here's a simple little subroutine that subtracts one 10-digit unpacked BCD number from another. Try to figure out how it works. (No, I'm kidding. Don't even waste your time. It's not worth the effort. But it's something you would have to do, over and over, while building a large Picaxe program "the Picaxe way.")

Code:
DecreaseFreq:

' Subtract tuning constant in FreqBuff[]
' from frequency in FreqBCD[].  Result back
' to FreqBCD[].

' If underflow, zero FreqBCD[]

  b0 = FreqLast           'point to end of FreqBCD[]
  b1 = BuffLast          'point to end of FreqBuff[]
  flag0 = 0                       'borrow flag
  
  do
  
    get b0,b2
    get b1,b3
    
    'borrow?
    if flag0 = 1 then
    
      if b2 > 0 then         'borrow from this digit before subtraction
      
        dec b2
        flag0 = 0
        
      else
      
        b5 = b0   'try to borrow from next digits until one is found
        
        do
        
          dec b5
          
          if b5 <> 255 then   'if no borrow possible, underflow
            goto underflow_df     
          endif
          
          get b5,b6
          
          if b6 > 0 then             'borrow           
            dec b6
            put b5,b6
            flag0 = 0
            goto borrowfixed_df
          endif
          
        loop while b5 <> 255      '255 only because FreqBuff = 0
        
      endif
      
    endif
     
borrowfixed_df:

    if b3 > b2 then
      flag0 = 1
      b2 = b2 + 10
    endif
    
    b2 = b2 - b3
    put b0,b2
    
    dec b0
    dec b1
  
  loop until b1 < FreqBuff
  
  
  'test for underflow
  
underflow_df:

  if flag0 = 1 then
    for b0 = FreqBCD to FreqLast    'zero FreqBCD
      put b0,0
    next
    flag0 = 0
  endif
  
return
Compare the above with the same subroutine, below, written using descriptive variable names:

Code:
DecreaseFreq:

' Subtract tuning constant in FreqBuff[]
' from frequency in FreqBCD[].  Result back
' to FreqBCD[].

' If underflow, zero FreqBCD[]

  symbol FreqPtr_df   = b0
  symbol ConstPtr_df  = b1
  symbol Freq_df      = b2
  symbol Const_df     = b3
  symbol Result_df    = b4
  symbol TempPtr_df   = b5
  symbol Temp_df      = b6
  
  FreqPtr_df = FreqLast           'point to end of FreqBCD[]
  ConstPtr_df = BuffLast          'point to end of FreqBuff[]
  flag0 = 0                       'borrow flag
  
  do
  
    get FreqPtr_df,Freq_df
    get ConstPtr_df,Const_df
    
    'borrow?
    if flag0 = 1 then
    
      if Freq_df > 0 then         'borrow from this digit before subtraction
      
        dec Freq_df
        flag0 = 0
        
      else
      
        TempPtr_df = FreqPtr_df   'try to borrow from next digits until one is found
        
        do
        
          dec TempPtr_df
          
          if TempPtr_df <> 255 then   'if no borrow possible, underflow
            goto underflow_df     
          endif
          
          get TempPtr_df,Temp_df
          
          if Temp_df > 0 then             'borrow           
            dec Temp_df
            put TempPtr_df,Temp_df
            flag0 = 0
            goto borrowfixed_df
          endif
          
        loop while TempPtr_df <> 255      '255 only because FreqBuff = 0
        
      endif
      
    endif
     
borrowfixed_df:

    if Const_df > Freq_df then
      flag0 = 1
      Freq_df = Freq_df + 10
    endif
    
    Freq_df = Freq_df - Const_df
    put FreqPtr_df,Freq_df
    
    dec FreqPtr_df
    dec ConstPtr_df
  
  loop until ConstPtr_df < FreqBuff
  
  
  'test for underflow
  
underflow_df:

  if flag0 = 1 then
    for b0 = FreqBCD to FreqLast    'zero FreqBCD
      put b0,0
    next
    flag0 = 0
  endif
  
return
I've got to admit that the second version isn't all that easy to figure out, either, but that's due to my shortcomings as a programmer, not a problem with the method. But it's much easier to read the second version than the first.

Note that, near the top of the subroutine, I've now included a block of aliases declared using 'symbol' statements. Rather than 'b0,' references to that register are now shown as 'FreqPtr.' Much easier to read.

Well, they would be shown as 'FreqPtr,' but Picaxe BASIC doesn't permit duplicate (or local) symbol declarations, either. Since it's likely that I'll be using 'FreqPtr' in other subroutines within my program, I've appended a little nickname to the end of each alias to, hopefully, make that alias unique within my program. The '_df' is a little nickname that's short for the subroutine name. Note that I also append the nickname to the labels used within the subroutine, making labels local, too. All I have to do is make sure that my nicknames are unique.


Conclusion

Well, actually, it's too early to add a conclusion. I'm still working on the program (as I probably will be for quite a while) and hopefully, will come up with another idea or two along the way. Or, instead, I might find a big 'gotcha' waiting for me.

But so far, it looks pretty good.

I'm hoping that you folks might use these ideas to improve your programming style, and particularly hoping that others will improve on my ideas and post their improvements.

Good luck!

Tom
 
Last edited:

Tom2000

Senior Member
Note to Technical:

After a little bit of thought, I believe that it might be possible to implement local descriptive variable names within the compiler and editor, with no firmware changes necessary.

There would be no changes required to the operation of the 'symbol' command. Instead, you'd introduce a new command that operates similar to symbol -- something like 'decl' or 'declare,' maybe -- that would still declare an alias, but in the background, append an invisible context tag to the alias.

The context tag would be some sort of reference to the subroutine where the local alias is declared.

This wouldn't solve the local variable problem, but the ability to use local variable names would contribute a lot to the Picaxe language without a great deal of work required to update the compiler.

Usage examples:

Code:
Subroutine_One:

  decl ThisVar = b0
  decl ThatVar = b1
  decl ALabel = label:

 <do something>

return

Subroutine_Two:

  decl ThisVar = b1
  decl AnotherVar = b7

  <do something>

return
The invisible context tag, appended by the compiler, would guarantee that using ThisVar in both Subroutine_One and Subroutine_Two would cause no problems.

Anything declared using 'symbol' would still work globally, ensuring backward compatibility.

Possible? Probable? :)

Tom
 
Last edited:

manuka

Senior Member
Tom- you've certainly put a LOT of thought into this! Keep in mind the educational target market of PICAXES, with a key intention being to cheaply let students experience stored program control of external hardware (often called the "LED workout").

I'm a comment fanatic & insist that "every line should have a 'comment", with intro. header comments outlining general info also.
 

Tom2000

Senior Member
Tom- you've certainly put a LOT of thought into this! Keep in mind the educational target market of PICAXES, with a key intention being to cheaply let students experience stored program control of external hardware (often called the "LED workout").

I'm a comment fanatic & insist that "every line should have a 'comment", with intro. header comments outlining general info also.
Stan,

When RevEd introduced processors that would support a program greater than 1000 lines in size, they took the Picaxe well outside the bounds of the educational market.

A program that size is bordering upon a serious microcontroller app, and it's exceptionally important that such a program is written in a maintainable fashion. It's tough to do so with a Picaxe. I'm trying to figure out how to do it.

Rich commenting helps some, but it's not the answer to the problem.

Thanks,

Tom
 
Last edited:

BeanieBots

Moderator
I have to agree with Stan. Comments are THE answer to program readability.
Particularly so in an education environment.
Not only does it explain what the program does, it also gives an indication that the 'student' understands what is going on.
The very act of commenting the code can itself reveal better coding practice and understanding.
Comments should explain what is going on rather than explain what the command does.
 

BCJKiwi

Senior Member
@Tom2000,
At the top of your program,
Make a section for EEPROM and list all the values used and what they are used for.
Likewise for scratchpad
Likewise for any variables you need to carry from one section of the program to another.
This forms your 'memory map' which keeps you out of trouble.

Also search for b0, b1, w0 etc to check where repeats occur in the program and check that they do not interfere with one another - Basic rule, don't put anything in a variable that cannot be overwritten in another part of the program - use all the available variables and watch for byte/word overlaps!

e.g. a small sample from a current ~4000 byte 28X1 program

Code:
'####################################################################################
'# EEPROM use - 0 to 255 available 'STATIC VALUES         #
'####################################################################################
'
' 0 thru 213 spare
eeprom 214,(%00000000,%11111100) '0
eeprom 216,(%00000000,%11111110) '1
eeprom 218,(%00000000,%11111111) '2
eeprom 220,(%10000000,%11111111) '3
eeprom 222,(%11000000,%11111111) '4
eeprom 224,(%11100000,%11111111) '5
eeprom 226,(%11110000,%11111111) '6
eeprom 228,(%11111000,%11111111) '7
eeprom 230,(%11111100,%11111111) '8
eeprom 232,(%11111110,%11111111) '9
eeprom 234,(%11111111,%11111111) '10
eeprom 236,(%11111111,%11111101) '11
eeprom 238,(%11111111,%11111100) '12
eeprom 240,(%01111111,%11111100) '13
eeprom 242,(%00111111,%11111100) '14
eeprom 244,(%00011111,%11111100) '15
eeprom 246,(%00001111,%11111100) '16
eeprom 248,(%00000111,%11111100) '17
eeprom 250,(%00000011,%11111100) '18
eeprom 252,(%00000001,%11111100) '19
eeprom 254,(%00000000,%11111100) '20
'
'255 spare
'
'####################################################################################
'# SCRATCHPAD 0 to 127 available  'DYNAMIC VALUES        #
'####################################################################################
'
'0 thru 2  'SPARE
'
'3         'hserin Speed1 - 1 byte
'6,7        'hserin Speed2 - 2 bytes
'
'8 thru 27   'SPARE
'
'Speed Indicators   program menu 1
'28         'Speed1L Indicator
'29         'Speed1H Warning
'30         'Speed2L Indicator
'31         'Speed2H Warning
'32         'Speed3L Indicator
'33         'Speed3H Warning
'34         'Speed4L Indicator
'35         'Speed4H Warning
'36         'Speed5L Indicator
'37         'Speed5H Warning
'
'38 thru 112 'SPARE
'
'113,114,115,116     Degrees 0 thru 3599
'117,118,119,120     Y Axis Accelerometer 0 thru 1024
'121,122,123,124     X Axis Accelerometer 0 thru 1024
'
'125,126,127 'SPARE
The program uses over 140 EEPROM addresses and over 80 Scratchpad addresses.

There is also a for/next loop after the eeprom section which copies around 40 eeprom values to scratchpad values to speed program execution by allowing @ptrinc/dec to be used in place of eeprom values in the body of the program.

As you suggest, values required in the next loop of the program are stored to scratchpad. 26 of the available 28 byte variables are used, with many being re-used multiple times throughout the program. The program was developed in modular style so each module can use pretty much any variables as any value required outside the module is either carried over to the next module, or saved to scratchpad.

The discipline forced by this programming methodology (along with extensive comments) makes for tight re-usable and readable code.
 
Last edited:

inglewoodpete

Senior Member
Writing large programmes for PICAXE is a challenge. My current project is a home theatre controller using a 40X1 and a 14M. (Programme sizes are currently ~3200 bytes and 253 bytes respectively).

With microcontrollers and particularly PICAXEs, we have to juggle the available resources. If its not hardware limitations (eg output pins or port capabilities), its registers or RAM or programme space.

Local variables may help some people in some situations but the utilisation of registers still has to be managed carefully, else programme space runs out. I have found that RAM has to be used carefully (Eg use loops), otherwise programme space gets chewed up by the Push and Pop commands which are very inefficient.

With care and many comments, registers can be reused in different routines as pseudo-local variables. However, hobby development of code can span several months and I have found all sorts of unexpected results occurring because I forget that I have already used a register in the (previously written) subroutine I am calling.

The following is a sample of how I document my subroutines. I sometime toy with whether to include the raw variable names (b0, w1 etc), as I have with the pin numbers, in the subroutine header comments but I am reluctant to duplicate the declarations elsewhere in the file.

Code:
'
'**** Assemble, then send 'Volume' Word (11 bits) to 1 of 2 TLC5620 4-channel DACs
'
''Volume' word format (x = don't care; S = chip select; A = address; M = multiplier; v = volume):
'			[x x x x S A A M][v v v v v v v v]
'On entry:		[    odd reg    ][    even reg   ]
'	Channel  (hibyte)	Volume control pointer (bits 0, 1, 2) + multiplier bit
'	Volume   (lobyte)	Volume level (8 bit value 0 - 255)
'	LoadReg  (pin)	Previously initialised to 1 (high): Load channel register with output DAC value
'	LoadDAC  (pin)	Previously initialised to 1 (high): Load all four DAC outputs from channel registers
'	LCount   (byte)	Counter
'Used:
'	Address  (byte)	bit 0 (multiplier) is used; bits 1,2 binary pointer to channel
'	Pattern  (word)	made up of Address (byte) and Volume (byte)
'Also used:
'	BitCount (byte)	Number of bits to be output in Sub 'ShiftOut'
'	BitPtr   (word)	Pointer to the bit to mask (1 - 16 bits)
'	SBit     (word)	0 or not 0: Bit to be transmitted
'Level 3 Subroutine
SetVolume:	DACSelect = Channel AND DACSelectMask	'Determine which DAC chip to address (0 or 1)
		If DACSelect = 0 Then					'Chip 0 (Channels 0 - 3)
			PinToClock = ClockDAC0Pin	'Effectively, a chip select Leg 34
		Else											'Chip 1 (Channels 4 - 7)
			PinToClock = ClockDAC1Pin	'Effectively, a chip select Leg 35
		EndIf
		PinForData = DataPin						'Pin # for volume serial data values to be output
		'
		Address = Channel * 2			'Copy address bits to bit 1,2 (from 0,1)
		Address = Address AND DACAddressMask	'Mask all other bits to 0
		If Volume > 127 Then						'This code translates 8-bit code to pseudo 9-bit
			Volume = Volume - 127 / 2 * 3 + 63	' and imitates upper part of log curve
			Address = Address OR MultiplyBy2Mask'Set DAC output x 2 gain control
		EndIf
		'
		Address = Address * 32	'Push address into top 3 bits
		ShiftOut PinToClock, DataPin, 1, (Address /3, SerialDataLo) 'MSBits first

		Low  PortC LoadRegOutC		'Xfer 1 downloaded serial value to DAC parallel register
		High PortC LoadRegOutC
		Return										'Note: All DAC outputs activated in calling routine
I appreciate you raising the topic, Tom. It is something that challenges me too. However, with the limited resources of a microcontroller, the establishment of any standards is going to be difficult.

In my opinion, the biggest things to happen to PICAXEs in recent times is the introduction of Do-Loops, the multibranch If/Then structure and the Select Case structure. A huge advance forward for the PICAXE.
 
Last edited:

westaust55

Moderator
Tom,
What you have written is pretty much spot on.

While I have just started experimenting with the PICAXE microcontrollers, I have experience in writing for microcontrollers and microprocessors in Assembly from 1970 (back when LED’s and Switches were the only I/O) to about 1985.

Good pre-definition and good documentation are the key to success in both writing and later understanding.
A few more features that I would add to your written description relating to the documentations are:

1. Use some form of template as a starting point to writing code, even for the small projects. Mine is a modified plagarised version from elsewhere.

The code snippet by BCJKiwi indicates he has such a template. Likely better than mine from what I can see.
BCJKiwi – any change you can post your program template as well.
2. Use many comments to help explain what the code is doing (other have also mentioned this)
3. Use plenty of white space to make the code readable. Don’t cramp the code text up so it is a mass of lines all on top of each other
4. Indent the code for readability. For example:
• Labels are the only things to start in the first column.
• All other code indented at least 2 and preferably 6 or 8 spaces
• For each Do..Loop, For…Next, and If…Then structure
indent all the code within the structure by at least 2 characters.

The actual examples that Tom has given show this but I feel it is worth putting it into descriptive terms to that new comers understand the whys and wherefores.

As I am new to the PICAXE and recognise the limit to variables and other storage I have prepared myself a sheet to help me keep track of the EEPROM, Scratchpad and variable usage. This may also be of help to others.
Attached/below are my program template (as cobble together so far) and my memory/storage map (although I have an 40X1, tried to colour code to aid use on other PICAXE models as well).

Code:
; =================================================
;   File....... Template
;   Purpose.... PICAXE  Programming Template
;   Author.....
;   E-mail.....
;   Started....
;   Updated.... DD MMM YYYY
;  ===============================================
; -----[ Program Description ]---------------------------------------------


; -----[ Revision History ]------------------------------------------------


; -----[ I/O Definitions ]-------------------------------------------------
Symbol
Symbol

; -----[ Constants ]-------------------------------------------------------
Symbol
Symbol
   


; -----[ Variables ]-------------------------------------------------------
Symbol
Symbol


; -----[ EEPROM Data ]--------------------------------------------------


; -----[ Initialization ]------------------------------------------------------

Init:


; -----[ Program Code ]----------------------------------------------------

Main:

           END
; -----[ Interrupt Routines (if used) ]-------------------------------------
; Interrupt:



;           RETURN


; -----[ Subroutines ]-------------------------------------------------------



;           RETURN
 

Attachments

Last edited:

BCJKiwi

Senior Member
Well I don't actually have a template, if you mean prebuilt text as in a PE "Header".
I just pull the top end from one program to the next and edit to suit.

Will this help to get you started? ( I now have a header!!!)

Code:
'####################################################################################
'# PICAXE 28X1  fw A.2,  ACCELEROMETER,  I/O EXPANDERS,  COMPASS,  RTC,  uALFAT     #
'####################################################################################
'# Program version A.B.C    yy/mm/dd                                                #
'####################################################################################
'
#picaxe 28x1
'SetFreq m8
SetFreq em16
'
#freq m16       'ensure programmer is running at same speed as chip
'#terminal 9600      'Baud rate
#terminal 19200      'Baud rate
'
'####################################################################################
'# EEPROM use - 0 to 255 available 'STATIC VALUES                                   #
'####################################################################################
'
'#rem
'   Values from 0 to 127 max mapped to scratchpad
'   Values yy thru zz set by configuration routines
'
'0 thru 11         Spare
'What this block is for
eeprom 12,(xx)              '
eeprom 13,(xx)              '
eeprom 14,(xx)              '
eeprom 15,(xx)              '
eeprom 16,(xx)              '
eeprom 17,(xx)              '
eeprom 18,(xx)              '
eeprom 19,(xx)              '
eeprom 20,(xx)              '
eeprom 21,(xx)              '
eeprom 22,(xx)              '
eeprom 23,(xx)              '
eeprom 24,(xx)              '
eeprom 25,(xx)              '
eeprom 26,(xx)              '
'27    Spare
'
'What this block is for
eeprom 28,(xx)              '
eeprom 29,(xx)              '
eeprom 30,(xx)              '
eeprom 31,(xx)              '
eeprom 32,(xx)              '
eeprom 33,(xx)              '
eeprom 34,(xx)              '
eeprom 35,(xx)              '
eeprom 36,(xx)              '
eeprom 37,(xx)              '
'#endrem
'
'38 thru 127         Spare
'   Values from 128 to 255 max for static values
'128 thru 255   SPARE
'
'####################################################################################
'# EEPROM use - 0 to 255 available 'STATIC VALUES                                   #
'####################################################################################
'
symbol RTC_0  = %11010000   'Dallas DS1307 RTC i2c address - Fixed
symbol CMP_0  = %11000000   'Devantech CMPS03  i2c address - Fixed
symbol uATF   = %10100100   'GHI Electronics uALFAT-TF i2c address - Fixed
symbol IO_0   = %01000000   'Compass - Microchip MCP23017 I/O expander i2c address
symbol IO_1   = %01000010   'Accelerometer X - Microchip MCP23017 I/O expander i2c address
symbol IO_2   = %01000100   'Accelerometer Y - Microchip MCP23017 I/O expander i2c address
symbol IO_3   = %01000110   'Lights          - Microchip MCP23017 I/O expander i2c address
symbol uADataReady = Input0 'uALFAT UART_TX/DATARDY
symbol uAStartLog  = Input1 'switch for Logging
symbol uARes  = Output0     'uALFAT RESET
symbol uARec  = Output1     'uALFAT Recording Indicator OK
symbol uAReF  = Output2     'uALFAT Recording Indicator FAULT
symbol cfg_sw = Input2      'Leg 13, Program mode select Push Button
symbol cfg_in = 5           'Leg 16, IR input
'
'####################################################################################
'# SCRATCHPAD 0 to 127 available  'DYNAMIC VALUES                                   #
'####################################################################################
'
'0 thru 2  'SPARE
'
'3          'hserin Speed1 - 1 byte
'6,7        'hserin Speed2 - 2 bytes
'
'8 thru 11   'SPARE
'
ptr=12                      '12 thru 37 copy eeprom data to scratchpad
for b0 = 12 to 37
read b0,@ptrinc             'FW A.2
next
'
'12                         '
'13                         '
'14                         '
'........
'35                         '
'36                         '
'37                         '
'38 thru 127         Spare
'
'####################################################################################
'# Initialise                                                                       #
'####################################################################################
Init:
Enjoy
 

moxhamj

New Member
What an interesting discussion. Regarding VB vs C, looking at all the changes to .net, many of which are not backwards compatible, I think both languages are merging into one. Java is probably part of that process too. As a VB programmer I'd say too many concessions have been made to C, but I'm sure the C programmers would say the opposite!

I think picaxe basic is great! But then again, that is in comparison to what I used to write for microcontrollers, which was machine code. A machine code program consists of unintelligible commands made up of a few letters and then a huge long line of comment describing what is going on. So the program ends up being all the comments rather than the actual program. It is probably a good habit to continue.

I prefer to stick to the smaller picaxes - 08Ms and 14Ms and occasionally an 18X if I need to drive an LCD. But I like to use lots of picaxes in networks to do difficult jobs - for example a solar radio transmitter I am building has an 08M running the regulator, a 14M running the transmitter and a detachable 18X running a display. I am sure it could all be done in 40 pin chip, but I like splitting it up precisely because the code becomes simpler and the tasks are modularised and then errors are less likely and the project gets completed faster.

As for unintelligable code, I'll see if I can dig up a long division routine written in machine code. That is truly unintelligable!
 

Tom2000

Senior Member
As you suggest, values required in the next loop of the program are stored to scratchpad. 26 of the available 28 byte variables are used, with many being re-used multiple times throughout the program. The program was developed in modular style so each module can use pretty much any variables as any value required outside the module is either carried over to the next module, or saved to scratchpad.
BCJ,

This sounds very much like "a technique." :)

Are you saying that you're using scractchpad RAM as a stack, to pass vars from one routine to another?

If you've got something interesting going on, as the above suggests, please write it up!

Thanks,

Tom
 

BCJKiwi

Senior Member
Well, I did not see it as anything particularly special, just the only practical way to achieve the result I was looking for.

If you want to see a small part of this program in action see here;
http://www.picaxeforum.co.uk/showthread.php?t=8513
posts #1 and #8

The sample code is only a small portion of the full program. There are quite a few routines collecting and manipulating data which generally are written to scratchpad. They are then read out of scratchpad all at once as part of the write routine to the uALFAT SD card.

The rest of the program uses numerous values stored in scratchpad in conditional tests utilising (mainly) @ptrinc to retrieve for the tests.

Also ephemeral data that needs to be retained over program cycles is stored to scratchpad.
 

Tom2000

Senior Member
BCJ,

Ah, I get it. You're using the scratchpad as general purpose RAM.

I had something else in mind... such as a formalized method for parameter passing within a program. But, following the general idea, I suppose that it would make more sense to pass parameters using scratchpad rather than using the limited register resources.

It would look something like this:

In the scratchpad RAM definition area of your program's preamble:

Code:
  symbol Parm0 = 0      ' This four-byte block used to pass parameters
  symbol Parm1 = 1
  symbol Parm2 = 2
  symbol Parm3 = 3
'  etcetera... for however many bytes you need
You might use the parameter block something like this:

Code:
Main:

  <some stuff>

  put Parm0,27               'set up parameters for subroutine call
  put Parm1,4
  gosub ParmUsingSubroutine

  get Parm0,b0               'get the result of the subroutine's processing
  
  <the rest of the stuff>

end

ParmUsingSubroutine:

  symbol Inpointer_ps   = b0
  symbol Outpointer_ps = b1
  symbol Result_ps       = b2

  get Parm0,Inpointer_ps
  get Parm1,Outpointer_ps

  <do something with the passed-in values>

  put Parm0,Result_ps     'pass result back to calling routine 

return
And I see that some registers should be reserved for Main's use, too. Maybe "borrow" some of the Level Three subroutine registers when Main knows that no Level Three subs will be called. I have to think about this...

Thanks,

Tom
 

BCJKiwi

Senior Member
That appears to complicate things un-necessarily to me.

symbol parm0=0
put parm0,27

is the same as
put 0,27

If you have a memory map established and know what the various locations are for, then the symbol is an extra step but I guess it improves readability which is your objective.

However this does not work the same way as the customary use of symbol. If;

Symbol parm12 = b12
let w1 = parm12 * 2

This will produce the result of whatever is in b12 * 2
But you can't do this if parm12 is a fixed number;

symbol parm12 = 12
let w1 = b12 * 2

This will always produce the same answer (24) regardless of what value you have 'put' into parm12
you still have to 'get' the value of parm12 into a variable and then you have to use the variable. I'm not sure that would be any clearer to me than using the scratchpad directly without the symbols at all.
But then that's just the way my brain works I guess.
 
Last edited:

hippy

Technical Support
Staff member
What an interesting discussion. Regarding VB vs C, looking at all the changes to .net, many of which are not backwards compatible, I think both languages are merging into one.
Converging towards C# is how I see it. There's a lot to be said in favour of .Net but it cannot be avoided that Microsoft has destroyed a perfectly usable and simple to use language which was VB6. Which is ironic really; having invented VB and liberated everyone from Windows Message Loops and unnecessary complexity it's now taking a major step backwards towards "nothing is simple". Anyone who says VB3-VB6 aren't legitimate programming languages or cannot be used to write credible programs doesn't deserve to be called "programmer". ( Yes, I toned that down ! ).

C for micros isn't so bad, especially when being used as a souped up Macro Assembler. All the fine control one needs with easy to use flow control. Which is exactly what it was meant for. I've never undestood how the sheeple were convinced it's the ideal language for applicatons and desktop programming.

That appears to complicate things un-necessarily to me.

symbol parm0=0
put parm0,27

is the same as
put 0,27

If you have a memory map established and know what the various locations are for, then the symbol is an extra step but I guess it improves readability which is your objective.
The advantage is that should the program change and the variable allocation need to alter, only the numbers in the Symbol definitions have to change.

My Rule 1 : Avoid constants in the program code. Especially for anything which is important. Once a constant is used in a program it is fixed and very hard to change later, and it's also hard to ensure that conflicts don't occur or find them when they do.

My Rule 2 : Never use default named variables ( eg, 'b5' , 'bit7', 'w3' ). The only exception is for 'b0', 'b1', 'w0' and 'bit0' through 'bit15' which should be used 'as is', never symboled to any other name and only ever used for temporary purposes. Every routine should be free to mangle those variables as they feel fit.

My Rule 3: All symboled names should be meaningful and ideally understandable by anyone without looking elsewhere; "fmwv" is not as good as "firmwareVersion". Variables and constants used in a subroutine should be prefixed with the name of the subroutine they are used in ( "ButtonPushCounter_currentCount" ), global and shared variables should be easily identifiable as such.

My Rule 4: Never test a Pin variable against a constant 0 or 1. Use a named, symboled constant set as required. The Pin variable ( as per Rule 2 ) should have a symboled name for when the pins need to be moved around. Use the "Is" operator when appropriate. Example ...

-- If doorBell Is DOORBELL_PUSHED Then
---- Gosub SoundBell
-- End If

One trick I use is to create two routines which can load and save a set of variables to particular places in SFR / scratchpad ( say b8 to b13 ), and these routines can be called on entry to subroutines and on exit. b0 to b7 are global ( b0/w0 are good for return results ). It's a bit like doing a big push and pop and is simpler than moving data to and from as needed.

As to Dr Acula's comment on division in assembler, just convert this ...

Code:
Symbol lhs   = w1 ' b3:b2
Symbol rhs   = w2 ' b5:b4
Symbol acc   = w3 ' b7:b6
Symbol loops = b8

lhs = 12345
rhs = 567
SerTxd( #lhs," / ",#rhs )
Gosub LhsBecomesLhsDividedByRhs
SerTxd( " = ", #lhs, CR,LF ) ' 12345 / 567 = 21

lhs = 12345
rhs = 567
SerTxd( #lhs," // ",#rhs )
Gosub LhsBecomesLhsModuloRhs
SerTxd( " = ", #lhs, CR,LF ) ' 12345 // 567 = 438

End

LhsBecomesLhsDividedByRhs:
  Gosub LhsBecomesLhsModuloRhs
  lhs = acc
  Return

LhsBecomesLhsModuloRhs:
  acc   = 0
  loops = 1
  Do While rhs < $8000
    rhs = rhs * 2
    loops = loops + 1
  Loop
  Do
    acc = acc * 2
    If lhs >= rhs Then
      lhs = lhs - rhs
      acc = acc | 1
    End If
    rhs = rhs / 2
    loops = loops - 1
  Loop Until loops = 0
  Return
 

BCJKiwi

Senior Member
Very good points Hippy.

Been reviewing the current project and can certainly improve readability by adopting your strategy(ies).

However will continue with the 'Memory Map' and add the symbols where appropriate within that map and change the code to the symbols.
 

westaust55

Moderator
Being new to PICAXE&#8217;s and after a couple of decades break from such programming, a few more standards/ rules that I am trying to establish for myself for clarity of reading the program code are as follows:

1. BASIC keywords and Interpreter commands &#8211; type the entire word in capitals
DO&#8230;UNTIL, FOR&#8230;NEXT, EEPROM, etc

2. Program Labels &#8211; start with an upper case and thereafter lower case (except for a second word/abbreviation (and allow numbers)
Main: , Interrupt:, Out_595:, etc

3. Variables &#8211; start with a lower case. Typically try and stick with lowercase letters (and allow numbers)
pattern, delaytime, etc


Cannot say that all my own examples todate comply with these yet but am getting back into consistency of code formatting (so also good to read about the methods and rules used by others)

Below is a slightly reworded version of some code I posted earlier now embedded within the programming Template I am starting to use. My programming Template may see further changes as I progress and glean ideas from other sources.

Code:
; =================================================
;   File....... Zig-Zag Flash 8 LEDs
;   Purpose.... Test control of LED&#8217;s using 74HC595 8-bit shift register
;   Author..... Westaust55
;   E-mail.....
;   Started.... 5-04-2008
;   Updated.... 15-04-2008
;  ===============================================
;
; -----[ Program Description ]---------------------------------------------
; test program to drive 8 LED via a 74HC595
; program uses the SHIFTOUT command instead of pushing bits out one at a time
; lit LED starts at the LSB (right) side and steps to the MSB (left) side
; then steps back again to the LSB side. Continues back and forth forever . . . . .
;
; -----[ Revision History ]------------------------------------------------
;
; B &#8211; Reformatted code for easier reading
;
; -----[ I/O Definitions ]-------------------------------------------------
;
; 74HC595 Control I/O
SYMBOL clock     = 4    ; clock on Output 4
SYMBOL serdata   = 5    ; data output on Output 5
SYMBOL latch     = 6    ; latch to 595 outputs with high pulse on Output 6
;
; -----[ Constants ]-------------------------------------------------------
;
SYMBOL bitz      = 8    ; number of bits to send through 74HC595
SYMBOL msb_1st   = 1    ; 1 = MSB first and idle state is low
;   
; -----[ Variables ]-------------------------------------------------------
;
SYMBOL pattern     = b20  ; variable to hold the LED pattern
SYMBOL delaytime = b21  ; delay between 'stepping' to next LED pattern
;
; -----[ EEPROM Data ]--------------------------------------------------
; EEPROM  locn, (data, data) &#8211; dummy line, not used for this program
;
; -----[ Initialization ]------------------------------------------------------
;
Init:   LOW latch
         LOW clock
         pattern    = %00000001 ; start at right most LED ( = LSB)
         delaytime  = 500            ; 500 = 0.5 sec for slow step rate    
;
; -----[ Program Code ]----------------------------------------------------
;
Main:
        DO
            GOSUB Out_595                                   ; display the LED pattern &#8211; only one LED lit at a time
            PAUSE delaytime
            pattern = pattern << 1                             ; shift the pattern left one bit
        LOOP UNTIL pattern = %10000000            ; until at left most ( = MSB) LED

        DO
            GOSUB Out_595                                   ; display the LED pattern &#8211; only one LED lit at a time
            PAUSE delaytime
            pattern = pattern >> 1                             ; shift the pattern right one bit
        LOOP UNTIL pattern = %10000000            ; until at right most ( = LSB) LED

        GOTO Main
;
;       END      - no end for this program, keeps on repeating
;
; -----[ Interrupt Routines (if used) ]-------------------------------------
;
; Interrupt:         &#8211; dummy line, not used for this program
;
;           RETURN
;
;
; -----[ Subroutines ]-------------------------------------------------------
;
Out_595:
        SHIFTOUT clock, serdata,msb_1st, (pattern/bitz)
        PULSOUT latch, 5
        RETURN
;
; -----[ The END ]-----------------------------------------------------------
 

ArnieW

Senior Member
Thanks Tom2000 and everyone else for a welcome discussion. I have a blog site where I keep notes about my picaxe based brewing interests and last year tried to work out some better coding practices (see Picaxe coding school). That blog was meant to be 'part 1' of some ongoing musing - but I ran out of enthusiasm when things got too complex.

I am about to cut my teeth again on the 28X1 based machine, so this topic is timely for me. Westaust55, thanks for the pdf of picaxe variables. I'd done a 'rough' sheet myself, but nowhere near as useful as yours. :)
 

Dippy

Moderator
I agree with WestAust's standards on upper and lower case letter use - this will, of course, upset DPG.

In fact, along with the colourisation in the Editor, I would like to see an option where it will capitalise the command.

Colour is fine on the screen but when you mono-laser-print your code then CAPITALS poduce a visual break. And as the CODE gets bigger then you need all the help you can get.

I deffo go for super-commenting.

I also like labels even if they don't get used. Many editors have a 'navigation' panel which show Labels and Subroutine names. When you want to whizz to the bit in your long code then just click. Bingo.

These would be my only suggestions as they would be fairly simple to do, and, after all, this is about 'good practice' and not really a lesson in C. Beyond the remit I would say, but interesting nonetheless.
 

techElder

Well-known member
This is a thread that deserves to be bumped up to the top again. Perhaps some of these ideas will improve some of the recent spaghetti code?

I'd be interested in how close some of you still are to your own suggestions. I'd also like to see your current methods.
 

BeanieBots

Moderator
The introduction of the X2 bread and their program slots with common variables has certainly made large programs more of a challenge. I now find myself frequently using up all the available variables and have resorted to using peek & poke at the start and end of most subroutines to push/pull values to a 'stack'. This is particularly important if there are any interrupt subroutines to avoid any variable corruption as it's almost impossible to know what variables are in use when the routine is called.
 

premelec

Senior Member
>This is a thread that deserves to be bumped up to the top again. Perhaps some of these
>ideas will improve some of the recent spaghetti code?

I'm thinking the simulation should have a graphic feature which draws a track line as it hops around-

Could change color and width depending on how long the spaghetti is.... :) Add steamy marks as it get extreme or in an infinited loop... have it fill the screen when it's really rank... pile at the bottom like the old 'drip' screen drip program. Kids would love it - :)
 

PerthEng

Member
This is a thread that deserves to be bumped up to the top again. Perhaps some of these ideas will improve some of the recent spaghetti code?

I'd be interested in how close some of you still are to your own suggestions. I'd also like to see your current methods.
maybe count me out, but since you raise the topic, why don't you do the review / comparison against samples from posts (help or own work) each has done recently and report back . . .:)
 

Rickharris

Senior Member
... using up all the available variables and have resorted to using peek & poke at the start and end of most subroutines to push/pull values to a 'stack'...
Oh are we going back to the bad old days of expensive memory and limited storage resulting in programmers squeezing maximum out of the system with clever, often undocumented, wrinkles.

I recall one programmer masking words so he could use the other half for some other task as he had run out of stack space. - fault finding nightmare (undocumented programme of course!)
 

boriz

Senior Member
Being a fairly old hand at BASIC in various forms. I have found all that&#8217;s really necessary is: A decent header with general overview of the programs function and version, and a single comment line at the beginning of each subroutine and loop, sometimes before complex conditions, meaningful variable names, and a main-loop-with-subs structure. Oh yes, and as few GOTOs as possible. (Best if there&#8217;s NONE!)

Of course, I don't always practice what I preach :)
 

hippy

Technical Support
Staff member
There are three arguments against commenting ( and even against documenting the code ); first is that the code should be understandable without any comments or documentation, secondly that, excepting simple typo's, if comments and documentation don't match the code then they aren't worth having, and thirdly that 'pointless comments' don't add anything to aid understanding.

The generally accepted middle ground in most cases is to make the code as self documenting as possible ( meaningful label and variable names ), with the comments describing what that routine or part of code does and why along with anything which helps the reader better understand the code, especially any 'clever tricks' used which may not be immediately obvious.

' Get the BCD number entered which is held as two ASCII digit characters
' in 'ascA' and 'ascB'
'
' number = ( ascA - "0" ) * 16 + ( ascB - "0" )
' number = ( ( ascA * 16 ) - ( "0" * 16 ) ) + ( ascB - "0" )
' number = ( ascA * 16 ) - ( "0" * 16 ) + ascB - "0"
' number = ( ascA * 16 ) + ascB - ( "0" * 16 ) - "0"
' number = ( ascA * 16 ) + ascB - ( $30 * 16 ) - $30
' number = ( ascA * 16 ) + ascB - ( $300 ) - $30
' number = ( ascA * 16 ) + ascB - ( $300 + $30 )
' number = ( ascA * 16 ) + ascB - ( $330 )

GetBcdNumberEntered:
number = ascA * 16 + ascB - $330
Return
 
Last edited:

BeanieBots

Moderator
Oh are we going back to the bad old days of expensive memory and limited storage resulting in programmers squeezing maximum out of the system with clever, often undocumented, wrinkles.
Far from it.
A simple "push variables' at the start and "pop" variables at the end.
A tempory variable (labelled as such) and the use of Peek/Poke Location where "Location" is defined as a symbol with what would have been the variable name if another one had been available.
Reads almost the same as if extra variable had been available.

eg
Code:
'Define variables
Symbol Time = b0
Symbol Temperture = b0
Symbol TempByte = b1

'Define other storage
Symbol Temperature_addr = 0
Symbol Time_addr = 1

... loads of code here

Subroutine: 'reads temperature and stores it without "using" another variable.
Poke Time_addr,Time 'push data
ReadTemp pin,TempByte '(or you can use Temperature as this is also defined as b0)
Poke Temperature_addr,TempByte
Peek Time_addr,Time 'pop data back into Time variable
Return
Temperature is now available by peeking Temperature_addr and Time (b0) has not been corrupted by using b0 for something else on a temporary basis. All very readable and clear what is going on.
 
Last edited:

BeanieBots

Moderator
and thirdly that 'pointless comments' don't add anything to aid understanding.
Oh so true, the amount of times I've things such as:

b0 = b0 + 1 'Add 1 to b0
What a waste of typing:mad:

This was very prevelant in assembler with lines such as:

LD,A,30 'Put 30 in the accumulator
WHY would be a useful comment.
 
Last edited:

Dippy

Moderator
I agree with the good advice re: commenting and sensibley labelled code.
Very much horses for courses and sometimes a comment is definitely needed just as a reminder as the command might not be obvious (esp with my memory):
ADCON2 = %10010100 // Conv Fosc/4 , Acq 4TAD , Right Justified
 

hippy

Technical Support
Staff member
Absolutely, that's exactly where good comments are required and it can be the same with PICAXE Basic where using any 'magic number' - so called as a number which is required but no one knows why or how it works.

Magic numbers can be found in many places, even in well intentioned code. I bet almost all forum members could make a good guess at what the following code is doing -

Code:
Do
  ReadAdc 1, w3
  ReadAdc 2, w6
  If w3 > w6 Then
    High 1
    High 2
  Else
    Low 1
    Low 2
  End If
Loop
But it adds even more to take out the magic numbers which are I/O pins, along with giving meaningful variable names. You can probably now tell the actual application and even imagine the hardware ...

Code:
Do
  ReadAdc NTC_INPUT_PIN, temperature
  ReadAdc POT_INPUT_PIN, thermostatKnob
  If temperature > thermostatKnob Then
    High FAN_RELAY_CONTROL
    High TOO_HOT_LED
  Else
    Low FAN_RELAY_CONTROL
    Low TOO_HOT_LED
  End If
Loop
You might even have more of a clue as to what's perhaps wrong if I post that it doesn't seem to work properly, seems to behave oddly when the temperature goes up.
 

Buzby

Senior Member
Hippy,

How many times have we told the newbies -

Give us a circuit diagram,

What PICAXE chip ?

A photo of your hardware

Capacitors on the 7805 etc.

Post your full code

Our CBs are all stressed out :)
 

inglewoodpete

Senior Member
A simple "push variables' at the start and "pop" variables at the end.
A tempory variable (labelled as such) and the use of Peek/Poke Location where "Location" is defined as a symbol with what would have been the variable name if another one had been available.
I'm with BB on this one, although with a variant. With a limited number of variables, they have to be closely managed.

Over the last couple of years I've used two methods of managing variable values. One is as BB describes: save (some) of the variables at the beginning of a subroutine and restore them at the end.

Where I have a number of "processes" in a programme, I ensure that any persistant values are saved at the end of the process, ready for reuse when ever required. If necessary, the values can be accessed from RAM by other processes.

Programming style
With PICAXEs struggling against their limitations (Eg speed, EEPROM or RAM), there can never be just one ideal programming style. Personally, I avoid the GoTo statement like the plague: it can easily create spaghetti code. However, it occasionally has its place in my code but only for very short jumps. The Case and structured If/Then/ElseIf/Else/EndIf structures can keep code very readable.

On the other hand, the On/GoTo and On/GoSub commands are very space efficient but potentially untidy. It will all depend on whether you are trying to wring out the last byte of program space in an 08M, 14M etc or you're trying the get the fastest possible execution in another model.

Peter
 

hippy

Technical Support
Staff member
Another technique available for PICAXE with scratchpad ( or @bptr ) is to use the scratchpad as a push-pop (LIFO) stack. This can also be used with recursive subroutines providing the maximum GOSUB depth of the PICAXE is not exceeded.

The following calculates a factorial using recursion, 5! = 1*2*3*4*5 = 120

Code:
#Picaxe 20X2
#No_Data
#No_Table
#Terminal 9600

b0 = 5
Gosub Factorial
SerTxd( #b0, "! = ",#w1 )
End

Factorial:
  If b0 <= 1 Then
    w1 = 1
  Else
    @ptr = b0 : ptr = ptr + 1
    b0 = b0 - 1
    Gosub Factorial
    ptr = ptr - 1 : b0 = @ptr
    w1 = w1 * b0
  End If
  Return
 

graynomad

Senior Member
With regard to capitalization I would like to add that constants should be all caps. ie

symbol FREQ = 16000

not

symbol freq = 16000

so when you see

x = freq / 20

500 lines of code later you don't have to wonder if "freq" is a variable that may have been changed somewhere else in the code. You know that FREQ is a constant, you may have to go and find what its value is but at least you know it's stable.

Oh, and NEVER use "x" as a variable name :)
 

inglewoodpete

Senior Member
Hungarian Notation

With regard to capitalization I would like to add that constants should be all caps.
Good point. It's something I do in my mainstream programming ("day job") but somehow haven't adopted in PICAXE work. I must ammend my ways (and my programs;))

An area that sometimes gets me undone in PICAXE programming is confusing byte and word variables when I'm programming many line (and days) after declaring a variable. Maybe I should adopt a form of Hungarian Notation:

Symbol bRAMPointer = b7
Symbol wEEPROMPointer = w6
 
Top