BS2sx, BS2e and BS2p application notes

(c) 1998 , 2001 EME Systems, Berkeley CA U.S.A.
<stamp index> <home>

contents (updated 1/5/2002)


Introduction

BS2 family portraitThe BS2SX, the BS2e and the new BS2P offer more program memory and more RAM than the original BS2. The BS2SX and BS2P also offer higher speed. The BS2P offer a wealth of new commands. including formatted LCD output, support for I2C and Dallas one-sire protocols, and direct cross-bank addressing of the entire 16kbyte data space.

The photo shows the gold BS2P in both the 40 pin package with a total of 32 general purpose i/o pins and the compatable 24 pin package which has 16 general purpose i/os..

Refer to the following parallax documents, free downloads from the Parallax web site, for information about the BASIC Stamp IIsx and the BASIC Stamp IIe.

BASIC Stamp manual, version 2.0
This has the up to date information on the multibank Basic Stamps
 
Windows stamp interface version 1.1
Windows stamp interface version 1.3
Look in the downloads section at Parallax. The most recent version is 1.3, which is in advanced beta testing stage. Version 1.3 has very nice features, so use it if you can. Version 1.1 is still available if you have problems with 1.3. (Version 1.1 does work fine with my Mac setup)

I have no problems with it on my normal desktop and Compaq laptop PC running Windoze 95. But I cannot use it on my MacG3 under virtual PC, reason unresolved. It runs, and it will program BS2s, but not any of the multibank Stamps. I also found that I could not program Stamps with it on one cheap Toshiba laptop.

 
Tip: The installer is a large (3.5 megabyte) program that can be problematic if you do not have a good internet connection. The Stampw.exe program does not really need any of the additional help files or TSRs for its operation. The version 1.3 Stampw.exe is a 1.2 megabyte program file that you can simply install by dragging it where you want. It can be zipped down to 0.5 megabyte.

There is also a DOS version of the software, a separate program for each flavor of Stamp. Stamp.exe, stamp2sx.exe, stamp2e.exe and stamp2p.exe. Please see below for a side by side procedure for using the DOS or the Windows software to load a program into the BS2sx.

Frequently asked questions

Find this in the downloads section at the Parallax site. This FAQ was recently revised at Parallax to cover and compare the BS2, BS2sx and BS2e, and it soon should cover the BS2P as well. Well worth looking at.


Example BS2SX test program, showing variable allocation

top

People who have never used the BS2SX before are often confused by the process of loading programs into the different banks, and by the process of defining variables and passing control from one program bank to another. The BS2SX documentation comes with a short demo program, but it is a little too short to illustrate all the important features. Here is an alternative demo program that shows more about passing variables in the RAM and in the scratchpad ram, and also how to define the convenient headers that tie together a BS2SX "project" within the STAMP2W.EXE software.

Program for bank 0. Name it, "TESTSX.BSX". Note the $STAMP directive that sets up a multi-bank project. The directive is understood only by the STAMP2W.EXE software, and is ignored by the DOS version, STAMP2sx.EXE software. Use $STAMP BS2e for those BS2e projects, with file extensions ".bse".

' {$STAMP BS2SX,testsx1.bsx,testsx2.bsx}
' for bank 0
' this bank initializes 3 ram variables
' and 3 scratchpad ram variables
' then jumps to bank 1 to display the values
' subsequently the project jumps back and
' forth between banks 1 and 2 to recalculate
' and to redisplay the values
'
x1 var word   ' define variables
x2 var word   ' same position as in banks 0 and 3
x3 var word
i  var byte   ' will be pointer to scratchpad ram
   
get 63,i      ' get the current bank number
debug cr,"testing bank ",i,cr  ' show it
x1=8000       ' initialize ram variables
x2=55
x3=-1
for i=0 to 5  ' initialize scratchpad values
  put i,i*5     ' to 0,5,10,15,20
next
run 1         ' run program in bank 1

Program for bank 1. Name it, "TESTSX1.BSX". This part of the project displays the values initialized in bank0 or the values recalculated in bank2. This does not require the $STAMP directive. It is just put here for reference.

' {$STAMP BS2SX}
' for bank 1
' program displays:
' 1    x1   x2   x3
'      sp0  sp1  sp2  sp3  sp4
' the first 1 is the number of this bank
' the values will be modified by the 
' program running in bank 2
'
x1 var word   ' define variables
x2 var word   ' same position as in banks 0 and 3
x3 var word
i  var byte   ' will be pointer to scratchpad ram
x  var byte   ' general purpose byte
get 63,x      ' get the current bank number
debug dec x, tab,dec x1,tab,dec x2,tab,dec x3,cr
for i=0 to 4  ' 5 values from scratchpad ram
  get i,x       ' get byte
  debug tab,dec x
next
debug cr
pause 2000
run 2         ' go to bank 2 to recalculate values

Program for bank 2. Name it, "TESTSX.BSX". This recalculates values, and then jumps back to bank 1 to display them.

' {$STAMP BS2SX}
' for bank 2
' program adds one to each ram variable
'   x1,x2,x3
' subracts one from each scratchpad variable
'   scratchpad locations 0,1,2,3,4
' then jumps back to bank 1 to display
   
x1 var word   ' define variables
x2 var word   ' same position as in banks 0 and 3
x3 var word
i  var byte   ' will be pointer to scratchpad ram
x  var byte   ' general purpose byte
   
x1=x1+1       ' increment ram variables
x2=x2+1
x3=x3+1
get 63,x      ' get current bank #
debug "bank ",dec x,cr ' show it for the record
for i=0 to 4  ' get 5 scratchpad values
  get i,x       ' get value
  x=x-1         ' decrement it
  put i,x       ' put it back
next
pause 1000
run 1         ' now to bank 1 to display


Using the DOS and Windows software with the BS2SX and BS2e

top

The way to load and run the program depends on which version of the software you are using.

DOS, STAMP2SX.EXE or STAMP2e.EXE or STAMP2p.EXE
Enter the first program above, press ALT-0 to target bank0, then press ALT-R to send the program to bank0 of the BS2SX and to run it. But it won't get very far, past the message "testing bank 0", because you haven't yet entered the second and third programs. Do that now. Save the first program under the name "testsx0.bsx". Now you can type in the program for bank1, or modify the first one. When finished, Save this program as "test1.bsx". Press ALT-1 to target it to bank1, and press ALT-R to send it and run it. You should see the "testing bank 0", followed by a printout of the initial values. Then, you guessed it, type in the program for bank2, and save it as "test3.bsx". Press ALT-2 to target it to bank2, and press ALT-R to send it and run it. Now you should see the whole action. It should go into a repeating pattern as the program alternates between bank 1 and bank 2. Pressing the reset button always runs the program in bank 0 first. It doesn't matter what you name the programs. There is no real association between them, except in your own planning. Any time when you load in a programs, one at a time, you must press the correct ALT-n command to target the program at hand to the proper bank, n, before you press ALT-R to actually send and run it. You can use an extension other than .bsx, but if you do, the dialog that comes up when you press ALT-L to load a new program will not display the files that do not have the .bsx extension.
WINDOWS, STAMP2W.EXE version 1.1
In the menu for preferences/editor operation, tell it you will be editing a BS2SX program in the dropdown menu. Also tell it to use either the "all" or the "modified" option for program download in another dropdown menu. Enter the first program in one window, then the second and third programs, each in a new window. Save and name the programs with the specific names that appear in the $STAMP directive above, using the .BSX or .BSE extension. These names above are test0.bsx, test1.bsx, and test3.bsx. Now you can close all of the programs. Now open the program for bank0, "test0.bsx". If you have entered the $STAMP directive correctly with all of the associated file names, then all three windows should pop up automatically. You have a "project" consisting of three windows for the three banks. When you press CTRL-R or choose RUN from the menu, all three of the programs will be loaded into the BS2SX, and will run from the start of program 0. When you choose to quit or to close one of the programs, the editor will ask if you want to close all of the windows, because they are now part of a unified project. Note that if you want to create a program for bank 2, you first have to create programs for banks 0 and 1. If you want to leave a bank with nothing in it, you at least have to enter one dummy statement, like DATA 0, or the editor will not allow you to tokenize the programs.
 
WINDOWS, STAMP2W.EXE version 1.3
This is the same as version 1.1, but it insists that you put in the $STAMP directive to identify the type of stamp you are working with, and it does a good job of checking on what kind of stamp is actually attached to the serial port. This version also has the capability to create a self-standing loader routine, that can be used to encapsulate a Stamp program as an executable that can program a stamp or a stache at a remote site, without need to see the program code or to mess with the main program settings. Great for OEM users of the stamp.

The information about programming the BASIC Stamp 2SX, 2e or 2p used to be spread between the thin hardware manual, which contains information about the DOS software, and the thin manual for the Windows version 1.096 of the programming software. The latest version of the manual is 2.0, which covers all of the Stamps, including the new BS2P. The

It is important to name the program you load with the proper extension for the stamp being targeted. You have three choices:

Every program associated with a BS2sx or BS2e or BS2p project should have the appropriate extension attached. Once you give a program a name, with one of those extensions, then it will always target the correct flavor of stamp when you go to run the program. In the windows software that is regardless of the setting for default stamp type under the editor operation tab.

The default stamp type under the editor operation tab applies only to new programs that you have just created and not yet named and saved to disk, or to programs that do not have one of the above extensions, .txt for example. Once a program has one of the above extensions, the extension overrides the default setting. Thus, you can have programs targeted to different flavors of stamps open in the editor window at the same time, and STAMPW.EXE will attempt to connect to a stamp based on the file name extension at RUN time. If the type of stamp connected does not match the file extension (or the default setting if applicable), then the editor will return an error message that the "Stamp of type xx has not been found".

The directive {$STAMP BS2..,....,} has an even higher priority. That is, if a project has a directive that says the project is for a BS2e, and the program is named with the BS2 extension, it is the $STAMP directive that will take precedence, and the file extension will be ignored. Of course, it is a good idea to keep the file names and the stamp directives consistent with one another. It is a good practice to include the $STAMP directive, now that there are so many flavors of stamps available. The version 1.3 STAMPW.EXE IDE enforces the consitency of the $Stamp directive.


Defining "global" and "local" variables in RAM and Scratchpad

top

People often ask how to define variables that are to be shared between banks in the BASIC Stamp 2SX or 2e when the programs get large and complicated. There are a couple of ways to do this. I prefer to take advantage of the automatic variable allocation that the STAMPW compiler provides. Declare enough variables as words first to accommodate all of the shared variables, and then create alias names for parts of those words to define shared bytes, nibs and bits. That puts all of the shared "global" variables in the first few words of memory. Then "local" variables can be defined for each bank as needed. They take whatever ram is left above the "global" variables.

It is important to understand that this works because the Stamp always allocates space for all the words you define in a program, in order, before it allocates space for bytes, then nibs, then bits. You can see this in the memory map of RAM that comes up when you hit CTRL-M. For example, if you have define a word, then a byte, then another word, the two words will still come first in memory, before the byte. If in another bank you need to define another word, it will slip in under the byte. However, if you define a word, and then define the byte as an alias name for part of that word, you can count on it staying in the same place in memory. That is, if you define the same essential words in the same order in each and every bank.

No matter how you cut it, when you get deep into programming the BS2SX, you have to think very carefully about how the variable space is allocated. It requires a very different way of thinking from the vanilla BS2.

For example, here is the start of the variable headers for one particular program. Note that there are three words defined, sxw, appw, and datpnt. Then by using aliases, the first two of these words are parsed into numerous named bytes, nibs and bits. Each of these parts serves some definite purpose (or in some cases multiple purposes) in the programs running in the different banks. For example, the config byte contains 8 one-bit status flags that are passed without change from bank to bank.

' ------- variables & aliases:
sxw    var   word        ' variable for bank switching
sxgo   var   sxw.byte0   ' byte for bank switching, two nibbles
sxgo2  var   sxgo.nib1   ' contains number of bank to run
sxaux  var   sxw.byte1   ' auxiliary for passing data between banks
ix     var   sxaux.nib0  ' nibbles for local index
jx     var   sxaux.nib1  ' note aliases
   
appw   var   word                ' variable for application status
config var   appw.byte0       ' configuration and status bits
LFb    var   config.bit0       ' =1 sends CRLF, =0 sends CR only
vuflg  var   config.bit1       ' view mode=0 shows data on screen at short intervals
zpflg  var   config.bit2       ' zip mode=1 scans with no sleeppower on
lgflg  var   config.bit3       ' =0 during each logging scan, e.g. once/hour
jbit   var   config.bit4       ' for rollover of time
lowbv  var   config.bit5       ' battery low=1
memry  var   config.bit6       ' memory low=1
mnflg  var   config.bit7       ' =0 when minute rollover
lgint  var   appw.byte1        ' logging interval in minutes. divides 1440
                               ' 1,2,3,4,5,6,10,12,15,20,60,90,120,240
                               ' this is read from eeprom lgint0 at startup
datpnt var   word              ' for pointer to current logging memory

Note in thinking about the above, where the STAMPW locates the variables. If you were to define these bits directly, e.g.

LFb    var  bit    ' define a bit, compiler puts it above words, bytes & nibs

instead of as aliases inside a word variable, the STAMPW compiler would move them up to the end of the variable space. It would put them after all of the words, bytes, and nibs. Adding any additional words, bytes or nibs would move the position of those bits in memory. In contrast, by defining the bits as alias names inside a word, we can be assured that they will stay put. The STAMPW compiler always puts the word variables in RAM first, in the order that they are defined.

Another alternative for working with global variables is to use the fixed variable names that are defined by STAMPW to refer to the locations in physical memory. (manual v1.9 page 214). For example, w12 is a predefined name that refers to the last word in the physical memory, and b25 similarly refers to the last byte. You can use those predefined names to put the "global" values at the end of memory. Then use the normal method to define the temporary variables that will be local to each bank. When you need to define nibs and bits, you still have to use aliases, for example,

sxgo   var  b22     ' define a byte in an absolute position in memory
LFb var w13.bit0    ' define a bit in an absolute position

Keep in mind that the automatic variable allocation will not pay any attention to your use of the fixed variables. If you go ahead and define 13 words in a program, that last word will quite happily exist right on top of w13 with no warning that there is an overlap.


Scratchpad RAM

top

The scratchpad ram is shared between all of the BS2e and BS2SX programs. Here is an example of variable definitions from one program. Note that the addresses are absolute byte addresses, from 0 to 62. The STAMP2W compiler does not do any kind of automatic allocation in the scratchpad area.

stack       con   62            ' bank-switching, return address
spaux       con   61            ' auxiliary bank-switching
spaz        con   60            ' reserved
spee1       con   59            ' high byte of log eeprom address
spee0       con   58            ' low byte of log eeprom address
' -------***scratchpad ram allocation for data buffer
hxi    con   0      ' humidity inside b
hxo    con   1      ' humidity outside b
tci    con   2      ' temperature inside w
tci1   con   3
tco    con   4      ' temperature outside w
tco1   con   5
dpi    con   6      ' dew point inside b
dpo    con   7      ' dew point outside b
bar    con   8      ' barometer w
bar1   con   9
ran    con   10    ' rainfall w
ran1   con   11
wsx    con   14    ' windspeed maximum value w
wsx1   con   15
wsa    con   12    ' windspeed average w
wsa1   con   13
wdr    con   16    ' wind direction w
wdr1   con   17

As with the ram variables, if you want to use the same definitions in several banks of a BS2SX or BS2e project, you have to type in or copy the same list of variable definitions at the top of each program in the project. And if you make any changes in the list, you have to make that same change in all the banks. (It sure would be handy to have an INCLUDE directive in PBASIC!)

The scratchpad buffer is used extensively to transfer data between banks. For example, a program in one bank can READ bytes from eeprom, and PUT those bytes into a buffer in scratchpad RAM, and then RUN a program in another bank, which can use those bytes in some way.

The scatchpad can also be used as a stack, for advanced bank switching applications. More below.


Crossbank "calls"

top

The BS2SX has 8 separate program banks. The RUN n command allows a running program to jump from bank to bank. The example above shows how a program running in bank zero can run or chain to a program in bank 1. The example assumes that there is only one program in each bank. If there is more than one, life is more complicated. What if you want a program to jump from a point in the middle of one bank to a point in the middle of another bank, do something, and then return back to the first bank to continue where it left off. The RUN command by itself does not take care of the details of getting there and back and it does not allow for the possibility of more than one destination in each bank.

Here is an approach that works for what I do. Something like this is pretty much essential if you want to look at the 16kbytes of program memory in the BS2sx or BS2e as space for one big program, as opposed to just 8 independent 2kbyte spaces that do not need to intercommunicate.

I use the word "call" loosely. The RUN command trashes the BS2SX subroutine stack, so I have implemented a supplementary return "stack" using a byte #62 in the scratchpad memory area. This is only a one level stack. Okay, it would be possible to implement a deeper stack, but, hey! KISS. Also, I have limited the cross-bank "call" to 16 possible labels in each bank. So far I haven't needed more, but this number is very easy to expand using an additional variable. A special byte in RAM holds in its low nibble the number (0-->7) of a target bank, and the same variable also holds in its high nibble an index for a target routine. When the program finds itself booting up in a new bank, a BRANCH instruction takes execution to a routine pointed to by that variable. On return from a "call", the contents of scratchpad byte #62 is moved into that same special RAM variable before the RUN instruction is executed.

In the BASIC Stamp the contents of both the main RAM and the scratchpad RAM are preserved through the RUN instruction. So programs running in different banks can share those variables. There are precautions you must take when defining variables in the different banks, so that the important ones will the same in all the banks. Discussed above.

When you reset the stamp or power it up, all RAM is zeroed. When the program finds itself booting up in bank zero, from a power up, then the designated branch variable will equal zero. In the scheme that I use, that takes the program into its initialization routine.

You cannot use a crossbank calls from within a subroutine. All crossbank calls have to be made from the main program level. The RUN command trashes the subroutine stack. The RUN command also trashes for-next logic. You can not use these crossbank calls from within a for-next loop. You can always work around that limitation by making your own for-next logic in program code.

Here is a bare bones example of how the above is implemented. First the program for bank 0:

' {$Stamp BS2sx,switch1.bsx}
' this is program switch0.bsx for bank 0
' bank switching variables are declared as part of a word variable,
' so they will come first in RAM:
wsx  var word        ' word variable defined 1st comes 1st in RAM
sxgo  var wsx.byte0  ' need a byte for bank switching to a target
sxrun var sxgo.nib1  ' bank number (0 to 7) of the target routine
sxgo2 var sxgo.nib0  ' index number (0 to 15) of routine in other bank
sxret var wsx.byte1  ' to hold the return vector (or stack)
   
' bank 0 targets:
xreset   con $00    ' bank 0, target #0 -- reset vector
xhere1   con $01    ' bank0, target #1
   
' bank 1 targets:
xother0  con $10    ' bank 1, target #0
xother1  con $11    ' bank 1, target #1
   
branch sxgo2,[reset,here1]
   
reset:
  debug "here we are, reset!",cr 
  sxret=xhere1    ' we want to return to here1 in this bank
  sxgo=xother1    ' we want to run routine #1 in bank 1.
  run sxrun       ' do it!
here1:          ' and come back here
  debug "now continue in bank 1 where we left off",cr
end   ' just end it here!
   

Now the program switch1.bsx for bank 1:

' {$Stamp BS2sx}
' this program switch for bank 1
' variables defined the same in both banks:
wsx  var word        ' word variable defined 1st comes 1st in RAM
sxgo  var wsx.byte0  ' need a byte for bank switching to a target
sxrun var sxgo.nib1  ' bank number (0 to 7) of the target routine
sxgo2 var sxgo.nib0  ' index number (0 to 15) of routine in other bank
sxret var wsx.byte1  ' to hold the return vector (or stack)
   
' bank 0 targets:
xreset   con $00    ' bank 0, target #0 -- reset vector
xhere1   con $01    ' bank0, target #1
   
' bank 1 targets:
xother0  con $10    ' bank 1, target #0
xother1  con $11    ' bank 1, target #1
   
branch sxgo0,[other0,other1]  ' first always branch on sxgo
   
other0:
  debug "here is other0, but we won't do it.",cr
  sxgo=sxret        ' restore the stack value, both bank # and target #
  run sxrun         ' return where we left off
   
other1:
  debug "here we are other1!",cr
  sxgo=sxret        ' restore the stack value, both bank # and target #
  run sxrun         ' return where we left off

The call always involves putting some value on the "stack", which here is just one byte, "sxret", a variable in RAM (or, could use scratchpad RAM instead, see below). Also the preparation for the call puts the desired target address into the variable "sxgo". The high nibble of sxgo, which is named, "sxrun", holds the bank number, while the low nibble, named "sxgo2", holds the index of the routine to run once we arrive at the other bank. Now the Stamp executes RUN sxrun. The Stamp switches over and starts executing code in the new bank. The contents of the variables in RAM and scratchpad RAM are unaffected. The first command in the new bank is a branch, which uses the low nibble of sxgo to branch to the specified target address in that bank. The the low nibble of target is an index, not an actual address. The branch commands maps the index to an actual address. The routine at the target address executes. At its completion, it moves the value from "sxret" into the bank-switching variable sxgo, then it hits the command, RUN SXRUN. That causes the Stamp to start executing code in the original bank. And the branch instruction there takes the program back to the predetermined target address.

The programs can to put whatever address on the stack. It does not have to be a return to continuing code. It can branch to some third location, or it can cause a reset to the program top, or it can chain to a program in a different bank.

I usually put the return address in the scratchpad RAM. In that scheme "stack" is an address in the scratch RAM, and the operation is a PUT in the main program and then later a GET in the second program.

'in both banks
stack   con 62   ' scratchpad ram location to hold return address
   
'... in bank 0
sxgo=xother1       ' where to go in the other bank
put stack,xhere1   ' where to come back to
run sxrun          ' do it!
here1:             ' come back here
   
'... in bank 1, having done its thing
get stack,sxgo     ' retrieve return address
run sxrun          ' go there
   

Remember that crossbank calls cannot be done from within subroutines nor from within for-next loops. Well, they can be done, but the stack or nesting will not be preserved. If a repeating loop is necessary with a cross-bank call, the index and loop must be managed as an ordinary variable.

