The 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..
The 2048 bytes of program memory in the original BS2 presents a hard limit. When and if you run up against it, you have a challenge, to say the least, to trim the fat and to rethink your problem to the bare bone essentials. With the BS2SX and BS2e it is possible to build a lot of program machinery and leave it in place for use with different applications, instead of having to re-optimize the whole chip for each new application. The downside is the temptation to get sloppy, to add fat features and bugs that complicate life instead of simplifying it. The bank structure of the chip presents a significant challenge to the design of large, integrated programs.
The BS2sx and BS2e have 63 bytes of scratchpad, while the BS2p offers 127.
This is in addition to the 26 bytes of main RAM. The scratchpad
RAM can only be accessed via the GET and PUT commands. Only the 26
bytes of the main RAM can be used directly in assignment
statements and formulas. The 26 bytes of the main RAM can be
accessed as bits, nibs, bytes, and words and can be used directly
as named variables in expressions and as the arguments of
commands. In contrast, the scratchpad RAM can only be accessed one
byte at a time, and the only way to retrieve a scratchpad byte it
is by using the GET or PUT command). examples...
GET 55,xbyte
GET myaddress,ybyte
PUT 55,xbyte+ybyte
PUT myaddress+1,xbyte
Both the main RAM and the scratchpad RAM are conserved when crossing from one bank to another with the RUN command.
This chip is new, and I expect to have some more timing data on it at a later date. There is rumor that Parallax may introduce a version of the BS2p running on an 8 megahertz clock, for lower power consumption and greater compatibility with the BS2 and BS2e.
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.
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.
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
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
The way to load and run the program depends on which version of the software you are using.
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 |
---|
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.
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.
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.
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.
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 -------
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 |
---|
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.
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:
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.
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.