PICAXE Questions & Answers

Part 2 - Software Issues



Everything's easy, once you know how.



Part 1 - Common Questions

Part 2 - Software Issues

Part 3 - Hardware Issues

Part 4 - Miscellaneous and Esoteric Issues

Part 5 - Known Bugs and Other Problems


What is the Byte to Word Variable mapping ?

Word variables are overlayed onto byte variables, when either of the two byte variables have their values changed the word variable changes, and when the word variable has its value changed one or both byte variables will have their values changed.

Byte variable 'b1' is the most significant byte of 'w0' and 'b0' is the least significant byte. The entire word variable to byte variable mapping is shown below ...

        w0 = b1:b0      ( msb:lsb )
        w1 = b3:b2
        w2 = b5:b4
        w3 = b7:b6
        w4 = b9:b8
        w5 = b11:b10
        w6 = b13:b12

The following program will demonstrate the mapping. When the Debugging Screen is set to display words, both 'w0' and 'w1' will contain $1234, and when displaying bytes, 'b0' and 'b2' will contain $34, and both 'b1' and 'b3' will contain $12 ...

        b1 = $12
        b0 = $34
        w1 = $1234
        DEBUG b0


How do I shift bits ?

To shift a byte or word variable left, multiply by 2 to the power of the number of bits you want to shift by ...

        variable = variable * 2     ' Shift left 1 bit
        variable = variable * 4     ' Shift left 2 bits
        variable = variable * 8     ' Shift left 3 bits
        variable = variable * 16    ' Shift left 4 bits
        variable = variable * 32    ' Shift left 5 bits
        variable = variable * 64    ' Shift left 6 bits
        variable = variable * 128   ' Shift left 7 bits

And, for word variables only ...

        variable = variable * 256   ' Shift left 8 bits
        variable = variable * 512   ' Shift left 9 bits
        variable = variable * 1024  ' Shift left 10 bits
        variable = variable * 2048  ' Shift left 11 bits
        variable = variable * 4096  ' Shift left 12 bits
        variable = variable * 8192  ' Shift left 13 bits
        variable = variable * 16384 ' Shift left 14 bits
        variable = variable * 32768 ' Shift left 15 bits

To shift a byte or word variable right divide by 2 to the power of the number of bits you want to shift by ...

        variable = variable / 2     ' Shift right 1 bit
        variable = variable / 4     ' Shift right 2 bits
        variable = variable / 8     ' Shift right 3 bits
        variable = variable / 16    ' Shift right 4 bits
        variable = variable / 32    ' Shift right 5 bits
        variable = variable / 64    ' Shift right 6 bits
        variable = variable / 128   ' Shift right 7 bits

And, for word variables only ...

        variable = variable / 256   ' Shift right 8 bits
        variable = variable / 512   ' Shift right 9 bits
        variable = variable / 1024  ' Shift right 10 bits
        variable = variable / 2048  ' Shift right 11 bits
        variable = variable / 4096  ' Shift right 12 bits
        variable = variable / 8192  ' Shift right 13 bits
        variable = variable / 16384 ' Shift right 14 bits
        variable = variable / 32768 ' Shift right 15 bits


How do I create long timeouts without stopping my program ?

It is not uncommon to want to have some code which does something, but some time later does something else if that thing hasn't been done. A typical case is a game where a button must be pushed within 10 seconds or the player automatically loses the game.

The obvious candidate would seem to be to create a program which waits for ten seconds, checks if the button push has occurred. If it has the button push code is executed, otherwise the 'timeout' code is executed ...

        WAIT 10
        IF pin1 = 0 THEN ButtonIsPushed
        GOTO TimedOut

The problem is that the button must be pushed ( wired for this example so it pulls Pin 1 to 0v when it is ) and be held at the end of the test. If the button is released before the end of the WAIT, then the push will not be seen.

To make the program more responsive, we need to check for the button push more frequently; in technical terms, this is, "Increasing the granualarity of the timeout" ...

        WAIT 1
        IF pin1 = 0 THEN ButtonIsPushed
        WAIT 1
        IF pin1 = 0 THEN ButtonIsPushed
        :
        WAIT 1
        IF pin1 = 0 THEN ButtonIsPushed
        GOTO TimedOut

We can represent this much more efficiently by using a counter to count the number of times we have waited, or by using a FOR..NEXT loop ...

        counter = 0
Loop:
        WAIT 1
        IF pin1 = 0 THEN ButtonIsPushed
        counter = counter+1
        IF counter < 10 THEN Loop
        GOTO TimedOut

or, more elegantly ...

        FOR counter = 1 TO 10 ' 10 x 1s = 10s
          WAIT 1
          IF pin1 = 0 THEN ButtonIsPushed
        NEXT
        GOTO TimedOut

Although we have improved things, the button push has to be seen at the end of each one second wait, so a short, stabbed push of the button may not be seen unless it happens to coincide with the actual button test at the end of the WAIT.

The trick is to reduce the wait ( using PAUSE now to allow millisecond waits ) and to have more of them ...

        FOR counter = 1 TO 250 ' 250 x 40mS = 10s
          PAUSE 40
          IF pin1 = 0 THEN ButtonIsPushed
        NEXT
        GOTO TimedOut

With a lower wait ( 40mS here ) we will see button pushes which last for about 40mS or longer.

The only downside of this technique is that as the PAUSE time is reduced, the time taken to execute the FOR..NEXT and the IF command start to add on to the time it takes to complete all loops, so 250 loops will actually take longer than one second, extending the time before a 'timeout' occurs. This can easily be rectified by reducing the number of times the loop is repeated, however the loop value required will often only be found by experimentation.