The overhead for the bank switching takes execution time. The RUN command on the BS2sx requires 350 microseconds, and a whopping 875 microseconds on the BS2 or BS2e. That combined with the BRANCH command and the other setup instructions makes the whole sequence take a couple of milliseconds. Does that matter? Maybe, maybe not. If timing is tight, minimize the number of transitions from bank to bank. Accumulate information in a buffer and then transfer it all at once with one cross-bank call, instead of making a series of cross-bank calls for each individual item. That applies to data logging for example. To get the fastest execution time, it is better to duplicate commonly accessed routine in several banks and thus avoid the cross-bank calls. For example, if code in several banks has to read a real time clock or external memory, it may be better to duplicate the necessary routines in all the banks that need them. Break programs into units that can stand pretty much on their own.

 

Crossbank "calls" using a scratchpad stack

top

It is possible to extend the logic to use a true stack pointer. I set aside the first byte in RAM as a stack pointer, and the second byte as the cross-bank variable.

wsx    var word       ' 1st word assigned in all main ram banks
stack  var wsx.byte0  ' stack pointer--do not use for anything else!
sxgo   var wsx.byte1  ' for bank switching

Also, in the scatchpad RAM, I set aside an area to be used as a stack, and initialize the stack pointer to point to that address:

stacktop    con 62   ' will be initial value of stack pointer
                     ' in scratchpad RAM, stack works down
                     ' reserve addresses 33 to 62
stack=stacktop       ' initialize the stack

 

When calling a new bank, the return address (as above) is put at the address in spRAM specified by the stack pointer, and the stack pointer variable is then decremented to reference the next free stack location. On return from the call, the return address is popped from the stack and the pointer incremented. This is potentially more versatile, as it can be used for data as well as for return addresses.

On the down side, all of this bank-switching and stack mechanism does require a lot of extra execution time in the Stamp and a modest amount of additional code. There are extra steps required to manage the stack pointer. The process of storing and restoring many bytes of data can eat up 10s of milliseconds. But that may not matter in comparison to the great transparency of operation it affords, and those milliseconds are nothing in some applications. Moreover, the stack mechanism demands extra programming and debugging care. The stack mechanism is not handled automatically (as it is in many micros) by the Stamp, and the IDE does not help with error messages. Rather, managing the stack is uniquely the responsibility of the person doing the programming. It is easy to make mistakes with the stack pointer and have confusing overruns. In practice, each program requires ad-hoc adjustments to the technique with tradeoffs between speed and maintain-ability.

There was an extended discussion on the stamps discussion list about this between myself and Peter Verkaik and others in May, 2001. Peter's take on this issue is posted on the FAQ at http://www.wd5gnr.com/stampfaq.htm.

The following is a demo based on the ideas presented in the previous section. This demo switches to bank 1 to input a string from the serial port, or user keyboard, which is burned into data eeprom in bank 1 before returning to bank 0. The program in bank 1 saves the entire contents of main ram on the stack before it does the serial input. Imagine the need to receive a string of data quickly, as for example, a GPS sentence. The entire main ram is needed to receive the serial string of length up to 24 bytes. The bank 1 routine restores the 24 original variables before it returns to bank 0. A second routine in bank 1 is xbank-called from bank 0, to display the stored message.

' {$Stamp BS2e,stack1.bse}
'---------------------------------------------
' bank0 of project stack.bse
' demo of scratchpad stack for crossbank calls
' Tracy Allen'---------------------------------------------
'
' --- scratch pad assignments
stacktop    con 62   ' will be initial value of stack pointer
   
' --- variable assignments
wsx    var word       ' 1st word assigned in all main ram banks
stack  var wsx.byte0  ' stack pointer--do not use for anything else!
sxgo   var wsx.byte1  ' for bank switching
sxrun  var sxgo.nib1  ' target bank for RUN
sxgo2  var wxgo.nib0  ' target routine from BRANCH within bank
cat    var sxgo       ' this byte can be reused within bank
   
X      var nib      ' for demo, preserved across banks
                    ' an index for the message to retrieve
   
' --- crossbank targets
xtop          con $00     ' bank 0 targets
xgetmessage   con $01
xshowmessage  con $02
xtest         con $03
xdispmsg      con $10     ' bank 1 targets
xgetmsg       con $11
   
' ----------------------------------------------------
' --- top of program
' sxgo is $00 at reset ---> branch to top
branch sxgo2,[top,getmessage,showmessage,test]
   
top:
  stack=stacktop       ' initialize the stack
  '...
   
getmessage:            ' get a new message from the serial port
  X=0
  sxgo=xgetmsg         ' target in another bank
  put stack,xshowmessage   ' return target in this bank
  stack=stack-1
  run sxrun            ' go get the new message
                       ' then return here to continue
   
showmessage:
  X=X+1                ' select a message
  debug ? X
  sxgo=xdispmsg        ' target in another bank
  put stack,xtest      ' stack the return target in this bank
  put stack-1,X        ' put the parameter X on the stack too
  stack=stack-2        ' 2 bytes have been "pushed"
  run sxrun            ' go show the message #X
   
test:
  if X=4 then getmessage
  
   
' {$Stamp BS2e} '--------------------------------------------- ' bank1 of project stack.bse ' demo of scratchpad stack for crossbank calls ' Tracy Allen '--------------------------------------------- ' ' data, messages in eeprom msg0 data "!"(25),0 ' reserve space for user message msg1 data "this is a switch!",0 msg2 data "not even Houdini could escape.",0 msg3 data "back to the future.",0 ' variable assignments wsx var word ' 1st word assigned in all main ram banks stack var wsx.byte0 ' stack pointer--do not use for anything else! sxgo var wsx.byte1 ' for bank switching sxrun var sxgo.nib1 ' target bank for RUN sxgo2 var wxgo.nib0 ' target routine from BRANCH within bank cat var sxgo ' this byte can be reused within bank char var byte ' ---------------------------------------------------- ' --- top of program branch sxgo,[getmsg,dispmsg] ' --- display the stored message ' this routine uses only two bytes of memory in addition to "stack" ' it recycles the variable "sxgo", alias "cat", as the message pointer ' and puts one byte on the stack to free up a byte for "char" dispmsg: put stack,b2 ' save the variable b2, same as char get stack+1,cat ' retrieve the index parameter lookup cat,[msg0,msg1,msg2,msg3],cat ' map to message start address dispmsgloop: read cat,char ' read one character if char=0 then dispexit ' stop at first null debug char ' show it cat=cat+1 ' next address in message goto dispmsgloop ' more dispexit: ' done debug cr ' end of line get stack+2,sxgo ' retrieve the return vector stack=stack+2 ' parameter and return are "popped" run sxrun ' do it, return to bank 0 ' --- get a user message and store it in eeprom in this bank ' this routine uses all the main ram as a data buffer, so ' it stores the preexisting variables on the stack and ' then restores them at the end getmsg: ' --- first save all of main ram on stack ' it recycles the variable "sxgo", alias "cat" as the loop index ' for saving all of the memory bytes from b2 to b25 absolute. for cat=0 to 23 put stack-cat,b2(cat) ' save all bytes in ram memory next ' --- now input a string, up to 24 bytes or CR ' if 30s timeout, then just retain the original message serin 16,$54,30000,getmsg2,[str char\24\CR] ' --- now put the string into eeprom as new msg0 for cat=0 to 23 write cat+msg0,char(cat) if char(cat)>0 then getmsg1 cat=23 ' bail out if it is a null getmsg1: next ' --- now restore the original variables and return getmsg2: for cat=0 to 23 get stack-cat,b2(cat) next get stack,sxgo ' get the return vector stack=stack+1 ' return address is "popped" run sxrun ' go back to bank 0 target

In the above "getmsg" routines, when saving and restoring the variables, I referred to the absolute variables b2(). That always refers to the third and successive bytes in the main ram. I could just have well used char() there. But this way emphasizes that saving and restoring operations on the stack really have nothing to do with the naming or use of the variables.


