Code:
' =========================================================================
'
' File...... hp97-clock.SXB
' Purpose... HP-97 interface with clock
' Author.... Katie Wasserman
' Started... 08/22/09
' Updated... 09/24/09
' Compiler.. SX/B Version 2.00.30
'
' =========================================================================
' -------------------------------------------------------------------------
' Program Description
' -------------------------------------------------------------------------
' Interface to an HP-97 Calculator -- no pod needed.
' Makes use of the following control lines available on the 97S header
' (or on any 97 by attaching to the right connection points).
' Control Line Header Pin # Description
'-------------- ------------ --------------------------------------------------
' GND 1 and 16 ground
' +V 9 and 10 +V (nominal 4.8 volts from battery in calculator)
' DATA 13 serial data -- normally low. When this line is low
' with no pulses it means that the calculator is idle and
' showing zero on the display. This code can use this
' to indicate that the calculator is read for input.
' RCD 5 keyboard scan reset -- normally high. When pulsed low it
' resets the 1 of 14 counter that scans the keyboard matrix.
' This happens continuously at about 218Hz.
' STR 11 keyboard scan clock -- normally low. When pulsed high the
' 1 of 14 keyboard row counter advances on the positive going edge.
' (this clocks at roughly 14 x RCD = 3.1khz)
' KBA 6 keyboard column line A -- normally high. Pressing a key in
' column A will generate a low pulse when the 1 of 14 counter
' selects the matching row.
' (there is no KBB)
' KBC 7 keyboard column line C -- normally high. (as above)
' KBD 8 keyboard column line D -- normally high. (as above)
' KBE 12 keyboard column line E -- normally high. (as above)
' The following pins on the header are not used:
' SYNC 15 serial address line sync (?)
' IS/IA 14 serial address line
' phi-1 3 calculator internal clock line phase 1
' phi-2 4 calculator internal clock line phase 2
' n/c 2 (no connection to this pin)
' This program allows the user to display a clock as HH.MM SS on the calculator by feeding
' keystrokes using the above interface lines to simulate key presses. It can do this
' continuously or only when requested (by displaying zero and halting the calculator with R/S).
' It also allows the user to set the clock when needed. In addition, this program can read
' any key press on the keyboard -- in fact it mirrors the calculator keyboard scanning routine
' and act on the key press. For example, the sequence 'f', 'f', 'RND' will be interpreted by
' program as a required to generate a 10-digit number from 0 to .9999999999 on the display.
' Whenever this code prompts the user for calculator input it will first clear the display to
' indicate that it is ready for input (except in the special key code reporting mode -- see
' below).
' To set the clock press: 'f', 'f', '+' key followed by the time
' in 24-hour format. 18.3724 would set the clock to 06:37:24 pm, for example.
' To set the clock for 12-hour format (no am/pm indicated) press 'f', 'f' '-'
' followed by the time in 12-hour format.
' To switch to 24-hour format press 'f', 'f', 'x' (multiply).
' To set the clock refresh interval in seconds press 'f', 'f', '8' follow by the number of
' seconds -- always 2 digits. E.g., 05 will set the display to update every 5 seconds.
' To start the clock with continuous updates press: 'f', 'f', '7'
' To start with clock with updates only when requested -- calculator halted (with R/S) and zero
' on the display press: 'f', 'f', '4'. The time will be entered as HH.MMSS followed by
' a R/S.
' To stop the clock press 'f', 'f, '1'. The clock display stop whenever you set the time
' to avoid confession.
' Execution of each command occurs when the key pressed is
' released. When the clock is displayed and changing rapidly it is
' necessary to press and hold 'f' and command keys for 1 second or so
' to overcome any interference from the microcontroller trying to update
' the display.
' Other stuff:
' To put the calculator in keystroke monitor and report column.row mode press: 'f', 'f', 'DSP'
' From this point on every key you press will show the scanning col.row once you release it.
' You need to switch off the calculator to exit this mode.
' The RTC has 31 bytes of battery-back RAM. The first 4 of these are used to save the state variables
' so that the calculator will resume in the last state (e.g., clock running) when powered up. The
' remaining 27 bytes can be used to store startup keystrokes (a macro) for anything that can be
' entered from the calculator keyboard. Press 'f', 'f', '5' followed by up to 26 keystrokes.
' to end entry turn of the calculator off. When you turn the calculator back on those keystrokes
' will run with 0.4 second pauses between them. To clear this macro, just turn off the calculator
' after entering 'f', 'f', '5'. Note: The special functions implemented by this code
' (using the 'f', 'f' prefix) are not available for use in macros.
' Power for the Parallax/SX28 and DS1302 (RTC) comes from the calculator Vb line. The RTC
' has it's own CR1225 timekeeping battery that should last many years.
' The following connections are made to the chips, GND is common to all.
' Parallax SX-28 (DIP):
' -1 28- +V-calc
' +V-calc -2 27-
' -3 26-
' GND -4 25-RC7 (CE on DS1302)
' -5 24-RC6 (I/O on DS1302)
' -6 23-RC5 (SCLK on DS1302)
' -7 22-
' -8 21-
' -9 20-
' RB0 (KBA) -10 19-
' RB1 (KBC) -11 18-
' RB2 (KBD) -12 17-
' RB3 (KBE) -13 16- RB6 (DATA)
' RB4 (STR) -14 15- RB5 (RCD)
' DS1302 (8-pin DIP)
' +V-calc -1 8- +V (3 volt lithium battery -- CR1225)
' Xtal -2 7- SCLK (RC5 on SX)
' Xtal -3 6- I/O (RC6 on SX)
' GND -4 5- CE (RC7 on SX)
' -------------------------------------------------------------------------
' Device Settings
' -------------------------------------------------------------------------
DEVICE SX28, OSC4MHz, TURBO, STACKX, OPTIONX
FREQ 4_000_000
' -------------------------------------------------------------------------
' IO Pins
' -------------------------------------------------------------------------
_KBA PIN RB.0 INPUT
_KBC PIN RB.1 INPUT
_KBD PIN RB.2 INPUT
_KBE PIN RB.3 INPUT
_STR PIN RB.4 INPUT SCHMITT
_RCD PIN RB.5 INPUT SCHMITT
_DATA PIN RB.6 INPUT SCHMITT 'needed because this a tri-state line with lots of noise
RTC_SCLK PIN RC.5 OUTPUT
RTC_IO PIN RC.6 INPUT PULLUP
RTC_CE PIN RC.7 OUTPUT
' available pins -- set to input and pullup
_RA0 PIN RA.0 INPUT PULLUP
_RA1 PIN RA.1 INPUT PULLUP
_RA2 PIN RA.2 INPUT PULLUP
_RA3 PIN RA.3 INPUT PULLUP
_RC0 PIN RC.0 INPUT PULLUP
_RC1 PIN RC.1 INPUT PULLUP
_RC2 PIN RC.2 INPUT PULLUP
_RC3 PIN RC.3 INPUT PULLUP
_RC4 PIN RC.4 INPUT PULLUP
' -------------------------------------------------------------------------
' Constants
' -------------------------------------------------------------------------
' value for _mode
cont_clock con 1
req_clock con 2
keycode con 3
' -------------------------------------------------------------------------
' Variables
' -------------------------------------------------------------------------
idle_count var word ' used for requested clock update
_rand var word ' used for f,f,RND function
i var byte ' temp
j var byte
k var byte
_i var byte ' temp for subs
_j var byte
_k var byte
row var byte ' passed between subs and main
col var byte
value var byte
f_count var byte ' count the number of 'f' key presses
_mode var byte ' Current mode of calculator, initially 0
save_mode var byte
temp var byte(16) 'temporary main code variables
rtc_address var temp(0)
rtc_data var temp(1)
p var temp(4)
q var temp(5)
time var byte(16) ' clock-related variables
s0 var time(0)
s1 var time(1)
m0 var time(2)
m1 var time(3)
h0 var time(4)
h1 var time(5)
mode_24 var time(6)
refresh_seconds var time(7)
last_sec var time(8)
last_min var time(9)
force_show_clock var time(10)
' -------------------------------------------------------------------------
INTERRUPT NOPRESERVE ' not currently used
' -------------------------------------------------------------------------
ISR_Start:
ISR_Exit:
RETURNINT
' =======================================================================
PROGRAM Start
' =========================================================================
' declare subroutines and functions
enter sub
wait_for_row sub
read_a_key sub
gen_random sub
set_clock sub
get_a_number func 1
rtc_read func 1,1,1
rtc_write sub 2,2
rtc_set sub
show_hours_minutes sub
show_seconds sub
set_refresh_seconds sub
mod_10 func 1,1,1
div_10 func 1,1,1
record_macro sub
pause_200 sub
' row, column lookup for entry
encode:
DATA 11,3 '0
DATA 8,0 '1
DATA 3,0 '2
DATA 5,0 '3
DATA 8,1 '4
DATA 3,1 '5
DATA 5,1 '6
DATA 8,2 '7
DATA 3,2 '8
DATA 5,2 '9
eex con 10
DATA 1,0 'EEX
dot con 11
DATA 3,3 '.
clx con 12
DATA 1,3 'CLX
prt con 13
DATA 1,2 'PRINT
ent con 14
DATA 7,0 'ENTER
r_s con 15
DATA 12,3 'R/S
f con 16
DATA 6,0 'f
x_y con 17
DATA 5,3 'swap
' decode uses a 15* col + row lookup and returns the number or a special code as follows:
' $EE-EEX
' $FF - n/a
' $DD - DSP
' $1F - 'f'
' $11 - '-'
' $12 - 'x'
' $13 - '+'
' $22 - 'RTN' (RND)
' $DD - '.'
decode:
DATA $FF
DATA $EE,$FF,$02,$0E,$03,$1F,$FF,$01,$0B,$0D,$FF,$0A,$0C,$DD
DATA $FF
DATA $12,$11,$05,$FF,$06,$FF,$13,$04,$FF,$FF,$FF,$FF,$FF,$FF
DATA $FF
DATA $FF,$FF,$08,$FF,$09,$FF,$FF,$07,$FF,$FF,$FF,$22,$FF,$FF
DATA $FF
DATA $FF,$FF,$DD,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$00,$FF,$FF,$FF
'-----------------------------------------------------------------------
' Program Code
' -------------------------------------------------------------------------
Start:
' initial conditions
_mode = 0 ' clock is off (overridden by RAM read)
mode_24 = 0 ' initially 12-hour format (overridden by RAM read)
refresh_seconds = 5 ' clock refresh interval (overridden by RAM read)
force_show_clock=1 ' force a clock update initially
f_count = 0
' Read the first non-volatile RAM memory location. If it contains a 233 then we
' assume that it has valid data. If not, it's likely that the battery has been replaced or
' is dead so do nothing. When the clock is set this location is set to 233.
i=rtc_read $C1
if i=233 then ' 233 is the key we use to indicate valid state variables in non-volatile RAM
' Check if there are saved macro keystrokes and play them
j=rtc_read $C9
if j>0 then
pause_200 ' wait for calculator to start
for i=1 to j
k=i*2
k=$C9 + k
p=rtc_read k
row=p & $0F
col=p >> 4
enter $FF ' special raw entry with row and col set already
pause 300
next i
endif
' Check if there are valid stored state variables
_mode=rtc_read $C3
if _mode=keycode then ' make sure keycode mode is never set on power up.
_mode=0
endif
mode_24=rtc_read $C5
refresh_seconds=rtc_read $C7
endif
top_loop:
random _rand ' keep calling random to help improve randomization
if _mode = keycode then
read_a_key ' sets row and col
if col<>$FF then
pause_200
k=col
p=div_10 row
q=mod_10 row
enter clx
enter k
enter dot
enter p
enter q
endif
endif
if _mode=cont_clock then
rtc_data = rtc_read $81 ' seconds
if rtc_data<>last_sec then
last_sec=rtc_data
s0=rtc_data & $0F
s1=rtc_data >> 4
i =10 * s1
i=i+s0
i=i // refresh_seconds
if force_show_clock=1 then
i=0
endif
rtc_data=rtc_read $83 ' minutes
if i=0 then
if force_show_clock=1 then ' this was a forced refresh so update HH.MM
last_min=-1
endif
if rtc_data <> last_min then ' only update HH.MM if needed
last_min=rtc_data
enter clx
show_hours_minutes
enter eex
endif
force_show_clock=0
show_seconds
endif
endif
endif
if _mode=req_clock then
idle_count = 0
count _DATA, 100,idle_count
if idle_count=0 then ' no activity on DATA line
enter clx
show_hours_minutes
show_seconds
enter r_s
endif
endif
' Look for 'f' key if found and last key pressed was 'f' too then look for next key and act on it
if _mode<>keycode then
read_a_key
if col<>$FF then
_i = col * 15
_i = _i + row
read decode+_i , value
if f_count = 2 then ' already 2 'f' presses so act on the next key, maybe
if value=$22 then ' 'RTN' (RND) key
gen_random
elseif value=$07 then ' '7' key
_mode=cont_clock ' turn on continuous update clock
force_show_clock=1 ' force an immediate clock display
elseif value=$13 then ' '+' key
save_mode = _mode
_mode=0 ' turn off clock display
set_clock
_mode = save_mode
elseif value=$11 then ' '-' key
force_show_clock=1 ' force an immediate clock display
mode_24=0
elseif value=$12 then ' '+' key
force_show_clock=1 ' force an immediate clock display
mode_24=1
elseif value=$08 then ' '8' key
save_mode = _mode
_mode=0 ' turn off clock display
set_refresh_seconds
force_show_clock=1
_mode = save_mode
elseif value=$04 then ' '4' key
_mode=req_clock ' turn on clock in requested time mode
elseif value=$DD then ' 'DSP' key
_mode=keycode
elseif value=$01 then ' '1' key
_mode=0 ' turn clock off
elseif value=$05 then ' '5' key
_mode=0 'turn clock off
record_macro
endif
rtc_write $C2, _mode
rtc_write $C4, mode_24
rtc_write $C6, refresh_seconds
rtc_write $C0, 233 ' mark them valid
f_count=0
else
if value=$1F then
f_count=f_count + 1 ' 'f' kep pressed
else
f_count=0
endif
endif
endif
endif
goto top_loop
end
'------------------------------------------------------------------
' Subs
'------------------------------------------------------------------
sub enter ' number or symbol constant (if $FF then expect row and col to be set)
' Simulate a key press by pulling the needed column to ground when the row is
' being scanned by the calculator.
_j = __PARAM1
if _j<>$FF then
_j = _j * 2
read encode+_j , row , col
endif
wait_for_row
_i = 1 << col ' make the needed pin an output
_i = not _i
tris_b = _i
RB = 0 ' and set it low
pause 50 ' for 50 ms (typical key press time)
input RB ' make it an input
pause 50
return
endsub
sub wait_for_row
' Sync with the keyboard scan routine in the calculator and wait until it's on the needed row.
' Call with row set.
do ' wait for counter reset
loop while _RCD = 0
do
loop while _RCD = 1
' loop until the needed row is being scanned
_i=0
do while _i<row
do
loop while _STR = 1
do
loop while _STR = 0
_i=_i+1
loop
return
endsub
sub read_a_key
' Sync with the calculator key board scanning routine and return row and column
' when the key is released. Return col=$FF if no key is pressed.
' This should work exactly the same way as the 97 does, or keys will be missed.
col=$FF
do ' wait for counter reset
loop while _RCD = 0
do
loop while _RCD = 1
row=0
do ' loop through the rows in tandem with the calculator looking for any key press
do
loop while _STR = 1
do
loop while _STR = 0
row=row+1
_k = RB & %1111
if _k=%1110 then
col=0
elseif _k=%1101 then
col=1
elseif _k=%1011 then
col=2
elseif _k=%0111 then
col=3
endif
if col<>$FF then ' have a key press so wait for key up
do
wait_for_row
_i= RB & %1111
loop until _i=%1111
return
endif
loop until row=15
return
endsub
sub gen_random
' generate a random number between 0 and 1
' feed the keystrokes to the calculator staring with a CLX and ending with swap, swap
' to act as a non-stack-lift entry (like the pi key would produce)
enter clx
enter dot
for i=1 to 10
random _rand
k= mod_10 _rand_MSB
enter k
next i
enter x_y
enter x_y
return
endsub
sub set_clock
' Set the clock using 12-hour format
' Read HH.MMSS from the keyboard and set the DS1302 time accordingly at 00 seconds.
pause_200
enter clx
h1=get_a_number
h0=get_a_number
_i=get_a_number ' and ignore it -- it should be a decimal point
m1=get_a_number
m0=get_a_number
s1=get_a_number
s0=get_a_number
rtc_write $8F, 0 'enable writes
rtc_data=s1 << 4
rtc_data=rtc_data | s0
rtc_write $80, rtc_data ' set seconds
rtc_data=m1 << 4
rtc_data=rtc_data | m0
rtc_write $82, rtc_data 'set minutes
rtc_data=h1 << 4
rtc_data=rtc_data | h0
rtc_write $84, rtc_data ' set hours
pause_200
enter clx
return
endsub
func get_a_number
do
read_a_key
loop until col<>$FF
_i = col * 15
_i = _i + row
read decode+_i , value
if value>9 then
value=1 ' return something that is allows legal in any time format
endif
return value
endfunc
sub rtc_write 'address, data
RTC_CE = 1 ' enable the RTC
_i=__PARAM1
_j=__PARAM2
rtc_set
_i=_j
rtc_set
RTC_CE = 0 ' disable the RTC
return
endsub
func rtc_read ' address
' Call the rtc_address return rtc_data set
RTC_CE = 1 ' enable the RTC
_i = __PARAM1
rtc_set _i
shiftin RTC_IO, RTC_SCLK, LSBPRE, rtc_data
RTC_CE = 0 ' disable the RTC
return rtc_data
endfunc
sub rtc_set
shiftout RTC_IO, RTC_SCLK, LSBFIRST, _i
return
endsub
sub show_hours_minutes
rtc_data = rtc_read $85
j=rtc_data & $0F
k=rtc_data >> 4
k=k*10
k=k+j
if mode_24=0 then ' 12-hour mode time correction
if k>=13 then
k=k-12
endif
if k=0 then
k=12
endif
endif
j= div_10 k
enter j
j = mod_10 k
enter j
enter dot
rtc_data = rtc_read $83
j= rtc_data >> 4
j=j & $07
enter j
j = rtc_data & $0F
enter j
return
endsub
sub show_seconds
rtc_data=rtc_read $81
j= rtc_data >> 4
j=j & $07
enter j
j = rtc_data & $0F
enter j
return
endsub
sub set_refresh_seconds
pause_200
enter clx
i=get_a_number
j=get_a_number
refresh_seconds=i*10
refresh_seconds=refresh_seconds+j
if refresh_seconds > 60 then
refresh_seconds=60
endif
if refresh_seconds < 1 then
refresh_seconds=1
endif
pause_200
enter clx
return
endsub
func mod_10
_i = __PARAM1
_i = _i // 10
return _i
endfunc
func div_10
_i = __PARAM1
_i = _i / 10
return _i
endfunc
sub pause_200
pause 200
return
endsub
sub record_macro
pause_200
enter clx
rtc_write $C8, 0
for j=1 to 26
do
read_a_key
loop until col<>$FF
i = col << 4
i = i | row
rtc_write $C8, j ' total number of key strokes -- max=26
k=j*2
k=k+$C8
rtc_write k, i
next j
return
endsub
Do you like the way the code block scrolls or should it just get very very large for cases like this?