Interrupts on the 68HC12


I have recently built a sensor/datalogger system that has to run on a single battery that originally only powered the sensor. In order to reduce the power consumption somewhat -- to get at least one week of power from the combined systems -- I decided to use the WAIT mode on each processor. In both cases, WAIT mode roughly halved the power requirement (to ca. 20 mA for each device). This looks to be adequate for one week's operation on a battery. (LATER: I had hoped that proper attention to port setups and such would improve this value but, alas, nothing seems to help. This is as good as it gets.)

For the datalogger, I decided to use the serial input interrupt. The CPU idles in WAIT mode until a character appears on the serial port. At this point, the character is read and appropriate action taken. In my system, there are commands for Start Of Data, End Of Data, and some housekeeping functions (like dumping and erasing data). The sensor sends data (bracketed by SOD and EOD) as ASCII-HEX on the serial input.

For the sensor, I decided to use the Real Time Interrupt because the sensor takes data at timed intervals of 10-20 minutes. I set up this internal clock to interrupt the CPU at 65.536 mSec intervals (the maximum available) and wrote a routine to stay in WAIT mode for 48 cycles or about 3 seconds. At this point, the CPU can read the clock and see if it is time to take data. If not, back into the WAIT routine for another 3 seconds.

In either case, the key element to handling interrupts is to SERVICE THE SOURCE OF THE INTERRUPT. In most cases, a bit is set by the interrupting device. This bit must be cleared before you exit the interrupt handler or ... guess what? Yep, the interrupt is serviced again. And again, and again and ... Sometimes the bit is cleared by reading it (see the Datalogger example) and sometimes by writing it (see the Sensor example).

DATALOGGER:

This code puts the CPU into WAIT mode until a character is received on the serial port. When the character is received, the calling program must then decide what to do with it. When processing is complete, the code can re-enter WAIT mode.

The SIO interrupt vector at $F7D6 was changed to $0F00, an address in EEPROM. At this address, the following interrupt handler was installed

4D C3 20 ( bclr siocr2, #$20 ; clear the interrupt enable bit
96 C4 ( ldaa siostr ; clear the interrupt bit
14 10 ( sei ; disable interrupts
0B ( rti

Remember, if using MaxForth to install this code, use EE! for the interrupt handler and FL! to install the vector to the handler routine.

This is the code fragment that calls WAIT mode:

: dokey ( - )
key dup sod = if datalog save-data then
dup D = if dump-data then
dup E = if erase-ram then
dup S = if disp-status then
KM = if true killme ! then
;
hex
code-sub wait
4cc3 , 20 c, ( bset siocr2,$20 ; set RCV INT bit
10ef , ( cli ; enable interrupts
3e c, ( wai ; WAIT
3d c, ( rts ; return to FORTH
end-code
decimal

( Initialize the datalogger, then wait for an input character
( Expect a command first: SOD to start datalogging or
( DUMP to dump data or STATUS to show # of data sectors or
( ERASE to erase CF-RAM by resetting # sectors to zero or
( KILL to exit from program.

: main ( - )
init ( initialize the system
begin
wait ( low-power mode until input
dokey ( process input
killme @
until
." Overwrite $D00 with $FF to stop autorun" cr
;

The routine WAIT enables received data interrupts on the serial I/O port. MAIN is the routine that calls WAIT and DOKEY processes the character. If it is a valid command, then the appropriate routine is called to execute it. SOD and EOD are the commands that bracket data to be stored. The routine that saves this data to RAM is called DATALOG:

( ---------------------------------------------------------
( Program datalog.asm
( 18 June 2009: Datalogger routine for SandScan unit
( 68HC12 subroutine:
( Called after receiving a Start Of Data word
( Reads data from SIO, saves to sequential loc'ns
( in array DATA.
( Ends and returns when receives End of Data word
(
( NOTE: all data must be ASCII-HEX or TEXT, no CTRL chars
(
( ---------------------------------------------------------
code-sub datalog
3b c, ( pshd ; save D accumulator
34 c, ( pshx ; save X register
35 c, ( pshy ; save Y register
ce c, data , ( ldx #data ; point to start of DATA array
08 c, ( inx ; save two bytes at start
08 c, ( inx ; for # bytes
cd c, 0000 , ( ldy #00 ; Y = byte counter

( inchr:
96c4 , ( ldaa scxsr1 ; check status
8420 , ( anda #$20 ; data ready?
27fa , ( beq inchr
96c7 , ( ldaa scxdrl ; get character
8105 , ( cmpa #eod ; is it EOD?
2706 , ( beq done ; if yes, exit
6a00 , ( staa 0,x ; save to data array
08 c, ( inx ; increment address
02 c, ( iny ; increment byte counter
20ee , ( bra inchr ; do again
( done:
7d c, data , ( sty data ; put byte count at DATA
31 c, ( puly
30 c, ( pulx ; recover registers
3a c, ( puld ; and accumulator
3d c, ( rts ; return to FORTH
end-code

This routine saves all the characters after the SOD to RAM (at the array I call DATA), keeping track of the number of bytes in a counter. When EOD is received, the byte count is saved at the beginning of the DATA array. In my system, this RAM buffer is then saved to a CF-RAM card for semi-permanent storage.

SENSOR:

This code puts the CPU into WAIT mode for approximately 3 seconds. Each time the RTI interrupt fires, the counter (held in the Y register) is decremented; when it reaches zero, the subroutine returns to the calling program. Note that you need to install an interrupt handler somewhere (I chose $0F00 in EEPROM) and point the RTI interrupt vector at $F7F0 to this handler. If you are using MaxForth to install these routines, remember to use EE! to put code in EEPROM and FL! to put the handler vector in Flash.


( Forth/asm fragment to test RTI interrupts on 68HC12
( This routine enters WAIT for approximately 3 seconds
( and exits with RTI interrupts disabled
(
( 2 July 2009: cfg

hex
code-sub wait3seconds
3b c, ( pshd
35 c, ( pshy
cd00 , 30 c, ( ldy #48 ; pre-load Y with count

( Enable RTI interrupt at 65.536 mSec intevals

4c14 , 87 c, ( bset rtictl,#$87 ; enable RTI ints @ 65.536 mSec
4c15 , 80 c, ( bset rtiflg,#$80 ; clear RTI interrupt flag
( sleep:
10ef , ( cli ; enable interrupts
3e c, ( wai ; wait for RTI interrupt

( Interrupt handler clears RTIFLG high bit, disables ints
( and RTI's to here

03 c, ( dey ; decrement tic counter
26fa , ( bne sleep

1410 , ( sei
31 c, ( puly
3a c, ( puld
3d c, ( rts
end-code

( The RTI interrupt vector at $f7f0 has been set to $0f00
( Code at $0f00 looks like this:
(
( 14 10 sei
( 4c 15 80 bset rtiflg,#$80
( 0b rti