Example of cross-bank calls, double precision math library

top

The following demo sets up several math routines in bank 1, so that a program running in bank 0 can call them. The main program in bank 0 simply asks the user for two double precision numbers as input, and prints out the results of addition, subtraction, multiplication and division. Please refer to a separate article for explanation of the double precision math routines. The division is limited to divisors less than $7fff, and not zero, so the routine returns an error message in the scratchpad RAM if there is a problem there.

In the following program there are references of the form, ""xdpmul", and also references to the same name without the "x" ,, "dpmul". For example, the value of xdpmul is $21, which means that it is the second crossbank routine in bank 1. dpmul is the actual address of that routine in bank 1. The index is used to connect to the actual address in the crossbank call.

Enter the following program, and name it "xbc1.bsx". {"xbc1.bse" if you are using a bs2e}. The crossbank logic is shown in red. There is a second program listing below. "xbc0.bsx", which has to go into bank 0.

Note that this routine uses an spRAM location for its stack variable, and the convention for the return addresses differs a little from the previous discussion. Same general idea. There are many ways to implement this and a lot depends on the specifics of the application, not to mention the whims of the programmer.

' {$STAMP BS2SX}
' --------------------------------------------------------
   ' this is program xbc1.bsx for bank 1
' part of project xbc0.bsx
   ' These are double precision math routines
' that can be called from the other banks
' Thomas Tracy Allen, 
'   
' variable allocation in all banks must align!
   
   ' **** cross-bank call pointers ****
xdpadd  con $01     ' routine 0 in bank 1, addition
xdpsub  con $11     ' routine 1 in bank 1, subtraction
xdpmul  con $21     ' routine 2 in bank 1, multiplication
xdpdiv  con $31     ' routine 3 in bank 1, division
   
xmain  con $00     ' routine 0 in bank 0 is always the initialization code.
xadd1  con $10     ' return point 1 in bank 0
xsub1  con $20     ' return point 2 in bank 0
xmul1  con $10     ' return point 3 in bank 0
xdiv1  con $20     ' return point 4 in bank 0
   
   ' **** scratchpad allocation ****
stack   con 62     ' scratch ram location for return "stack"
   
' **** main RAM allocation ****
sxw   var word       ' reserve first word in main RAM
sxgo  var sxw.byte0  ' byte for bank switching, note alias
sxgo2 var sxgo.nib1  ' routine to branch to in target bank, note alias
                     ' nib 0 is the bank number
sxaux var sxw.byte1  ' variable for example function "call", note alias
cb    var sxaux.nib0 ' carry/borrow from operations, note alias fixes it in 1st word in RAM
ec    var sxaux.nib1 ' error code, returned by division routine
   
x0    var word       ' variable for input, low word
x1    var word       ' variable for input, high word
y0    var word       ' variable for input, low word
y1    var word       ' variable for inptu, high word
z0    var word       ' variable for output, low word
z1    var word       ' variable for output
z2    var word       ' variable for output
z3    var word       ' variable for output, high word
                    ' note that only multiply requires 4 words.
   
ix    var nib       ' variable for division index
                   ' temporary, not maintained in crossbank call
   
divZero  data "divide by zero",13
divBig   data "denominator too large (>$7fff)",13
' --------------------------------------------------------
top:
   branch sxgo2,[dbadd,dpsub,dpmul,dpdiv]  ' 4 of the 16 possible destinations are used
         
' -------------------------------------------
' double precision addition: z1:z0 := x1:x0 + y1:y0
   dpadd:
        z0=x0+y0
  z1=z0 max y0 - y0 max 1 + x1 + y1
  cb=z1 max y1 - y1 max 1   ' carry out of double prec. add
   get stack,sxgo       ' get return vector from stack
run sxgo             ' return to calling routine
   
   ' -------------------------------------------
' double precision subtraction: z1:z0 := x1:x0 - y1:y0
   dpsub:
        z0=x0-y0
  z1=z0 max y0 - z0 max 1 + x1 - y1
  cb=z1 max x1 - z1 max 1   ' borrow from double prec. subtract
   get stack,sxgo       ' get return vector from stack
run sxgo             ' return to calling routine
   
   ' -------------------------------------------
' double precision multiplication: z3:z2:z1:z0 := x1:x0 * y1:y0
   dpmul:
     z0= x0 * y0                  ' low word of product
  z2= x0 ** x0                    ' second word of product
  z1= x1 * y0 + z2                ' includes cross terms
  cy= z1 max z2 - z2 max 1        ' and carry from addition
  z2= y1 * x0                     ' other cross term
  z1= z1 + z2
  cy= z1 max z2 - z2 max 1 + cy   ' and carry from last addition
  z2= x1 * y1 + cy                ' third word of product, note carry from z1 term
  cy= z2 max cy - cy max 1        ' start new carry from additon
  z3= x1 ** y0                    ' cross term
  z2= z2 + z3                     ' add
  cy= z2 max z3 - z3 max 1 + cy   ' carry
  z3= y1 ** x0                    ' other cross term
  z2= z2 + z3                     ' add
  cy= z2 max z3 - z3 max 1 + cy   ' carry
  z3= y1 ** x1 + cy               ' last term, note carry from z2 term
   get stack,sxgo       ' get return vector from stack
run sxgo             ' return to calling routine
   
   ' -------------------------------------------
