Microcontrolled Biostimulator
Fall 1999
Prof. Land and Brian Wong
Introduction:
The microcontrolled biostimulator
is designed to be a inexpensive alternative to more expensive commercial
models. By replacing costly discrete IC timers with one microcontroller,
a huge price savings can be acheived; a microcontrolled stimulator can
cost as little as five dollars. Due to the price savings, a stimulator
can easily be distributed in classrooms, labs, and field experiments. Note
the microcontrolled biostimulator is not "cheap" alternative. Timing parameters
and errors are similar or better than a normal discrete IC based designs.
Specifications:
Maximum | Minimum | Comments | |
Input Voltage | 20V | 5V | Using Atmel development board |
Output Voltage | 5V | CMOS compatible | |
Time step | 100us | 100us | Firmware adjustable |
Beats | 255 | 0 | |
Interval | 65536 * Time step | 0 | |
Duration | 65536 * Time step | 0 | |
Inital Delay | 65536 * Time step | 0 | |
Interbeat Interval | 65536 * Time step | 0 | |
Sync Pulse | Yes | ||
Manual Pulse | Yes | ||
Y2K? | Yes |
Philosophy:
The biostimulator is divided
into two discrete parts. The first is the "Timer Core"; this segment controls
all the timing for the stimulator. The timer core is part of every stimulator.
The second part is the "Wrapper"; this segment provides interface
between the core and the user. As the timer core is a stand-alone component,
the stimulator can adapt to a wide variety of situations.
Timer Core:
The timer takes five different values:
Inputs and Outputs are as follows:
PortB #3 is the pulse output.
PortB #0 is the syncronization
output.
PortA #5 and #6 are Pulse/external
inputs.
The timer core is quite simple. TimerA controls the time base, the "tick" for the clock. Currently it is set to a 100us tick. When TimerA overflows, it provides a 'tick' to TimerB. As the microcontroller runs TimerB accumulates a value from these ticks. While running, the controller polls TimerB to see if certain points in the specified pulse train, like the end of a duration, have been reached. When this happens, PortB#3 is turned on or off, depending on the previous status of the port. At the same time, the timer calculates the next milestone that TimerB needs to hit. When the new value is detected PortB#3 toggles again. This repeats as necessary. When the interval is over, everything resets and starts anew with a new sync pulse. The sync pulse and the pulse output can be syncronized when the 'Sync delay' is set to zero. Having a non-zero value shifts the pulse train so that it starts later than the sync pulse.
Wrapper:
The "Wrapper" is simply
a user interface for the biostimulator. Depending on the application, it
can take many forms. Lab users may prefer the RS-232 support, while field
and classroom use may enjoy a LCD/button type interface. The wrapper is
also totally unnecessary; if long term embedded solutions are needed, the
stimulator can be used as an OTP device.
In its current iteration,
however, the RS-232 support is implemented. Using a PC, new timing parameters
can be inserted to the biostimulator. It must be noted that you need a
serial program like "Hyperterminal" to communicate with the stimulator.
To set up a serial link, the computer needs to have the serial cable
attached to COM1 and the serial port on the evaluation board. Then in hyperterminal,
set the protocol to 9600-8-N-1.
9600 refers to the baud
rate, or speed, of the serial communications. The baud rate on the computer
can be set to a higher setting, but the user must manually change this
setting in the microcontroller code. Right now it is set to 9600, and it
is fast enough for most purposes.
The '8' refers to the the
word size. All communications for the biostimulator will only use 8 bit
words.
For parity, set it to 'N'
for none, set the stop bit to '1', and there is no flow control.
Here's how to use the RS232 implementation:
Note that all times are entered in ***MICROseconds***.
N)um beats? 1
I)nterval? 100
D)uration? 50
S)ync Delay?0
P)ulse delay? 0
M)ulti press'2' single press'1')?
N)um beats? _
Guts:
The wrapper is probably the largest part of the
code. Approximately two-thirds of the code space is used for print statements,
text string definitions, and parsing. Within the wrapper, there are two
distinct routines. One is the wrapper itself and the other is the parser.
The wrapper does all the "please display this string" calls and UART control.
The parser's main function is to take ASCII input and translate them into
binary, while storing copies of the ASCII string in RAM.
The parser can be called the "guts" of the wrapper since it does all the vital work. As user input comes from the keyboard, the input is pushed onto a stack. This is great. However, the input is serial and it turns out that the most significant digit would appear as the least significant digit if popped normally from the stack. Now although this doesn't matter so much for the translation into binary since multiplication and additions are associative, it does make it slightly hairy when moving the digits into RAM. To alleviate the problem, we have two memory pointers working simultaneously. One is a RAM pointer to the value's RAM location for ASCII storage, and another is a copy of the stack pointer to find the right digit in the stack. The RAM pointer is post-incremented, and the "stack" pointer is preincremented. The RAM pointer is initally loaded with a fixed address, or "tag" for the value. The "stack" pointer is originally a copy of the stack pointer when the program initally jumps to the parser and has not begun to accept input. As input comes in, newer numbers are located in lower memory addresses, since the stack starts at 0xFF and works its way to 0x00. Predecrementing the "stack pointer" is to reflect this decrementing memory address. Essentially, the "stack" pointer reads the stack from the bottom up, instead of the top down....this preserves the natural order of the numbers as they are put in ASCII form into RAM. This also has the savings of not having to reparse the numbers from binary to ASCII to stick them into RAM. So in one pass, the values are read from the stack, inserted in the correct order into RAM, and translated into binary.
Once all the numbers are parsed, the stack is flushed. This is to return the stack to its original state when it first entered the parse routine. This is important, or else the microcontroller will interpret one of the numbers that was entered to be a memory address to jump back to. To do this, a small 'pop' routine runs at the end of the parse routine. This loops only as many times as there are digits entered. Once all then numbers are popped off, then the program returns normally.
The source code.
Timer Core:
Wrapper: