<<Previous

Next>>

7. Other PICBASIC™ Considerations

7.1. How Fast is Fast Enough?

The PICBASIC™ Compiler generates programs intended to be run on a PICmicro MCU with a 4MHz crystal or ceramic resonator. All of the time sensitive instructions assume a 1 microsecond instruction time for their delays. This allows a Pause 1000, for example, to wait 1 second and the SERIN and SEROUT command's baud rates to be accurate.

There are times, however, when it would be useful to run the PICmicro MCU at a frequency other than 4MHz. Even though the compiled programs move along at a pretty good clip, it might be nice to run them even faster. Or maybe it is desirable to do serial input or output at 19,200 baud rather than the current top speed of 9600 baud.

PICBASIC™ Compiler programs may be run at clock frequencies other than 4MHz if you pay attention to what happens to the time dependent instructions. If you wish to run the serial bus at 19,200 as described above, you would simply clock the PICmicro MCU with an 8MHz crystal rather than a 4MHz crystal. This, in effect, makes everything run twice as fast, including the SERIN and SEROUT commands. If you tell SERIN or SEROUT to run at 9600 baud, the doubling of the crystal speed will double the actual baud rate to 19,200 baud.

However, keep in mind commands such as PAUSE and SOUND will also run twice as fast. The Pause 1000 mentioned above would only wait .5 seconds with an 8MHz crystal before allowing program execution to continue.

This technique may also be used to enhance the resolution of the PULSIN and PULSOUT instructions. At 4MHz these instructions operate with a 10 microsecond resolution. If a 20MHz crystal is used, the resolution is increased 5 times to 2 microseconds. There is a tradeoff however. The pulse width is still measured to a 16-bit word variable. With a 2 microsecond resolution, the maximum measurable pulse width would be 131,070 microseconds.

Going the other direction and running with a 32.768Khz oscillator is problematic. It may be desirable to attempt this for reduced power consumption reasons and it will work to some extent. The SERIN and SEROUT commands will be unusable and the Watchdog Timer may cause the program to restart periodically. Experiment to find out if your particular application is possible at this clock speed. It doesn't hurt to try.

The time dependent instructions are I2CIN, I2COUT, PAUSE, POT, PULSIN, PULSOUT, PWM, SERIN, SEROUT, SLEEP, and SOUND. It is possible to modify the actual library routines to maintain proper instruction timing at clock speeds other than 4MHz, although with some functions a fair bit of effort may be required.

7.2. Assembly Language

Assembly language routines can be a useful adjunct to a PICBASIC™ Compiler program. While in general most tasks can be done completely in PICBASIC™, there are times when it might be necessary to do a particular task faster, or using a smaller amount of code space, or just differently than the compiler does it. At those times it is useful to have the capabilities of an in-line assembler.

It can be beneficial to write most of a program quickly using the PICBASIC™ language and then sprinkle in a few lines of assembly code to increase the functionality. This additional code may be inserted directly into the PICBASIC™ program, added to the main library, or included as another file.

7.2.1. Programming in Assembly Language

PBC programs may contain in-line assembly; one or more lines of assembly code preceded by the ASM keyword and ended by the keyword and ended by the ENDASM keyword. Both keywords appear on their lines alone.

The lines of assembly are copied verbatim to the assembly output file. This allows the PBC program to use all of the facilities of PM, the PICmicro Macro Assembler. This also, however, requires that the programmer have some familiarity with the PBC libraries. PBC's notational conventions are similar to other commercial compilers and should come as no shock to programmers experienced enough to attempt in-line assembly.

All symbol names defined in a PBC program are similarly defined in assembly, but with the name preceded with an underscore ( _ ). This allows access to user variables, constants, and even labeled locations, in assembly. Similarly, system variable (such as W0) names are also preceded by underscores (such as _W0).

Thus, any name defined in assembly starting with an underscore has the possibility of conflicting with a PBC generated symbol. If conflict is avoided, can these underscored assembly values be accessed from PBC? No. Remember, the underscored names generated by PBC are only shadows of the actual information defined in the compiler. Since in-line assembly is copied directly to the output file and not processed by the compiler, the compiler not only lacks any type or value information about assembly symbols, it is completely unaware that they exist. If variables are to be shared between assembly and PBC, either use predefined system variables or define the variables in PBC.

Just as underscored symbols have possible conflicts, so do symbols not starting with underscores. The problem is internal library variables. Luckily, most library variables contain an '@' or make reference to one of the working registers (such as R0). Avoiding such names should be reduce problems. If you should have a name collision, the compiler will report the duplicate definitions as an error.

7.2.2. Assembly Language Examples

To write a byte to PORTA in PICBASIC™ you could simply:

Poke 5,B0  'Send whatever is in variable B0 to PortA (register 5)

But for code speed and size reasons you might write:

asm 'The following code is written in assembler
clrb RP0 ;Make sure we're pointing to the proper register page
mov 5,_B0 ;Send whatever is in variable B0 to PortA (register 5)
endasm