If you need to repeat a loop more than 255 times, you will need to make sure that the 'counter' variable is a word variable not a byte variable.


How do I do things after a long time delay ?

The maximum single delay allowed using PAUSE or WAIT is 65,535mS, or just over 65 seconds. To delay for longer you can simply use more delays, or, better still, count the number of delays executed.

The following code shows how to create a delay which will allow us to do something every 100mS, every second, every minute, every hour and every 24 hours after the PICAXE has been reset ...

        SYMBOL jiffies = b0
        SYMBOL seconds = b1
        SYMBOL minutes = b2
        SYMBOL hours   = b3

    StartDelay:

        jiffies = 0
        seconds = 0
        minutes = 0
        hours   = 0

    WaitForOneTick:

        PAUSE 100 ' 100mS

        GOSUB EveryOneHundredMillisecondsEvent

        jiffies = jiffies + 1
        IF jiffies <= 9 THEN WaitForOneSecond
        jiffies = 0

        GOSUB EverySecondEvent

        seconds = seconds + 1
        IF seconds <= 59 THEN WaitForOneSecond
        seconds = 0

        GOSUB EveryMinuteEvent

        minutes = minutes + 1
        IF minutes <= 59 THEN WaitForOneSecond
        minutes = 0

        GOSUB EveryHourEvent

        hours = hours + 1
        IF hours <= 23 THEN WaitForOneSecond
        hours = 0

        GOSUB EveryTwentyFourHourEvent

        GOTO WaitForOneTick

A 'jiffy' is a common computer science term for a unit of time which is an integer divisor of a second; in this case each jiffy is 100mS, and there are 10 jiffies in a one second period.

Events can also be added to occur after particular time periods by simply checking the component part of the time's value when it is incremented. For example, adding the following after the 'minutes = minutes + 1' line, allows events to be generated every five minutes ...

        temporaryValue = minutes // 5
        IF temporaryValue <> 0 THEN NotMultipleOfFiveMinutes

        GOSUB EveryFiveMinutesEvent

    NotMultipleOfFiveMinutes:

The example repeats 'events' continually; the 'EveryHourEvent' will be called every hour. If you want a delay that stops after the first time an event is generated, simply stop the program afterwards with an 'END', and do not loop back to the 'WaitOneSecond:' label.


What do the % and // operators do ?

The '%' and '//' operators are the same, and the two operator symbols can be used interchangeably with each other. The two assignments below are functionally equivalent ...

        b0 = b1 % b2
        b0 = b1 // b2

The % and // operators are what are usually called "MOD" in other programming languages and return the "Modulus" of a number; that is the remainder after the value on the left has been divided by the value on the right. For example ...

    "10 // 3" gives the result "1", because 3 can be divided into 10 three times with a remainder of one - ( 3 * 3 ) + 1 = 10.

    "10 // 2" gives the result "0", because 2 can be divided into 10 exactly five times with a remainder of zero - ( 2 * 5 ) + 0 = 10.

    "N // 1" will always return the result 0, as will "N // N".

    "N // 0" will always return N, although the operation is mathematically undefined, and should not be used.


How do the &/ and |/ operators work ?

Despite what some versions of the PICAXE documentation has said, the &/ ( "AND NOT" ) is not a "NAND" operator, and |/ ( "OR NOT" ) is not a "NOR" operator.

With 'b2 = b1 &/ b0', the value in 'b0' is inverted before it is ANDed with the value in 'b1' to give a result in 'b2', and likewise for |/ the value in 'b0' is inverted before ORing with the value in 'b1'. This gives an entirely different result to performing an AND or OR and then inverting the result.

The actual operator functions are as follows ...

    AND NOT    b2 = b1 &/ b0           b2 = b0 ^ $FF & b1
    OR NOT     b2 = b1 |/ b0           b2 = b0 ^ $FF | b1

    NAND       b2 = b1 NAND b0         b2 = b1 & b0 ^ $FF
    NOR        b2 = b1 NOR b0          b2 = b1 | b0 ^ $FF

Note that the NAND and NOR operators don't actually exist on the PICAXE so the more verbose expressions must be used; 'b1' and 'b0' are interchangeable in both the NAND and NOR expressions. When using word variables, the inversion is achieved by using $FFFF rather than $FF.

The ^/ ( "XOR NOT" ) operator is the same as "XNOR", as described in the documentation.


Using PEEK and READ with Word Variables

The documentation supplied with the Programming Editor is not entirely clear on how the PEEK and READ statements work when using word variables to store the values obtained.