' double precision division: z1:z0 := x1:x0 / y0, reaminder=z3
' y0 limited to 0 to 32767, 0 to $7fff
   dpdiv:
  ec=0    ' no error assumed
  z0=1    ' this division algorithm only works for divisors <32768
  if y1>0 or y0>$7fff then errmsg
  z0=0    ' divide by zero not allowed
  if y1=0 and y0=0 then errmsg
  x1=x1<<1+x0.bit15   ' convert numerator to 16:15 format
  x0.bit15=0
  z1=x1/dv           ' high word
  z0=0               ' initialize for long division
  z3=x1
  for ix=14 to 0     ' long division, 15 bits
     z3=z3//dv<<1
     z0.bit0(ix)=z3/dv
  next
  z0=z0+(x0/dv)     ' result plus contribution from low word
  ' now find the remainder
  z3=(z3//dv)+(x0//dv)	' combined remainder
  z0=(z3/dv)+z0	       ' may have to add one to quotient
  z1=z1+z0.bit15            ' add possible carry  16:15
  z0.bit15=0	   	 ' 
  z3=z3//dv		     'final remainder   
  z0.bit15=z1.bit0      ' convert quotient to standard binary format
  z1=z1>>1
   dpdiv9:
   get stack,sxgo       ' get return vector from stack
run sxgo      ' return to calling routine
   
   ' ------------------------------------
' move error message to the scratchpad RAM
errmsg:
  lookup z0,[divZero,divBig],z0  ' decode message address
  z1=0
  ec=1  ' set the error flag
errmsg1:
  read z0+z1,x1  ' read byte from eeprom
  put z1,x1      ' put byte in scratchpad
  z1=z1+1        ' next address index
  if x1<>13 then errmsg1  ' continue until x1=CR
   get stack,sxgo       ' get return vector from stack
run sxgo      ' return to calling routine
   
      
   
      '------ end of program xbc1.bsx for bank 1 -------
   

Enter the following program for bank 1, and name it "xbc0.bsx". Or use "xbc0.bse" if you are using a bs2e. This is the main bank 0 program that will call the math subroutiines.

' {$STAMP BS2SX xbc1.bsx}
' --------------------------------------------------------
   ' this is program xbc0.bsx for bank 0
' part of project xbc0.bsx
   ' These are double precision math routines
' that can be called from the other banks
' Thomas Tracy Allen, 
'   
' variable allocation in all banks must align!
      
      ' **** cross-bank call pointers ****
xdpadd  con $01     ' routine 0 in bank 1, addition
xdpsub  con $11     ' routine 1 in bank 1, subtraction
xdpmul  con $21     ' routine 2 in bank 1, multiplication
xdpdiv  con $31     ' routine 3 in bank 1, division
   
xmain  con $00     ' routine 0 in bank 0 is always the initialization code.
xadd1  con $10     ' return point 1 in bank 0
xsub1  con $20     ' return point 2 in bank 0
xmul1  con $10     ' return point 3 in bank 0
xdiv1  con $20     ' return point 4 in bank 0
   
   ' **** scratchpad allocation ****
stack   con 62     ' scratch ram location for return "stack"
   
' **** main RAM allocation ****
sxw   var word       ' reserve first word in main RAM
sxgo  var sxw.byte0  ' byte for bank switching, note alias
sxgo2 var sxgo.nib1  ' routine to branch to in target bank, note alias
                     ' nib 0 is the bank number
sxaux var sxw.byte1  ' variable for example function "call", note alias
cb    var sxaux.nib0 ' carry/borrow from operations, note alias fixes it in 1st word in RAM
ec    var sxaux.nib1 ' error code, returned by division routine
   
x0   var word       ' variable for input, low word
x1   var word       ' variable for input, high word
y0   var word       ' variable for input, low word
y1   var word       ' variable for inptu, high word
z0   var word       ' variable for output, low word
z1   var word       ' variable for output, high word
z2   var word       ' variable for output
z3   var word       ' variable for output, high word
                    ' note that only multiply requires 4 words.
   
' -------------------------------------------
top:
   branch sxgo2,[main,add1,sub1,mul1,div1]  ' up to 16 possible destinations
   
main:
  serout 16,$54,[CR,Enter an 8-digit number in hex $xxxxxxxx: ]
  serin 16,$54,[hex4 wx1,hex4 wx0]
  serout 16,$54,[CR,Enter an 8-digit number in hex $yyyyyyyy: ]
  serin 16,$54,[hex4 wy1,hex4 wy0]
   
addition:
  put stack,xadd1  ' routine index and bank for return from call
  sxgo=xdpadd      ' routine index and bank for crossbank call
  run sxgo         ' crossbank call
   add1:              ' return here
        serout 16,$54,[CR,"  sum=",HEX4 z1,HEX4 z0," carry=",BIN1 cb]
   
subtraction:
  put stack,xsub1  ' routine index and bank for return from call
  sxgo=xdpsub      ' routine index and bank for crossbank call
  run sxgo         ' crossbank call
   sub1:              ' return here
        serout 16,$54,[CR,"  difference=",HEX4 z1,HEX4 z0," borrow=",BIN1 cb]
   
multiplication:
  put stack,xmul1  ' routine index and bank for return from call
  sxgo=xdpdiv      ' routine index and bank for crossbank call
  run sxgo         ' crossbank call
   mul1:              ' return here
        serout 16,$54,[CR,"  product=",HEX4 z3,HEX4 z2,HEX4 z1,HEX4 z0]
   
division:
  put stack,xdiv1  ' routine index and bank for return from call
  sxgo=xdpdiv      ' routine index and bank for crossbank call
  run sxgo         ' crossbank call
   div1:              ' return here
  if ec then div2  ' was there an error?
     serout 16,$54,[CR,"  quotient=",HEX4 z1,HEX4 z0. "remainder=",hex4 z3,CR]
goto main
   
   div2:               ' come here if there is a division error
     ' the error message will be in the scratchpad RAM, null delimited.
  sxaux=0
  debug  CR
div3:
  read sxaux,z0     ' print the message and  CR on screen
  debug z0
  if z0<>13 then div3
goto main
'------ end of program xbc1.bsx for bank 1 -------

 


Example of cross-bank calls, Stamp eeprom for data logging.

top

The BS2e is a natural canditate for a small data logger, because of its extra banks of memory. The data logging program can reside in one bank, while the remaining memory serves to hold the acquired data. The catch with the BS2e and BS2sx is that there is no way to put data directly into any bank other than the one that is currently running a program. The only link between banks is the RUN command, and and data to be transferred between banks must take the ride as a variable in either the main RAM or in the scratchpad RAM. Therefore, each bank that is going to hold data must also contain a small routine capable of writing the data and reading it back, and the mechanism of crossbank calls.

Note: The BS2p does not need the crossbank calls that were necessary with the BS2e and BS2sx. It has a newly introduced STORE command that gives direct access to eeprom for data storage in any bank, even from a program running in a different bank. Programming a data logger for the BS2p is much simpler and does not require crossbank calls.

But here is a demo program that shows how to do it with the BS2e or BS2sx. Te data to be entered will come from a DS1620 temperature sensor. Banks 1 to 7 will hold data, and also the small but necessary interbank data transfer program. The time is set by the modestly accurate sleep timer. A fully developed implementation of the data logger would have more than one channel of data to be logged, more accurate clock, etc etc.

A simple BS2 data logger using the DS1620 is to be found in unit 6 of Earth Measurements from the Stamps in Class series. The DS1620 here is the same as the one there.

One big issue for a Stamp data logger is "where to store the address pointer"? This is the address of the location in memory where the next data value will be stored.

All said, the following program cops out and simply uses RAM for the address pointer. You start an experiment, and the logging loop runs until it collects the allotted number of points, then it stops collecting data. When you reset the stamp, it asks via the debug interface if you want to read the data. Yes? It spits out the data from the beginning of the log up to the allotted number. If the experiment does not run for the full time, enough to fill the whole memory, then data displayed at the end will belong to a previous run. There are many ways to work around that. The point here is just to illustrate the mechanism of cross-bank calls for data logging and retrieval.

The data is stored in banks 1 to 7. The address pointer goes from 0 to 13300 (7 banks at 1900 bytes per bank, with 148 bytes reserved for the crossbank program). The bank to jump to is determined by a computation, by dividing the address by the number of bytes allotted per bank. The address within each bank is then the remainder from the division. For example, if the address is currently 9753, then the bank number to read or write to is 5 (=9753/1900), and the address to access within that bank is 253 (=9753//1900).

For a change, the "stack" in this short program is just a variable in the main ram, instead of in the spRAM. Same idea. It is the address to return to after a call. This program does not need a fancy stack mechanism. The logger sleeps 10 minutes between readings.

The following code is for bank 0:

' ====================================================================
' {$STAMP BS2e,dlogger1.bse,dlogger2.bse dlogger3.bse,dlogger4.bse,dlogger5.bse,dlogger6.bse,dlogger7.bse}
' this is program is dlogger0.bse for bank 0
' Example program, crossbank subs for BS2SX
' (c) 2000, Thomas Tracy Allen, Electronically Monitored Ecosystems
'    
' ====================================================================
' logs up to 13300 bytes in each run
' readout displays up to that number
' data is stored in banks 1 to 7 along with a short access program
' 1900 bytes per bank * 7 banks = 13300 bytes
' one byte per record, temperature from DS1620, positive, 0.5 degree
' logger sleeps 1 minutes between each reading
' 9 days deployment at that rate.
' address pointer is in RAM
   
' **** crossbank call addresses ****
xreadlog2     con $01   ' this is a return address in bank 0
xlogNextByte2 con $02   ' ditto
   
xreadrecord    con $0   ' the same relative destination in banks 1 to 7
xwriterecord   con $1   ' ditto for write
                        ' the bank will be computed for the above
   
   
' **** main RAM allocation ****
sxw    var word       ' reserve first word in main RAM
sxgo   var sxw.byte0  ' byte for bank switching, note alias
sxgo2  var sxgo.nib0  ' routine to branch to in target bank, note nibble alias
sxrun  var sxgo.nib1  ' bank that contains the routine
stack  var byte       ' to store the return vector from crossbank call
   
logadr var word       ' address for memory write and read
result  var word      ' word for data, will store byte in log memory
   
' ==== bank 0 program, start here from reset or RUN 0 ===================================
top:
   branch sxgo2,[init,readlog2,logNextByte2]  ' destinations in this bank
   
' ------------------------------------------------------------
' ---- initialization code, from reset, or after data read ----
init:
  ' this is the initialization code that is executed
  ' from reset or from power up
  outs=%0000000000000000      ' all outputs low
       'fedcba9876543210 
  dirs=%1111111111111111      ' all pins outputs initially
  
decision:           ' show user options, get input decision   
  serout 16,$54,[cr," 1)read data  2)log data",cr," (1,2)?>"]
  serin 16,$54,[result]
  branch response-$31,[readlog,newlog]
goto decision  ' unknown response, try again
   
   
' ---------------------------------
' ---- read data from log file ----
' this reads one byte (record) at a time from the eeprom
' in the appropriate bank and brings it back here to display
' loops through entire memory
readLog:
readLog1:
  stack=xreadLog2      ' return to readLog2
  sxrun = logadr/1900+1  ' which bank to run? 1-7
  sxgo2=xreadrecord        ' get ready to read a byte
  run sxrun                 ' do it
readlog2:                  ' return here with byte in result
  value=result*5           ' temperature in tenths (to 0.5 degree C)
  serout 16,$54,[cr,dec logadr,tab,dec value/10,".",dec1 value," 'C"]
logadr=logadr+1
  if logadr<13300 then readlog1
finishedRead:
  debug cr,"finished"
  end
   
   
' ---------------------------------
' ---- write data to log file -----
' this is a loop, until memory fills or user resets
newlog:
logNextByte:               
  gosub ds1620             ' get the datum in result
  stack=xlogNextByte2      ' return vector
  sxrun=logadr/1900+1      ' which bank to run? 1-7
  sxgo2=xwriterecord       ' get ready to write the byte
  run sxrun                ' do it
logNextByte2:
  logadr=logadr+1
  sleep 60                 ' 1 minute sleep
  if logadr<13300 then logNextByte
finishedLog:
end                         ' hold here until reset
   
   
' ----------------------------
' subroutine acquires data from DS1620 in single shot mode
' the following setting in eeprom needs to be done only once
' be sure to cycle the power to the DS1620 after changing!
' P13=chip select
' P14=chip clock
' P15=data in and out (with resistor to DS1620)
'high 13   'puts the DS1620 in CPU single shot mode
'  shiftout 15,14,lsbfirst,[$C,$3]
'low 13
ds1620:        ' start a conversion
   high 13
     shiftout 15,14,lsbfirst,[$EE]
   low 13 
busy:          ' wait for data to be ready (up to one second!)
   high 13
     shiftout 15,14,lsbfirst,[$AC]
     shiftin 15,14,lsbpre,[result]
   low 13
   if x.bit7=0 then busy   ' x.bit7 goes high when done
   
   high 13      ' request data byte and get it
     shiftout 15,14,lsbfirst,[$AA]
     shiftin 15,14,lsbpre,[wx]	' get datum, will assume >0, ignore sign
   low 13
   ' will return with degees C * 2 in result
return
  ' ------ end of program dlogger.bse for bank 0 -------

And the following code is for banks 1 to 7. Copy this code for all banks from 1 to 7, just name the files dlogger1.bse, dlogger2.bse ... dlogger7.bse.

' ====================================================================
' {$STAMP BS2e}
' this is program is dloggerN.bse for bank N
' (c) 2000, Thomas Tracy Allen, Electronically Monitored Ecosystems
'    
' ====================================================================
   
logSeg con 1900       ' number of log locations in each bank 1 to 7
                      ' leaves 148 bytes for the access programs.
logfile data $ff(logseg) ' reserves bytes for the logfile
                       ' and sets the value to $ff
                       ' don't really need to do this, but why not?
   
' **** main RAM allocation ****
sxw    var word       ' reserve first word in main RAM
sxgo   var sxw.byte0  ' byte for bank switching, note alias
sxgo2  var sxgo.nib0  ' routine to branch to in target bank, note nibble alias
sxrun  var sxgo.nib1  ' bank that contains the routine
stack  var byte       ' to store the return vector from crossbank call
   
logadr var word       ' address for memory write and read
result var word      ' word for data, will store byte in log memory
   
' ==== bank 1-7 program ==========================================
top:
   branch sxgo2,[readrecord,writerecord]  ' destinations in this bank
   debug cr,7,"hey! bad crossbank call"
   
readrecord:
  read logadr//1900,result   ' remainder is address for data in bank
  sxgo=stack
  run sxrun
   
writerecord:
  write logadr//1900,result
  sxgo=stack
  run sxrun
   
' ------ end of program dloggerN.bse for bank 1-7 -------
   


Here is the core of the same program, for the BS2p. The big difference is that the little programs in banks 1 to 7 are no longer necessary. The STORE command accesses the data in all those banks directly from the program running in bank 0.

The READ and WRITE commands take an argument in the range of 0 to 2047 just as with the earlier Stamps. But the STORE command changes the bank referred to. Since no memory in the other banks is needed for program, all 2048 bytes can be used for data, a total of 14336 bytes (7*2048).

' ---------------------------------
' ---- read data from log file ----
' this reads one byte (record) at a time from the eeprom
' in the appropriate bank and brings it back here to display
' loops through entire memory
readLog:
readLog1:
  store logadr/2048+1      ' which bank contains the data
  read logadr//2048,result  ' remainder is address for data in bank
readlog2:                  ' return here with byte in result
  value=result*5           ' temperature in tenths (to 0.5 degree C)
  serout 16,$54,[cr,dec logadr,tab,dec value/10,".",dec1 value," 'C"]
logadr=logadr+1
  if logadr<14336 then readlog1
finishedRead:
  debug cr,"finished"
  end                     ' hold here until reset
   
' ---------------------------------
' ---- write data to log file -----
' this is a loop, until memory fills or user resets
newlog:
logNextByte:               
  gosub ds1620             ' get the datum in result
  store logadr/2048+1      ' which bank contains the data
  write logadr//2048,result   ' remainder is address for data in that bank
logNextByte2:
  logadr=logadr+1
  sleep 60                 ' 1 minute sleep
  if logadr<14336 then logNextByte
finishedLog:
end                         ' hold here until reset
   
   


How the OWL2e uses Crossbank "calls" to script data acquisition

top

My OWL data loggers make lots of use of crossbank calls. Separate banks are dedicated to initialization and user input, to scanning the input channels, to the memory and clock routines, and to the drivers for different types of sensors and processes.

bank 0) initialization and user input, branches to other routines
bank 1) main loop that scans the input channels and records data in the log file
bank 2) loop that reads out the data from the data log to offload it to the serial port
banks 3 to 7) sensor driver routines.