This code takes 3 words of code space and runs in 3 microseconds (with a 4MHz oscillator) as opposed to the PICBASIC™ statement which takes up a little more code space but takes several microseconds longer to execute. (The clrb RP0 isn't strictly necessary. The compiler normally clears that bit before returning from a library routine. But better safe...)

Note that in the assembly example above the variable B0 was preceded by an underscore ( _ ). By convention, PICBASIC™ labels and variables that are accessed in assembler must be preceded by an underscore. This is to keep lower level label assignments from interfering with PICBASIC™ labels and variables.

You might also note that the comment delimiter changed from the single quote (') in PICBASIC™ to the semi-colon (;) after the ASM command. Unfortunately, each language's syntax has a different requirement for this character.

Larger assembly language routines may be included in your program or in a separate file. If a routine is used by only one particular PICBASIC™ program, it would make sense to include the assembler code within the PICBASIC™ file. This routine can then be accessed using the CALL command.

'PICBASIC™ example program with embedded assembly language subroutines
loop: For B0 = 0 To 255 'Count up B0 in a For..Next loop
Call shiftout 'Call assembly routine to shift B0 out PortA
Next B0 'Do next count
Call shiftin 'Shift in a byte from PortA to B0
Serout 0,N2400,(B0) 'Send the byte out serially on Pin0
Goto loop 'Go do it all again
End

asm

'Assembly language code follows
 
_shiftout mov T0,#8 ;Setup temporary variable with bit count
soloop rr _B0 ;Get low order bit of data byte to carry
jnc solow ;If no carry then data bit should be low
setb PortA.0 ;Otherwise set data bit high
skip ;Skip over next instruction
solow clrb PortA.0 ;Set data bit low
setb PortA.1 ;Toggle clock high
clrb PortA.1 ;Toggle clock back low
djnz T0,soloop ;Loop to do all 8 bits
return ;Go back to main program when done
 
_shiftin mov T0,#8 ;Setup temporary variable with bit count
siloop setb PortA.1 ;Toggle clock high
clrb PortA.1 ;Toggle clock back low
clc ;Preset carry to off
snb PortA.0 ;If data bit is low then skip over next instruction
stc ;Else set carry to on
rl _B0 ;Roll bit into result byte B0
djnz T0,siloop ;Loop to do all 8 bits
goto done ;Exit through library routine Done
 

endasm

'End of assembly language code

Don't forget to put an END or GOTO or some other mechanism at the end of your PICBASIC™ code to keep the processor from "falling into" your assembler subroutines upon execution.

Also note that the function terminates by jumping to DONE rather than simply returning. Returning would be fine, but the DONE function performs other housecleaning needed by PBC (resetting the RP0 bit and hitting the Watchdog). W is not affected by done.

If the assembler routine is destined to be used by several PICBASIC™ programs, it makes sense to put it into its own separate text file and simply tell the assembler to INCLUDE this file.

'PICBASIC™ example program with assembler code included in a separate file

loop: For B0 = 0 To 255 'Count up B0 in a For..Next loop
Call shiftout 'Call assembly routine to shift B0 out PortA
Next B0 'Do next count
Call shiftin 'Shift in a byte from PortA to B0
Serout 0,N2400,(B0) 'Send the byte out serially on Pin0
Goto loop 'Go do it all again
End

asm

'Assembly language code follows
include "shift.inc" ;Assembly code will be inserted here

endasm

The third option for using assembly code with a PICBASIC™ program is to add the code right into the PBH.INC or PBL.INC files. These files are automatically included by the compiler.

This is the avenue most fraught with danger. These files are built in a particular manner and caution should be exercised before changing them. Also, as updates to the compiler are released, these files will change and your routines would need to follow the changes. If this is the path of choice, be sure to make copies of the original files and only work from those copies.

7.2.3. Placement of In-line Assembly

PBC statements execute in order of appearance in the source. The first executable line that appears is where the program starts execution. That statement literally appears in memory right behind the controller's startup code. Similarly, the END implicit at the end of every PBC program is accomplished by having the code for the END function appear first and unconditionally in the library. It appears in memory directly behind the user's last statements. This method saves two unneeded jumps and everything normally works out all right.

The tendency of programmers is to place their own library functions written using the inline assembler either before or after their code. In light of the above explanation, this could create some obvious problems. If they appear early in the program, the assembly routines execute prior to any PBC instructions (some programmers will invariably exploit this feature). If they appear at the tail of the program, execution which "falls off the end" of the PBC statements may mysteriously find themselves unintentionally executing assembly routines.

What should you do? Simple. Unlike a hosted system (such as the PC or the Mac), there is little reason for an embedded system to terminate. Thus, place your assembly routines after your PBC code. If you need to terminate your program, explicitly place an END statement at the end of your code rather then relying on the implicit END.

7.2.4. The PICmicro Macro Assembler

The PICmicro Macro Assembler (PM) included with the PICBASIC™ Compiler can use "8051 style" mnemonics or the Microchip mnemonics. The included header files which define the register and bit names, however, are in the 8051 form. This format includes the register name and bit name into one symbol. This makes it quicker and easier to write the code but also makes it somewhat incompatible with Microchip code that may be scattered through their data books.

It is a fairly simple matter to convert these symbols as they are typed in or use your text editor's or word processor's search and replace function to change them.

For example, if the Microchip code says:

bsf status,rp0 ;set bit rp0

the equivalent PM code would be:

bsf rp0 ;set bit rp0

The reason behind this change is that the symbol RP0 is defined to already include the information that it is in the Status register. See the PM include files, P16C84.INC for example, for a complete list of these symbol definitions for each PICmicro.

The PM instruction set is listed in the appendix of this document. For complete information on the PICmicro Macro Assembler, see the PM.TXT file on disk.

7.3. Interrupts

Interrupts can be a scary and useful way to make your program really difficult to debug.

Interrupts are triggered by hardware events, either an I/O pin changing state or a timer timing out and so forth. If enabled (which by default they aren't), an interrupt causes the processor to stop whatever it is doing and jump to a specific routine in the PICmicro called an interrupt handler.

Interrupts are not for the faint of heart. They can be very tricky to implement properly, but at the same time they can provide very useful functions. For example, an interrupt could be used to buffer serial input data behind the scenes while the main PICBASIC™ program is off doing something else. (This particular usage would require a PICmicro with a hardware serial port.)

The PICBASIC™ Compiler does not directly support interrupts, but that does not mean they cannot be used. The PICBASIC™ library routines are not reentrant. This means that you cannot execute PICBASIC™ statements from within an interrupt handler - the interrupt handler must be written in assembly language. This statement alone should be enough to strike fear into the heart of the BASIC programmer.

However, if you just gotta do it, here are some hints on how to go about it.

When an interrupt occurs, the PICmicro stores the address of the next instruction it was supposed to execute on the stack and jumps to location 4. The first thing this means is that you need an extra location on the PICmicro stack, which is only 8 deep to begin with.

The PICBASIC™ library routines can use up to 4 stack locations themselves. The remaining 4 are reserved for CALLs and nested BASIC GOSUBs. You must make sure that your GOSUBs are only nested 3 deep at most with no CALLs within them in order to have a stack location available for the return address. If your interrupt handler uses the stack (by doing a Call itself for example), you'll need to have additional stack space available.

Once you have dealt with the stack issues, it gets even trickier. Since you have no idea of what the processor was doing when it was interrupted, you have no idea of the state of the W register, the Status flags, PCLATH or even what register page you are pointing to. If you need to alter any of these, and you probably will, you must save the current values so that you can restore them before allowing the processor to go back to what it was doing before it was so rudely interrupted. This is called saving and restoring the processor context.

If the processor context is not left exactly the way you found it, all kinds of subtle bugs and even major system crashes can and will occur.

This of course means that you cannot even safely use the compilers internal variables for storing the processor context. You cannot tell which variables are in use by the library routines at any given time.

We have reserved three internal variables strictly for use by an interrupt handler: I0, I1, and I2 (Only I0 has a real RAM location on a PIC16F84. Use a PICmicro with more RAM such as a PIC16F84 for access to the additional locations.) If you require more than these variables, you can reserve as many of the user variables (B0, B1, ...) as you like by simply not using them in your PICBASIC™ program. You may use these locations to store W, the Status register, or any other register that may need to be altered by the interrupt handler.

The interrupt routine should be as short and fast as you can possibly make it. If it takes too long to execute, the Watchdog Timer could timeout and really make a mess of things. Perhaps the most useful function would be to simply set a PICBASIC™ variable to a particular value when an interrupt occurs. When the PICBASIC™ program sees this value, it knows an interrupt has occurred and it can take the appropriate actions.

The interrupt handler itself may be placed in the file PBH.INC in the INC subdirectory. Near the end of the file is the label Start. This is where the PICmicro MCU will start executing code on reset, location 0. A few lines down is a section of code that has been commented out. If this code is uncommented, it will enable you to insert your interrupt handler at location 4. Place your assembly language interrupt handler routine after the ORG 4. The routine should end with an RETI instruction to return from the interrupt and allow the processor to pick up where it left off in your PICBASIC™ program.

From this point you are on your own. If you follow the above suggestions, your interrupt routine should be able to coexist peacefully with your PICBASIC™ program.

Definitely, definitely, definitely refer to the Microchip PICmicro MCU data books for additional information on how to use interrupts. They give examples of storing processor context as well as all the necessary information to enable a particular interrupt. This data is invaluable to your success.

7.4. Life After 2K

When a PICBASIC™ program grows longer than 2K (which is a pretty big program), problems will occur. PICmicro MCU instructions such as CALL and GOTO only have enough bits within them to address 2K of program space. To get to code outside the 2K boundary, the PCLATH register must be set before each Call or Goto.

In the interest of minimizing program size, the PICBASIC™ Compiler makes no effort to address this problem. The 2K boundary presents a problem to PICmicro MCU programming in any language; assembly, C, or PICBASIC™.

If it is necessary for a program to be longer than 2K, the .ASM file the compiler generates can be "fixed-up" and then assembled after setting PCLATH before and after the appropriate instructions. It would probably be best to use memory beyond 2K for large table storage to keep "fix-ups" to a minimum.

See the Microchip PICmicro MCU data books for more information on PCLATH and address sizes.

<<Previous

Next>>