Whereas all other statements which read an 8-bit, byte value will store that value as a 16-bit, word value, with the MSB cleared, the PEEK and READ commands leave the MSB unchanged ...

        w0 = $1234
        POKE $50,$AB
        PEEK $50,w0
        SERTXD( #w0 )   ' Displays 4779 ( $12AB )

        w0 = $1234
        WRITE $00,$AB
        READ $00,w0
        SERTXD( #w0 )   ' Displays 4779 ( $12AB )

The manufacturer states that this is the expected behaviour as PEEK and READ are both byte oriented commands, although I personally believe it is a bug because that argument also applies to any command which returns a byte value which is then placed in a word variable ...

        LET w0 = b2
        LET w0 = pins
        INFRAIN w0
        INFRAIN2 w0
        READADC pin,w0
        READI2C pin,w0
        READTEMP pin,w0
        SERIN pin,baud,w0

The operation of PEEK and READ is different to all these cases, however the manufacturer states that there is no intention to change the way the commands work, and there is always a potential for 'breaking' existing code if they did.

When using PEEK or READ with word variables, it is necessary to clear the MSB of the variable to ensure the resulting value which you expect is placed into that variable.


How do the MIN and MAX operators work ?

A good question, and I would defy anyone to claim to have a complete understanding of the operator's function from their first reading of the PICAXE Basic Commands Summary datasheet ...

    MAX - make less than or equal to the maximum

    MIN  - make greater than or equal to the minimum

The MIN and MAX functions are not as they are in many other programming languages, and rather counter-intuitively, MIN chooses the maximum of two values, while MAX chooses the minimum of two values.

In the PICAXE, MIN and MAX are 'limiting functions'; MIN limits the result to a minimum value, while MAX limits the result to a maximum value - For 'x MIN y', the result will never be less than y, and for 'x MAX y', the result will never be greater than y. Combined, for 'x MIN a MAX b', the result will never be less than a or greater than b.

This can be demonstrated by the following program ...

        FOR b0 = 1 TO 4                             ' x
          FOR b1 = 1 TO 4                           ' y
            b2 = b0 MIN b1                          ' x MIN y
            b3 = b0 MAX b1                          ' x MAX y
            b4 = b0 MIN 2 MAX 3                     ' x MIN 2 MAX 3
            SEROUT TX,TX_BAUD,(#b0,#b1,#b2,#b3,#b4) ' Output results
          NEXT
        NEXT
        END

The results are as follows ...

    x
    y
    1 1 1 1
    1 2 3 4
    2 2 2 2
    1 2 3 4
    3 3 3 3
    1 2 3 4
    4 4 4 4
    1 2 3 4
    x MIN y 1 2 3 4 2 2 3 4 3 3 3 4 4 4 4 4
    x MAX y 1 1 1 1 1 2 2 2 1 2 3 3 1 2 3 4
    x MIN 2 MAX 3 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3


Why doesn't the MIN operator work ?

The MIN operator require the following firmware versions to work as documented ...

    PICAXE-08  :  Version 4.2 and above
    PICAXE-08M  :  Any version
    PICAXE-18  :  Version 2.5 and above
    PICAXE-18A  :  Any version
    PICAXE-18X  :  Any version
    PICAXE-28  :  Version 3.5 and above
    PICAXE-28A  :  Any version
    PICAXE-28X  :  Any version
    PICAXE-40X  :  Any version


How do I use RANDOM ?

The 'RANDOM variable' command will generate a sequence of pseudo-random numbers, providing the contents of the variable remain unchanged between each RANDOM statement using that variable. If the variable's contents are changed, then the randomness of the sequence will be destroyed, and you may always see the same number returned.

The RANDOM command can use either a byte or word variable, however, whereas the use of the word variable gives you a very long sequence of numbers before it repeats that sequence, using a byte variable gives just a short sequence ...

        b0 = 0

    Loop:

        RANDOM b0
        SEROUT TX_PIN, TX_BAUD, (#b0)

        GOTO Loop

This example will show the pseudo-random number sequence is 0, 5, 11, 23, 47, 94, 189, 122, 244, 232, 208, 161, 66, 133 and then the cycle repeats again from 11.

In Hexadecimal, the sequence is : 00, 05, 0B, 17, 2F, 5E, BD, 7A, F4, E8, D0, A1, 42, 85 and then back to 0B.

It is likely that a different number sequence will be generated if the byte variable is initialised to something other than a number in this sequence, but there is no guarantee that the cycle will contain many more numbers before repeating or that it will show better pseudo-randomness.

The problem is that RANDOM expects a 16-bit number to create the next number in sequence, and while a byte variable is converted to a 16-bit number for use, the top 8-bits of the word are always set to zero. The bits left in the top 8-bits, necessary to generate the next number in sequence next time are lost when the current number is put back into the byte variable, and the length of the sequence becomes truncated.

The easiest solution to creating long 8-bit sequences is to use RANDOM on a word variable and then use one of the byte variables which it overlays onto as the 8-bit pseudo-random number; for 'w0' this would be 'b0' or 'b1'. It is important that neither the word variable of the two byte variables which it overlays onto are altered between the use of RANDOM or the randomness of the sequence will be altered as described earlier.

It is possible to 'kick' the byte variable so it has a different value in it before the next RANDOM statement, but just adding one to the variable only extends the number sequence to 24 before repeating, adding two has no effect and still returns a 12 number sequence. You will need to experiment if you wish to use this technique.

An alternative solution if you do not wish to use word variables with RANDOM is to write your own 8-bit Pseudo-Random Number Generator ...


How do I write an 8-Bit Pseudo-Random Number Generator ?

The following routine will generate a maximal repeating pseudo-random number sequence in 'b0', providing that the contents of 'b0' is not altered between calls to the routine. It is not the best pseudo-random number generator in the world but it is fairly code efficient and does have a very long repeat sequence.

    RandomizeB0:
        IF b0 = 0 THEN RandomizeB0Msb1
        IF bit7 = 0 THEN RandomizeB0Msb0
    RandomizeB0Msb1:
        b0 = b0 * 2 ^ $1D
        RETURN
    RandomizeB0Msb0:
        b0 = b0 * 2
        RETURN

If you need to use byte variable 'b1' the code becomes ...

    RandomizeB1:
        IF b1 = 0 THEN RandomizeB1Msb1
        IF bit15 = 0 THEN RandomizeB1Msb0
    RandomizeB1Msb1:
        b1 = b1 * 2 ^ $1D
        RETURN
    RandomizeB1Msb0:
        b1 = b1 * 2
        RETURN

If you need to use a byte variable other than 'b0' or 'b1' then you'll have to use a temporary variable and the following code ...

    RandomizeBn:
        IF bN = 0 THEN RandomizeBnMsb1
        bTemporary = bN & $80
        IF bTemporary = 0 THEN RandomizeBnMsb0
    RandomizeBnMsb1:
        bTemporary = $1D
    RandomizeBnMsb0:
        bN = bN * 2 ^ bTemporary
        RETURN


How do I create a random number at reset ?

It's more difficult than you would imagine.

The most obvious thing to do would be to put a RANDOM statement at the beginning of your program and use whatever random number is placed into the associated variable. The problem is that the number generated depends upon what value was in the variable before RANDOM is executed. Because the PICAXE always sets all variables to zero at reset or power-on, RANDOM will always be called with the variable having the same number ( zero ) every time and will consequently always deliver the same, next random number in sequence every time.

Whenever your program starts it will always get given the same random number.

The way to avoid this is to make sure that the variable used in the RANDOM command is set to a different value every time the programs runs. The problem now is finding a number which is different every time the program runs; where do we get one from ?

A desktop PC is lucky because the hardware includes a real-time clock and so it can choose a starting value dependent upon the time of day, which is unlikely to be the same every time the program is run. In a PICAXE system where there is a real-time clock, that can be used as it is for a PC, but most PICAXE's won't have a real-time clock attached.

It's not possible to use the elapsed time counter which exists in the PICAXE chip, the TMR0 register of a PICmicro, because, by its nature, it will almost certainly take the same amount of time ( instruction clock cycles actually ) to get to the point you read its value every time the PICAXE is reset, and you will read the same value every time.

If you have an external input sensor, a light sensor, temperature sensor, humidity sensor or something similar which measures something which varies over time, you could read that and use it as a value which may be different every time the PICAXE is reset, but most environmental measurements change little over short periods of time.

The most appropriate way of doing what we need to is to use the data EEPROM within the chip which we can use when we reset the PICAXE, and update ready to use for next time. The data EEPROM is non-volatile, which means that the value will not get forgotten even when the PICAXE is powered off. We don't need to worry about what value is in the EEPROM when we first run our program, as it really doesn't matter.

The following example code uses data EEPROM location $00 to store a value which will have changed every time we reset the PICAXE ...

    PowerOnReset:

        EEPROM $00,(0)

        READ $00,b0
        b0 = b0 + 1
        WRITE $00,b0

        RANDOM w0       ' or RANDOM b0

This will give us a differing random number in 'w0' ( or 'b0' ) whenever the PICAXE is reset, and subsequent executions of 'RANDOM w0' ( or 'RANDOM b0' ) will get further random numbers in the sequence.

Note that we have included an 'EEPROM $00,(0)' statement. The primary purpose of this is to make sure that the Programming Editor knows we are using the data EEPROM location $00 to hold some data, and it will warn us should we use other EEPROM commands and try and re-use that location when we can't. It may be necessary to use an alternative location if you want to use your own EEPROM statements and want to use location $00.

It will also ensure that on PICAXE's which have shared program code and data EEPROM space that we don't download a program which will become corrupted when we try to run it, which would be a nightmare to debug otherwise.

One consequence of this EEPROM statement is that whenever the program is downloaded into the PICAXE, it will always start its random number sequencing from the same place. Although this sounds like a major disadvantage, it is often perfect if you need to debug your code, and once the PICAXE has had its final download done, and the program is complete, it will behave randomly after that. If you do need to test your code with different values upon each download, then changing the value specified by the EEPROM statement in the Programming Editor before each download is probably the easiest way to do it.

But I don't have any spare EEPROM locations !

If you don't have any spare EEPROM locations, you can hold the value needed in volatile data memory ( SFR's in PICmicro parlance ), and use PEEK and POKE to access it ...

    PowerOnReset:

        PEEK $50,b0
        b0 = b0 + 1
        POKE $50,b0

        RANDOM w0       ' or RANDOM b0

This will generate a new random number every time the PICAXE is reset, but it may generate the same random number after it has been powered off and back on again.

Note that the PICAXE-08 does not support the PEEK or POKE commands, and it will be necessary to use a data EEPROM location. Although the PICAXE-08M does support PEEK and POKE, because it can only be reset by powering it off and back on, the value in volatile memory may be reset to the same value every time it is reset. It is recommended to use a data EEPROM location on a PICAXE-08M.


How do I create a random number between two values ?

You can create a random number between 0 and N by using the modulus ( // or % ) operator on the random number with a value of N+1, as shown below ...

        RANDOM w0
        b2 = w0 // N_plus_1

For the more general case, of needing a returned value between any two values, Nmin and Nmax, the following can be used ...

        RANDOM w0
        b2 = w0 // Nmax_minus_Nmin_plus_1 + Nmin

To create a randomly rolled dice value of 1 to 6, Nmin is 1, Nmax is 6, and Nmax-Nmin+1 is 6-1+1, or simply 6, giving ...

        RANDOM w0
        b2 = w0 // 6 + 1

This particular case will give a sequence of which the first 50 rolls of the dice are ...

    36666541111236612562543535442624362662424255614362

There are, as can be easily seen, a lot of consecutively repeating numbers which does not really reflect our experience in rolling real dice - especially in terms of rolling consecutive 6's !

Although the above sequence could happen 'randomly' in real life, what we want is not necessarily randomness in terms of what a pseudo-random number generator may churn out. It is sometimes necessary to modify the random sequence to push it towards, "what we want", but that is a very subjective matter.

We can modify our sequence by dividing the random number by a value before we perform the modulus arithmatic, and for this case, dividing by five seems to be a suitable value ...

        RANDOM w0
        b2 = w0 / 5 // 6 + 1

Giving a sequence which has a much better 'feel' to it ...

    12354124235424254563356536624511532663665331514363

A 'divide by five' is not however satisfactory for other values of Nmin and Nmax, and an amount of experimentation will be needed to arrive at a satisfactory value to divide by.

In some cases we may not want to allow repeating values at all, and to avoid that we can simply force the dice, in this example, to be re-rolled whenever we get the same number as last time, as below ...

    RollDice:
        RANDOM w0
        b2 = w0 // 6 + 1
        IF b2 = b3 THEN RollDice ' Same as before - Roll again
        b3 = b2

This gives us a sequence of ...

    36541236125625435354262436262424256143625625361356

The moral is to always check your random number sequences to see what they are generating, and to 'tweak them' until you have something which feels reasonably realistic.


How do I convert between Decimal and BCD ?

To convert a Decimal byte to two BCD nibbles ...

        SYMBOL bDecimal = b0
        SYMBOL bTensNibble = b1
        SYMBOL bUnitNibble = b2

        bDecimal = 26

        bTensNibble = bDecimal / 10
        bUnitNibble = bDecimal // 10

        ' bTensNibble now has value $02, or 2
        ' bUnitNibble now has value $06, or 6

To convert a Decimal byte to a BCD byte ...

        SYMBOL bDecimal = b0
        SYMBOL bBcd = b1

        bDecimal = 26

        bBcd = bDecimal / 10 * 16
        bBcd = bDecimal // 10 | bBcd

        ' bBcd now has value $26, or 38

To convert two BCD nibbles to a Decimal byte ...

        SYMBOL bDecimal = b0
        SYMBOL bTensNibble = b1
        SYMBOL bUnitNibble = b2

        bTensNibble = 2
        bUnitNibble = 6

        bDecimal = bTensNibble * 10 + bUnitNibble

        ' bDecimal now has value 26, or $1A

To convert a BCD byte to a Decimal byte ...

        SYMBOL bDecimal = b0
        SYMBOL bBcd = b1

        bBcd = $26

        bDecimal = bBcd / 16 * 10
        bDecimal = bBcd & $F + bDecimal

        ' bDecimal now has value 26, or $1A


How do I use interrupts and SETINT ?

Understanding interrupts and the associated commands is complex if you are unfamiliar with hardware interfacing, interrupt programming or programming in general. Interrupts are not usually the things newcomers to the PICAXE would use because they do need this understanding, however they are simpler to use than one would imagine.

The use of SETINT and related issues are all described in the Basic Commands Summary manual. This is a reference guide which while giving examples is not intended to be a tutorial on the theory of operation and use of interrupts, which is a whole subject in itself.

The SETINT has two arguments, a 'compare with value' (input) and an 'input mask' (mask) in that order.

The 'input mask' defines which pins are to be checked to see if there's an interrupt to be generated ...

    %00000001 will check input pin 0
    %00000010 will check input pin 1
    %00000100 will check input pin 2
    %00001000 will check input pin 3
    %00010000 will check input pin 4
    %00100000 will check input pin 5
    %01000000 will check input pin 6
    %10000000 will check input pin 7

These can be combined to check a number of input pins together ...

    %00000011 will check input pins 1 and 0
    %10000100 will check input pins 7 and 2

Having decided which pins you want to use for the interrupt, the value determines the second parameter of the SETINT command.

Once a SETINT is active, the PICAXE monitors the pins you have specified in 'input mask' where a '1' is present, ignoring other pins.

Every time the PICAXE checks the input pins it creates an 8-bit value which reflects the value of the pins it did read with others being forced to zero.

An input mask of %10000100 will check pins 7 and 2 and create a value of %a0000b00 where bit 'a' will be 1 if pin 7 is high and 0 if low, and bit 'b' will be 1 if pin 2 is high and 0 if low.

The 'compare with value', the first argument of the SETINT command, is what this created value is compared with, and if the two match, then the interrupt will occur, if they don't match then the interrupt won't occur.

If the 'input mask' is %10000100, pins 7 and 2, then the valid 'compare with value' can be one of the following ...

    %00000000 - Pin 7 = 0 and pin 2 = 0
    %00000100 - Pin 7 = 0 and pin 2 = 1
    %10000000 - Pin 7 = 1 and pin 2 = 0
    %10000100 - Pin 7 = 1 and pin 2 = 1

So, if you want to generate an interrupt whenever Pin 7 is high and Pin 2 is low, the 'input mask' is %10000100 and the 'compare with value' is %10000000, giving a SETINT command of ...

    SETINT %10000000,%10000100

The interrupt will then occur when, and only when, pin 7 is high and pin 2 is low. If pin 7 is low or pin 2 is high the interrupt will not happen.


How do I process an interrupt ?

Interrupts are not enabled until the first SETINT is encountered. After that, any input interrupt condition which is met causes a subroutine jump to a routine which must be labelled 'Interrupt:'. The RETURN of that routine cause execution to continue from the point where the interrupt occured.

When the 'Interrupt:' rutine is entered, the SETINT is disabled and must be re-enabled for further interrupts to be responded to. Interrupts are re-enabled by issuing another SETINT command which is activated when the RETURN of the interrupt routine is executed.

It is important that the condition which caused the interrupt in the first place has 'disappeared' or when interrupts are re-enabled and the RETURN is executed, another interrupt will be immediately seen. If the interrupt was caused, by way of example, by a button being pushed; the interrupt routine should check if the button is still held and only re-enable the interrupt and continue when it has been released.

The following code demonstrates how to handle an interrupt caused by a button which sets Input Pin 7 to 1 when pushed and to 0 when it is released ...

        SETINT %1000000,%10000000 ' Interrupt on Pin 7 High

    Loop:

        Main program code

        GOTO Loop

    Interrupt:

        Code to do something when button pushed

    WaitForButtonRelease:
        IF pin7 = 1 THEN WaitForButtonRelease

        SETINT %1000000,%10000000 ' Re-Enable interrupt

        RETURN                    ' Allow the interrupts


When do interrupts work ?

When interrupts have been enabled the PICAXE monitors the input pins specified by the SETINT command to determine if an interrupt has occured, and if it has a subroutine jump to the 'Interrupt:' routine is made.

The input pins are checked just before each PICAXE statement is executed, but not while that statement is executing. This means that the interrupt must exist for long enough for the PICAXE to see it. Interrupts which appear only while the PICAXE is busy doing something else will be missed.

Commands which require the full attention of the PICAXE cannot be interrupted once they have started to execute, and will not continue program execution until they have done thir job, are ...

  • INFRAIN and INFRAIN2
  • INFRAOUT
  • KEYIN
  • NAP
  • SERIN
  • SLEEP
  • SOUND

The PLAY and TUNE commands can be interrupted partway through, after each note has been completed, but a SOUND command cannot. When a PLAY or TUNE command is interrupted, any notes that haven't been played will be discarded and execution after the RETURN from the 'Interrupt:' routine will continue at the statement following the PLAY or TUNE command that was interrupted.

The PAUSE and WAIT commands can be interrupted, but not a NAP or SLEEP. Any delay in progress will be cut short and execution after the RETURN from the 'Interrupt:' routine will continue at the statement following the PAUSE or WAIT command that was interrupted.


How do I handle Multiple Interrupts ?

The PICAXE can only have one interrupt active at any one time, but it is possible to use the SETINT command to handle more than one interrupt line at different times.

It is also possible to combine all interrupts into a single "interrupt input line" and then have your program determine what actually caused the interrupt to occur ...


How do I handle Multiple Source Interrupts ?

The PICAXE allows an interrupt to be activated whenever certain input lines are at certain levels, but it is quite common to want to have an interrupt whenever any line is active, but the SETINT command does not allow this.

The solution to this problem for the PICAXE was identified by Andrew Whitehead who came up with the idea of diode-mixing interrupt lines together, and checked it worked.

To interrupt on two or more positive going lines, diode mix the lines together to a single input pin on which to interrupt. You must also add a pull-down resistor from the diode-mixing point to 0V. Whenever any of these lines go high, an interrupt will be generated. The interrupt lines which are being mixed can also be taken to individual PICAXE input pins to allow the source of the interrupt to be determined. Andrew has an excellent diagram showing how this is done for his AxeBot robot.

The following program shows how to respond to such a scheme ...

        SETINT %00001000,%00001000 ; Interrupt when Pin 3 is high

    DoNothing:
        GOTO DoNothing

   Interrupt:
        IF pin1 = 0 THEN CheckPin2Interrupt

        GOSUB HandlePin1Interrupt

   CheckPin2Interrupt:
        IF pin2 = 0 THEN ReEnableInterrupts

        GOSUB HandlePin2Interrupt

    ReEnableInterrupts:
        IF pin1 <> 0 OR pin2 <> 0 THEN ReEnableInterrupts

        SETINT %00001000,%00001000 ; Interrupt when Pin 3 is high
        RETURN

To interrupt on one or more negative going lines, diode-mixing with the diodes reversed ( the pointy ends away from the PICAXE ) and a pull-up at the mixing point should also work but has not been tested by the author.


How many GOSUB's can I use ?

You can have up to 16 GOSUB's on a PICAXE-08, 18 and 28, but on the PICAXE-08M, 18A, 18X, 28A or 28X/40X, you can only use 15 GOSUB's as one of the 16 which the firmware supports is always reserved for use with interrupts ( SETINT ) whether interrupts are used or not.

The only case where the reduction in the number of GOSUB's allowed will prove problematic is when you are migrating program code from a PICAXE-08, 18 or 28 to one of the more advanced variants and have used 16 GOSUB's.

The PICAXE-18X ( Firmware 8.2 and above ), PICAXE-28X and PICAXE-40X ( both Firmware 7.4 and above ) support up to 256 GOSUB's selectable through the View/Options menu of the Programming Editor; again, one is reserved for interrupt use.


How do I overcome the GOSUB Limitation ?

The PICAXE-18X ( Firmware 8.2 and above ), PICAXE-28X and PICAXE-40X ( both Firmware 7.4 and above ) support up to 256 GOSUB's, selectable through the View/Options menu of the Programming Editor.

For those without PICAXE-18X's or 28X's or with earlier firmware versions which don't support more than 15 or 16 GOSUB's, the easiest way to overcome this limit is to create your own 'return address jump table' and 'stack' using the SFR's of the PICAXE which can be accessed by using the PEEK and POKE commands.

Note that the PICAXE-08 does not support PEEK or POKE so this technique is not possible, and the 08 is restricted to using the 16 GOSUB limited.

        SYMBOL depth    = b0

        SYMBOL at       = b12
        SYMBOL stackPtr = b13

'       Initialise the stack pointer. For all PICAXE variants this
'       is $50 and allows subroutines to be nested up to 48 deep on
'       the PICAXE-18.

        stackPtr = $50

' ******************************************************************
' *                                                                *
' *     The User Program                                           *
' *                                                                *
' ******************************************************************

'       Prints "-1+2-3<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>4"

'       Note that all subroutine calls use "at=<number>:GOTO <name>"
'       which must be followed by "Pushed<number>:", where <number>
'       is a unique identifying number in the program, and <name> is
'       the name of the subroutine to call.

Again:

        at=0:GOTO Minus         ' Effectively "GOSUB Minus"
        Pushed0:

        SERTXD ("1")

        at=1:GOTO Plus          ' Effectively "GOSUB Plus"
        Pushed1:

        SERTXD ("2")

        at=2:GOTO Minus         ' Effectively "GOSUB Minus"
        Pushed2:

        SERTXD ("3")

        depth = 20
        at=3:GOTO Recurs        ' Effectively "GOSUB Recurs"
        Pushed3:

        SERTXD ("4")

        PAUSE 5000              ; Delay 5 seconds and repeat

        GOTO Again

' ******************************************************************
' *                                                                *
' *     User Subroutines                                           *
' *                                                                *
' ******************************************************************

'       All subroutines must start with the following line ...
'       "POKE stackPtr,at:stackPtr=stackPtr+1" and end with ...
'       "GOTO Pop" rather than a "RETURN".

Minus:  POKE stackPtr,at:stackPtr = stackPtr + 1
          SERTXD ("-")
        GOTO Pop

Plus:   POKE stackPtr,at:stackPtr = stackPtr + 1
          SERTXD ("+")
        GOTO Pop

'       This is a recursive subroutine

Recurs: POKE stackPtr,at:stackPtr = stackPtr + 1

          depth = depth - 1
          IF depth = 0 THEN Recur9
          SERTXD ("<")
          at=4:GOTO Recurs        ' Effectively "GOSUB Recurs"
          Pushed4:
          SERTXD (">")

Recur9: GOTO Pop

' ******************************************************************
' *                                                                *
' *     Stack Handler                                              *
' *                                                                *
' ******************************************************************

Pop:
        stackPtr = stackPtr - 1
        PEEK stackPtr,at

        ' This "BRANCH" will need to be edited to include all return
        ' labels defined by "Pushed<number>:" in the user program

        BRANCH at,(Pushed0,Pushed1,Pushed2,Pushed3,Pushed4)

' ******************************************************************
' *                                                                *
' *     End of program                                             *
' *                                                                *
' ******************************************************************


Why is there a GOSUB limitation ?

For most microcontrollers, a subroutine call involves the GOSUB pushing a "Return Address" ( the address of the instruction which follows the GOSUB ) to a stack and a RETURN pops the return address from the stack and then jumps to that location to continue program execution. On the PICAXE, things are done slightly differentlty.

To save limited memory within the PICAXE ( RAM / SFR ), the PICAXE numbers every GOSUB statement used ( 0 to 15 or 0 to 255 ) and it is this number, which forms part of the GOSUB instruction, which is pushed to the stack when the GOSUB is executed. The RETURN pulls this number from the stack and a lookup table is used to determine what the Return Address for that GOSUB actually is.

This allows the four level deep stack to be implemented using only a small amount of RAM/SFR memory - just two bytes for 16 GOSUB's and just four bytes for 256 GOSUB's - but it does require an additional Return Address Table to be placed within the Program Code area; 11-bits for 16 GOSUB's and at least 14-bits for 255 GOSUB's.

That the limitation exists is partly historical, and partly to allow the PICAXE firmware to be used in PICmicro devices which have limited RAM/SFR memory, and to ensure compatibility between the PICAXE variants.

Although a four subroutine deep stack would only need 16 bytes of RAM/SFR memory for any PICAXE which had up to 8192 bytes of program code ( approximately 2000 lines of source code ), and it would remove the limitation on the number of GOSUB's allowed entirely, it would very likely affect the range of addresses which the programmer can safely access using PEEK and POKE commands. Going to such a scheme, although it would solve one problem ( or perhaps more correctly, overcome a rare and "minor irritant" ), it would introduce a whole raft of other problems, especially as it would result in PICAXE's using different subroutine calling schemes.

It should also be remembered that, because the number of GOSUB's required within a program is usually relative to the size of program that can fit within the program code space, it is unusual for the GOSUB limitation to cause a problem, although there are particular types of program whose designs suggests multiple GOSUB's as the best method of implementation. For those, the solution is to use a PICAXE-18X, 28X or 40X which support up to 255 GOSUB's.


How do I check a One-Wire Device Checksum ?

The following code will allow you to confirm that the checksum of a Dallas Semiconductor One-Wire device ( such as the DS18B20 temperature sensor ) is correct. Developed with thanks to Peter H Anderson.

The checksum is an 8-bit CRC, using a polynomial of x8+x5+x4+x+1.

        SYMBOL DS18B20    = PinNumber

        SYMBOL byte       = b0
        SYMBOL crc        = b1
        SYMBOL bit        = b2
        SYMBOL k          = b3

        SYMBOL POLYNOMIAL = $8C     ' x8+x5+x4+x+1

        READOWSN DS18B20            ' Fills b6 .. b13

        crc = 0

        byte = b6  : GOSUB CrcAdd
        byte = b7  : GOSUB CrcAdd
        byte = b8  : GOSUB CrcAdd
        byte = b9  : GOSUB CrcAdd
        byte = b10 : GOSUB CrcAdd
        byte = b11 : GOSUB CrcAdd
        byte = b12 : GOSUB CrcAdd

        IF crc <> b13 THEN ChecksumFailed

        END

    CrcAdd:
        FOR bit = 0 TO 7
          k = byte ^ crc & 1
          IF k = 0 THEN CrcAdd1
          k = POLYNOMIAL
        CrcAdd1:
          crc = crc / 2 ^ k
          byte = byte / 2
        NEXT
        RETURN


How do I calculate an 8-Bit CRC ?

The following code will allow an 8-bit Cyclic Redundancy Check code to be generated. Developed with thanks to Peter H Anderson.

        SYMBOL byte       = b0
        SYMBOL crc        = b1
        SYMBOL bit        = b2
        SYMBOL k          = b3

        SYMBOL POLYNOMIAL = $8C     ' x8+x5+x4+x+1

        crc = $FF

        byte = "A" : GOSUB Crc8Add
        byte = "B" : GOSUB Crc8Add
        byte = "C" : GOSUB Crc8Add

        crc = crc ^ $FF

        ' crc is $C7

        END

    Crc8Add:
        FOR bit = 0 TO 7
          k = byte ^ crc & 1
          IF k = 0 THEN Crc8Add1
          k = POLYNOMIAL
        Crc8Add1:
          crc = crc / 2 ^ k
          byte = byte / 2
        NEXT
        RETURN


How do I calculate a 16-Bit CRC ?

The following code will allow a 16-bit Cyclic Redundancy Check code to be generated. Developed by Greg Newton with thanks to Peter H Anderson.

        SYMBOL crc        = w0  ' b0/b1
        SYMBOL k          = w1  ' b2/b3
        SYMBOL byte       = b4
        SYMBOL bit        = b5

        SYMBOL POLYNOMIAL = $A001

        crc = $FFFF

        byte = "A" : GOSUB Crc16Add
        byte = "B" : GOSUB Crc16Add
        byte = "C" : GOSUB Crc16Add

        crc = crc ^ $FFFF

        ' crc is $7AAF

        END

    Crc16Add:
        FOR bit = 0 TO 7
          k = byte ^ crc & 1
          IF k = 0 THEN Crc16Add1
          k = POLYNOMIAL
        Crc16Add1:
          crc = crc / 2 ^ k
          byte = byte / 2
        NEXT
        RETURN


How do I calculate a 32-Bit CRC ?

The following code will allow an 32-bit Cyclic Redundancy Check code to be generated. Developed with thanks to Peter H Anderson and Greg Newton.

        SYMBOL crch         = w0    ' b0/b1
        SYMBOL crcl         = w1    ' b2/b3
        SYMBOL kh           = w2    ' b4/b5
        SYMBOL kl           = w3    ' b6/b7
        SYMBOL byte         = b8
        SYMBOL bit          = b9

        SYMBOL POLYNOMIAL_H = $EDB8
        SYMBOL POLYNOMIAL_L = $8320

        crch = $FFFF
        crcl = $FFFF

        byte = "A" : GOSUB Crc32Add
        byte = "B" : GOSUB Crc32Add
        byte = "C" : GOSUB Crc32Add

        crch = crch ^ $FFFF
        crcl = crcl ^ $FFFF

        ' crch is $A383
        ' crcl is $0348

        END

    Crc32Add:
        FOR bit = 0 TO 7
          kh = 0
          kl = byte ^ crcl & 1
          IF kl = 0 THEN Crc32Add1
          kh = POLYNOMIAL_H
          kl = POLYNOMIAL_L
        Crc32Add1:
          crcl = crcl / 2 ^ kl
          kl = crch & 1
          IF kl = 0 THEN Crc32Add2
          crcl = crcl ^ $8000
        Crc32Add2:
          crch = crch / 2 ^ kh
          byte = byte / 2
        NEXT
        RETURN


My I2C program doesn't work

The first place to check is the Revolution Education Ltd datasheet on I2C communications at ...

And then to check ...

  • Have you got the I2C device wired up correctly ?
  • Have you got the right pull-ups in place ?
  • Are you using the correct pins ?
  • Is the I2C device powered up ?
  • Are you using the correct voltages ?
  • Have you set the I2C address pins correctly ?
  • Is any I2C device Write Protect disabled ?
  • Have you specified an I2CSLAVE command ?
  • Have you got the I2CSLAVE command options right ?
  • Are you sending the right I2C address ?
  • Is I2CSLOW or I2CFAST the correct option ?
  • Is I2CBYTE or I2CWORD the correct option ?
  • Are you sending the correct data ?
  • Are you sending the right amount of data ?
  • Do you have a delay after WRITEI2C ?
  • Are you verifying the data written correctly ?


PICAXE is a trademark of Revolution Education Ltd. These PICAXE pages are produced entirely independantly of Revolution Education Limited and may not reflect the opinion of Revolution Education Limited or its agents. The information provided is based upon and derived from information published by Revolution Education Limited, other sources of PICAXE information and the author's own experiments and prior experience. The views expressed by the author do not necessarily represent those of Revolution Education Limited or its agents. While every effort has been made to ensure that the information on these PICAXE pages is accurate and correct, the author can accept no responsibility for any errors or ommissions which do occur. The information provided is used entirely at your own risk.





Associated Articles

  The PICAXE Processors
  PICAXE News
  PICAXE Questions & Answers
  PICAXE Comparisons
  PICAXE Pinouts
  PICAXE Serial Interfacing
  PICAXE Infra-Red Interfacing
  PICAXE Wireless Interfacing
  PICAXE LCD Interfacing
  PICAXE LCD Interfacing
  A Real-Time Clock for the PICAXE-18X
  PICAXE Optimisations
  The PICAXE Birthday Box Project
  PICAXE Telephone Exchange Simulator
  The Brainf**ked PICAXE
  The PICAXE Extended Programming Interpreter
  Build Your Own Basic Stamp
  Tech Toys



Sites to Visit

  PICAXE Home Page
  Revolution Education Ltd

  Tech-Supplies Ltd



Site Navigation

  Home Page
  What's New
  Search
  Add Bookmark
  Have Your Say
  Guestbook




First published on Thursday the 25th of February, 2004 at 14:38:14
Last upload was on Monday the 23rd of August, 2004 at 00:37:27