Several of the core routines are repeated in each bank. For example, it is more convenient to repeat the routine that reads the clock and some of the eeprom routines in a couple of banks, rather than having to do a crossbank call to access them in another bank.

Here is an example of a use of the bank switching, as implemented in the OWL2e data logger. A main program loop reads data from a table in eeprom. The table consists of multiple records, linked by a variable length offset.

 

scantable diagram

Each record holds all the information necessary for one action to be taken by the program. These actions are performed in sequence as the table is scanned by the main program. The records in the current incarnation can be from 3 to 13 bytes long, depending on how much information is necessary to perform the action.

An action might be something like reading a temperature from a certain channel. The information needed will be the reference to a "driver" that runs in order to acquire the temperature, the channel number to which the sensor is attached, and perhaps calibration factors, format specifiers, units of measurement and so on. There are different drivers for different types of sensors and actions--humidity, rainfall, windspeed, thermostat etc.

The main program loop that scans the table is relatively short and resides in bank 1, the same bank as the scan table itself.

The low nib of the first byte of each record specifies the length of the record, which is the same as the offset to the start of the next record. (It would be better to have longer records, but the length is limited by need to transfer information by way of the RAM or scratchpad RAM buffer. The new BS2P has a STORE command that makes it easy to access the scan table from a program running in any program bank, so the scrachpad RAM or regular RAM will not be necessary to transfer this data.)

The high bit of the first byte flags the "often" option. Some actions have to be performed every time around the loop (~1/second). This includes things like averaging and scanning rain gages. The often bit allows the program to run more efficiently and at a lower power consumption by skipping drivers that do not need the frequent attention.

The remaining three bits of the high nibble specify how many bytes of data this driver will put into the data buffer. This can be from zero to 7. This is a data buffer that is maintained in the scratchpad RAM. This not only includes data that will be put into the log file, but also intermediate data such as counts and averages.

The program reads in the first byte of the scan table, and then determines if it needs to read more at this pass. If not, it simply jumps to the next record and moves the data buffer pointer up by the specified amount. If the program determines that it is time to execute the full routine, it has more work to do. It reads in the second byte of the record. This is an index for a crossbank call, exactly as outlined in the previous article. It contains the bank number and a branch address of a routine that will acquire and process the data from a certain sensor, or take some other desired action. That byte is read into the variable sxgo.

The rest of the parameters (1 to 11 of them) in the record are read into a buffer in RAM. The return address is PUT onto the "stack", and then a RUN command is executed to transfer control to the driver routine in another bank. The driver routine does its thing, expecting to find the parameters it needs in the RAM buffer. When the driver is finished, it GETs the return pointer from the "stack" and returns to the original bank where the main program loop continues with the next entry in the scantable.

The point of this scenario is:

The other common parameters in a script record are:


Operating and sleep current

top

The first question I had about the BS2SX and BS2e concerned their power consumption. My OWL2c data loggers are designed for operation in remote sites for extended periods of time, almost always from a small solar panel, or from a small 1.2a-h gel-cell battery only. The logger can sleep much of the time, but needs to wake up at intervals of ten seconds or so to take readings and to perform other tasks. The overall current drain and battery life is determined by the percent time spent in the low current sleep mode and vs the high current operating mode.

The situation is complicated by the fact that, in the sleep mode, the Stamp wakes up once every 2 seconds (approx) to check its status. So for example if the SLEEP 10 command is encountered, the Stamp will wake up very briefly at 2, 4, 6, 8 and 10 seconds to check its status and at the 10 second point it will stay awake and continue with the command after the SLEEP command. So during the sleep mode, there are two components to the current. One is a microamp level sleep current, punctuated by much higher operating current for the status check. The time average of those is the "averaged sleep current".

Here are my results from my first batch of 11 BS2sx' from the first (blue-board) production run. These were powered from a 6 volt gel cell with a terminal voltage of 6.4 volts. There was a shottky diode in series with the battery connected to pin 1 of the BS2SX, the Vin terminal. All of the user pins were configured as LO outputs, with no external connections. The currents were measured with a 4.5 digit ammeter in series with the power supply lead. The timing was measured using an analog scope triggered on the power supply current spike. The program is listed below.

Operating current:
average operating current...............65 milliamps
     (64.2 to 65.9 milliamps in sample)
Sleep current (SLEEP NAP END):
power-down current.......................46 microamps
     (45 to 48 microamps in sample)
average sleep cycle time at 25°C.........2.15 seconds   (SLEEP or END)
average sleep ON time at 25°C............0.016 seconds  (SLEEP or END)
average sleep current at 25°C............527 microamps  (SLEEP or END)

A word is in order about the last three items, leading to the average sleep current. The BS2SX in executing the SLEEP or END commands wakes for 16 milliseconds, once every 2.15 seconds, to check its status. During this mode the BS2SX draws 65 milliamps for 0.74% of the time (0.016/2.15), and it draws 46 microamps for 99.26% (2.144/2.15) of the time. The average current drain in these modes is thus
0.0074*65+0.9926*0.046 = 0.527 milliamps.
This value may change as temperature of the chip changes. The above data was taken at a temperature near 25°C.

The ideas in the previous paragraph are true of the non-turbo BS2 as well, with its PIC processor running at a cool 20 mhz. But the BS2 operating current is 7 milliamps, and the sleep current is 30 microamps, giving an averaged sleep current of around 80 microamps. Actually the averaged current is near 50 microamps, because for some reason the PIC does not draw the full 7 milliamps when it wakes up from sleep to check status--it only draws about 2 milliamps then.

The above results were found by running the following program:

init:
  outs = $0000
  dirs = $ffff
loop:
  pause 1000
  sleep 5
goto loop
   

See here for a table comparing the power supply drain of the different stamps.


Hot LT1121 regulator

top

Summary of the features of the LT1121CS8-5 regulator.

Marked "11215" on the chip. or is it 121A5.
Maximum input voltage: +/- 30 volts.
Maximum output current before limiting: 150 milliamps minimum.
Short circuit limited and temperature protected.
Input can be at zero volts and output at +5 volts, with only 16 microamps reverse current.
Minimum quiescent current ~ 30 to 50 microamps at Iout = 0.
Ground pin current ~ 6.7% of output current.
Power dissipation = Iout * (Vin - Vout) + Iout / 15 * Vin
Maximum junction temperature: 125°C
Thermal resistance junction to ambient with minimal heat sink: 125°C/watt.
Critical power dissipation (125°C Tj) at 25°C ambient: 0.8 watt.
Critical power dissipation at 50°C ambient: 0.625 watt.

The power rating is what I wanted to get to. What is the total current that can be supplied by the onboard regulator before it goes into a limiting mode? The current supplied by the regulator is the sum of the 65ma base operating current for the Scenix processor, plus the current sourced by the output pins, plus any additional current drawn by outside circuitry.

The first production run of the BS2SX used the LT1121CS8-5 voltage regulator, and I understand that future runs will use the improved LT1121ACS8-5, which has a better thermal coefficient.

The following table considers three possible power supply voltages applied to pin 1 of the LT1121A or the LT1121. The body of the table shows nominal output current at which the critical power is exceeded, for ambient temperatures of 25 deg C and 50 deg C. It assumes a thermal coefficient of 131 degrees C per watt for the LT1121CS8-5, and thermal coefficient of 74 degrees C per watt for the LT1121ACS8-5.

  stamp |     1121A               1121
  _Vin_ |_@25______@50___|___@25______@50___ 
        |                |
    12  | 173      129   |    97       73
        |                |                    thermally limiting
     9  | 293      219   |   165      123     output current, mA
        |                |                    
     6  | 964      721   |   542      407

The BS2SX draws 65 ma. It would not be prudent to draw more than 75% of the limiting current. The table is a guideline for maximum allowable current. The above figures are liberal. The regulator on the BS2sx is provided with hardly any heat sink at all, so the assumed thermal coefficient may be a bit too high. If you need higher currents and high supply voltages, it would be good to use a preregulator or other means to keep the supply input voltage to between 6 and 9 volts.

The BS2e only draws 22ma while operating, so the power demand the regulator is much, much less a consideration unless it has to drive external loads or operate from a high input supply voltage.


<top> <index> <home> logo < mailto:info@emesystems.com>