$mod51
$debug
mcon equ 0c6h
ta equ 0c7h
org 0 ; reset interrupt vector
sjmp start
org 0bh ; timer 0 overflow interrupt vector
ajmp timerint
org 2bh ; power fail interrupt vector
ajmp powerfail
org 30h ; message strings
crlf: db 0dh,0ah,'$'
timer: db 17, 50, 08, 15 ; January
db 18, 35, 07, 40 ; February
db 19, 15, 06, 50 ; March
db 19, 55, 05, 50 ; April
db 20, 35, 05, 05 ; May
db 21, 00, 04, 45 ; June
db 20, 55, 05, 00 ; July
db 20, 15, 05, 30 ; August
db 19, 15, 06, 15 ; September
db 18, 20, 06, 55 ; October
db 17, 35, 07, 40 ; November
db 17, 25, 08, 10 ; December
org 78h ; start of main program code
start: mov sp,#17h ; allow use of Rn banks 00, 01, and 10
acall inithw ; initialize hardware
setb rs1 ; switch to controller register space
mov r5,#3 ; R5:R4 = controller set point
mov r4,#232
mov r3,#0fh ; R3:R2 = current HV control value
mov r2,#0ffh
setb psw.5 ; start with control/transmit enabled
clr rs1 ; return to default register space
mov r7,#6 ; initialize counter variables used in
mov r6,#25 ; the timer interrupt handler
main:
sjmp main ; continue main loop
; ReadADC. Get 12 bit value from the MAX186 on port 1. R1 selects the
; channel to read (0 to 8), or more specifically, SEL2-SEL0. The most
; significant nybble of conversion is returned in R1 bits 3-0, and the
; least significant byte is returned in R0. Pin assignments:
; P1.0 <-- Dout, P1.1 --> Din, P1.2 --> SCLK, P1.3 --> CS.
; (Operates in currently selected register space).
readadc:
clr p1.3 ; take CS low to wake up the MAX186
mov a,r1 ; prepare a control byte
rl a ; channel select bits need to end up
rl a ; in a.6 through a.4
rl a
rl a
orl a,#10001110b ; complete the control byte
mov r0,#8 ; initialize bit counter
nbit1: rlc a ; send the control byte, MSB first
mov p1.1,c
setb p1.2 ; pulse SCLK between bits
clr p1.2
djnz r0,nbit1 ; repeat for all eight control bits
nop ; wait 10us to insure complete conversion
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
clr a
mov r0,#4 ; initialize bit counter
nbit2: setb p1.2 ; shift high nybble of result into acc,
clr p1.2 ; pulsing SCLK between bits
mov c,p1.0
rlc a
djnz r0,nbit2 ; repeat to get four bits
mov r1,a ; store the high nybble in R1
mov r0,#8 ; initialize bit counter
nbit3: setb p1.2 ; shift lower two nybbles of result into
clr p1.2 ; accumulator, pulsing SCLK between bits
mov c,p1.0
rlc a
djnz r0,nbit3 ; repeat to get eight bits
mov r0,a ; store the low byte in R0
setb p1.2 ; shift out the remaining four bits
clr p1.2 ; (zeros) required by the MAX186
setb p1.2
clr p1.2
setb p1.2
clr p1.2
setb p1.2
clr p1.2
setb p1.3 ; take CS high
ret
; WriteDAC. Send 12 bit value (most significant nybble in R3 bits 3-0,
; least significant byte in R2) to the DAC-8043 on port 1. Pin
; assignments: P1.4 --> SRI, P1.5 --> CLK, P1.6 --> LD.
; (Operates in currently selected register space).
writedac:
mov b,r3 ; save R3 register (caller needs it)
mov a,r3 ; put upper nybble in accumulator
rlc a ; prepare to shift out the MSB
rlc a
rlc a
rlc a
mov r3,#4 ; initialize bit counter
nbit4: rlc a ; shift four bits out to SRI,
mov p1.4,c ; providing a clock pulse on CLK
setb p1.5 ; between each one
clr p1.5
djnz r3,nbit4 ; repeat four times
mov a,r2 ; put lower two nybbles in accumulator
mov r3,#8 ; initialize bit counter
nbit5: rlc a ; shift eight bits out to SRI,
mov p1.4,c ; providing a clock pulse on CLK
setb p1.5 ; between each one
clr p1.5
djnz r3,nbit5 ; repeat eight times
clr p1.6 ; pulse LD low to load the DAC register
setb p1.6
mov r3,b ; restore R3 register
ret
; BinToFloat. Converts a 12 bit value (most significant nybble in R1 bits
; 3-0, least significant byte in R0) into an ASCII floating point string
; of the form wxyz. The ASCII bytes for each digit are returned in R3,
; R2, R1, and R0 (for digits w, x, y, and z, respectively) of the RS=01
; register space. May only be called from the RS=10 register space.
bintofloat:
mov a,r0 ; transfer parameters to new Rn space
clr rs1 ; and remain there for this subroutine
setb rs0
mov r0,a
setb rs1
clr rs0
mov a,r1
clr rs1
setb rs0
mov r1,a
mov r3,#0ffh
mov a,r0
clr c
subtra: inc r3
mov b,a
subb a,#100
jnc subtra
clr c
cjne r1,#0,next
sjmp skip
next: djnz r1,subtra
subtra2:mov b,a
subb a,#100
inc r3
jnc subtra2
skip: mov a,b
mov b,#10
div ab
add a,#48 ; convert to ASCII
mov r1,a ; R1 now has the 10's place
mov a,b
add a,#48 ; convert to ASCII
mov r0,a ; R0 now has the 1's place
mov a,r3
mov b,#10
div ab
add a,#48 ; convert to ASCII
mov r3,a ; R3 now has the 1000's place
mov a,b
add a,#48 ; convert to ASCII
mov r2,a ; R2 now has the 100's place
setb rs1 ; restore controller Rn space
clr rs0
ret
; SendFloat. Takes the four ASCII numeric characters returned by BinToFloat
; (R3 . R2 R1 R0 in the RS=01 register space) and sends them to the serial
; port after a leading space character. May only be called from the RS=10
; register space.
sendfloat:
clr rs1 ; values are in separate Rn space
setb rs0
jnb ti,$ ; send a space character
clr ti
mov sbuf,#' '
jnb ti,$ ; send ones place
clr ti
mov sbuf,r3
jnb ti,$ ; send decimal point
clr ti
mov sbuf,#'.'
jnb ti,$ ; send tenths place
clr ti
mov sbuf,r2
jnb ti,$ ; send hundredths place
clr ti
mov sbuf,r1
jnb ti,$ ; send thousandths place
clr ti
mov sbuf,r0
setb rs1 ; restore controller Rn space
clr rs0
ret
; SendStr. Sends characters pointed to by DPTR out the serial port until
; a '$' character is encountered. Uses currently selected register space.
sendstr:
mov a,#0
movc a,@a+dptr
cjne a,#'$',sendnext
ret
sendnext:
jnb ti,$
clr ti
mov sbuf,a
inc dptr
sjmp sendstr
; Add16. Adds the 16-bit number in R1:R0 to the 16-bit number in R3:R2, and
; stores the result in R3:R2. Uses the currently selected register space.
add16: mov a,r2
add a,r0
mov r2,a
mov a,r3
addc a,r1
mov r3,a
ret
; Sub16. Subtracts the 16-bit number in R5:R4 from the 16-bit number in
; R1:R0, and stores the result in R1:R0. Uses the currently selected
; register space.
sub16: clr c
mov a,r0
subb a,r4
mov r0,a
mov a,r1
subb a,r5
mov r1,a
ret
; DivBy2. Divides the 16-bit number in R1:R0 by two, preserving the sign.
; Uses the currently selected register space.
divby2: clr c
mov a,r1
anl a,#80h
jz positive
setb c
positive:
mov a,r1
rrc a
mov r1,a
mov a,r0
rrc a
mov r0,a
ret
; Trunc12. Examines the 16-bit signed integer in R3:R2 and truncates it
; to a 12-bit unsigned integer. Values < 0 are truncated to 0 and
; values > 0xfff are truncated to 0xfff. Uses the currently selected
; register space.
trunc12:
mov a,r3
rlc a
jc isneg ; test sign bit
anl a,#0e0h ; check other three bits past 12-bit limit
jz isok
mov r3,#0fh ; R3:R2 > 0xfff, so truncate to 0xfff
mov r2,#0ffh
ret
isneg: mov r3,#0 ; R3:R2 < 0, so truncate to 0
mov r2,#0
isok: ret ; 0 < R3:R2 < 0xfff, so leave as-is
; InitHW. Initializes the serial port, ADC, DAC, and processor safety
; features at beginning of program execution.
inithw: mov r3,#0fh ; send 0xfff to the DAC so we will
mov r2,#0ffh ; start with the PMT high voltage
acall writedac ; turned off
mov ta,#0aah ; timed access
mov ta,#55h
anl pcon,#0fbh ; disable watchdog timer temporarily
mov ie,#82h ; enable only the timer 0 interrupt
mov tmod,#21h ; select timer 1 mode 2 (auto reload)
; and timer 0 mode 1 (16 bit)
mov th1,#0f0h ; this is correct for 1200 bps with a
mov tl1,#0f0h ; 7.3728 MHz crystal
mov th0,#10h ; this will generate overflow interrupts
mov tl0,#08h ; every 0.1s (10 Hz)
mov scon,#52h ; select serial mode 1
mov tcon,#40h ; start up timer 1 only (for serial port)
mov r3,#0eh ; allow roughly 3 seconds for the TNC
loop2: mov r2,#0ffh ; to power up and arrive at the command
loop1: mov r1,#0ffh ; prompt
djnz r1,$
djnz r2,loop1
djnz r3,loop2
jnb ti,$ ; put the TNC into transparent mode by
clr ti ; sending "tr"
mov sbuf,#'t'
jnb ti,$
clr ti
mov sbuf,#'r'
jnb ti,$
clr ti
mov sbuf,#0dh
mov ta,#0aah ; timed access
mov ta,#55h
setb ip.7 ; set RWT (reset watchdog timer)
mov ta,#0aah ; timed access
mov ta,#55h
orl pcon,#4ch ; set POR, EWT, and EPFW
setb tr0 ; start timer 0 for 10 Hz interrupts
acall readadc ; "prime" the analog input channels
acall readadc
ret
; PowerFail. ISR for the power fail interrupt; sets high voltage to zero
; and takes any other action necessary to prepare for program termination,
; then waits in a loop until Vcc again reaches a safe level or program
; execution is terminated.
powerfail:
push acc
mov r3,#0fh ; turn off high voltage to the PMT
mov r2,#0ffh
acall writedac
danger: acall resetwt ; wait until the danger condition passes
mov a,pcon ; or execution is terminated
mov a,pcon
anl a,#20h
jnz danger
pop acc
reti
; TimerInt. ISR for a periodic 10 Hz interrupt generated by timer 0.
; This code utilizes the RS=10 register space (the "controller space")
; as well as the standard RS=00 space.
timerint:
clr tr0 ; halt timer
mov th0,#10h ; reload for another 0.1s run
mov tl0,#08h
setb tr0 ; restart timer
acall gettime ; fetch date/time information
clr rs1 ; go to the date/time register space
setb rs0
mov a,r4 ; convert BCD month into decimal month-1
jnb acc.4,notens
anl a,#11101111b
add a,#9
sjmp testtime
notens: clr c
subb a,#1
testtime:
mov b,#4 ; multiply value by 4 to index into the
mul ab ; times array
jnb psw.5,testtim2 ; add two if we want to look at a potential
add a,#2 ; turnoff (to fetch sunrise times)
testtim2:
mov r7,a ; save this index value for later uses
mov a,r2 ; convert BCD hours into decimal hours
anl a,#0f0h
rr a
rr a
rr a
rr a
mov b,#10
mul ab
mov b,r2
anl b,#0fh
add a,b
mov b,a
mov a,r7
mov dptr,#timer ; fetch the hours value from the array,
movc a,@a+dptr ; appropriate for the current month
cjne a,b,nomatch ; test hour
mov a,r1 ; convert BCD minutes into decimal minutes
anl a,#0f0h
rr a
rr a
rr a
rr a
mov b,#10
mul ab
mov b,r1
anl b,#0fh
add a,b
mov b,a
mov a,r7
inc a ; fetch the minutes value from the array,
movc a,@a+dptr ; appropriate for the current month
cjne a,b,nomatch ; test minute
cpl psw.5 ; change modes from nighttime (xmit) to
; daytime (standby), or vice-versa
jb psw.5,nomatch
setb rs1
clr rs0
mov r3,#0fh ; turn off high voltage to the PMT
mov r2,#0ffh
acall writedac
acall resetwt
clr rs1
clr rs0
reti
nomatch:
clr rs1 ; insure that we start out in default
clr rs0 ; register space
jnb psw.5,short ; do nothing if the "transmit bit" is zero
djnz r7,short1 ; return early if not yet time for a control
; action
mov r7,#6 ; schedule a control cycle every ~0.6s
sjmp ctrl
short1: ajmp short
ctrl: setb rs1 ; switch to controller register space
clr rs0
mov r1,#0 ; read PMT signal
acall readadc
mov a,r1 ; save PMT value for possible later display
mov r7,a
mov a,r0
mov r6,a
acall sub16 ; subtract set point (R5:R4) from PMT
acall divby2 ; level (R1:R0), and divide resultant
acall divby2 ; (R1:R0) by eight
acall divby2
acall add16 ; add result to current HV control value
acall trunc12 ; (R3:R2) and truncate to positive 12-bit
; range limits
acall writedac ; take control action
acall resetwt ; reset watchdog timer
clr rs1 ; switch to default register space
clr rs0
djnz r6,short ; return without transmitting a status line
; if it is not yet time
mov r6,#25 ; schedule a status run every ~15s
mov r7,#3 ; schedule a control cycle sooner than
; usual, since the status transmission
; takes ~0.34s
setb rs1 ; switch to controller register space
clr rs0
clr tr0 ; this is gonna take >> 0.1 seconds...
acall sendtime ; send current time/date to serial port
acall resetwt
mov a,r3 ; display HV value
mov r1,a
mov a,r2
mov r0,a
acall bintofloat
acall sendfloat
acall resetwt
mov a,r7 ; retrieve and display last PMT value
mov r1,a
mov a,r6
mov r0,a
acall bintofloat
acall sendfloat
acall resetwt
mov r1,#4 ; display PD value
acall readadc
acall bintofloat
acall sendfloat
acall resetwt
mov r1,#1 ; display temperature value
acall readadc
acall bintofloat
acall sendfloat
acall resetwt
mov dptr,#crlf ; add CR/LF at end of line
acall sendstr
acall resetwt
clr rs1 ; restore default register space
clr rs0
mov th0,#7ch ; these should be tweaked to equalize delays
mov tl1,#00h ; depending on the time required for
setb tr0 ; transmitting the status line
short: acall resetwt ; reset watchdog timer
reti
; ResetWT. Resets the watchdog timer on-board the DS5000T processor module.
resetwt:
mov ta,#0aah ; timed access
mov ta,#55h
setb ip.7 ; set RWT
ret
; GetTime. Reads the DS1215 Real-Time Clock onboard the DS5000T processor
; module. GetTime may only be called from the RS=00 (default)
; register space.
gettime:
clr rs1 ; switch to a spare set of Rn registers
setb rs0
mov mcon,#0f8h ; turn off CE2 for memory access
acall open ; set up to read date/time
acall rbyte ; read hundredths/tenths of a second
mov r6,a
acall rbyte ; read ones/tens of seconds
mov r0,a
acall rbyte ; read minutes
mov r1,a
acall rbyte ; read hours
mov r2,a
acall rbyte ; discard day of week
acall rbyte ; read date
mov r3,a
acall rbyte ; read month
mov r4,a
acall rbyte ; read year
mov r5,a
acall close ; close clock register
clr rs1 ; go back to default register space
clr rs0
ret
; SendTime. Sends a concise time/date ASCII string out the serial port,
; given the data in RS=01 from the GetTime routine. SendTime may only
; be called from the RS=10 (controller) register space.
sendtime:
clr rs1 ; switch to a spare set of Rn registers
setb rs0
mov a,r2 ; send first hour digit
anl a,#0f0h
rr a
rr a
rr a
rr a
add a,#30h
jnb ti,$
clr ti
mov sbuf,a
mov a,r2 ; send second hour digit
anl a,#0fh
add a,#30h
jnb ti,$
clr ti
mov sbuf,a
mov a,r1 ; send first minute digit
anl a,#0f0h
rr a
rr a
rr a
rr a
add a,#30h
jnb ti,$
clr ti
mov sbuf,a
mov a,r1 ; send second minute digit
anl a,#0fh
add a,#30h
jnb ti,$
clr ti
mov sbuf,a
mov a,r0 ; send first second digit
anl a,#0f0h
rr a
rr a
rr a
rr a
add a,#30h
jnb ti,$
clr ti
mov sbuf,a
mov a,r0 ; send second second digit
anl a,#0fh
add a,#30h
jnb ti,$
clr ti
mov sbuf,a
jnb ti,$ ; send "."
clr ti
mov sbuf,#'.'
mov a,r6 ; send tenths of a second digit
anl a,#0f0h
rr a
rr a
rr a
rr a
add a,#30h
jnb ti,$
clr ti
mov sbuf,a
jnb ti,$ ; send " "
clr ti
mov sbuf,#' '
mov a,r4 ; send first month digit
anl a,#0f0h
rr a
rr a
rr a
rr a
add a,#30h
jnb ti,$
clr ti
mov sbuf,a
mov a,r4 ; send second month digit
anl a,#0fh
add a,#30h
jnb ti,$
clr ti
mov sbuf,a
mov a,r3 ; send first date digit
anl a,#0f0h
rr a
rr a
rr a
rr a
add a,#30h
jnb ti,$
clr ti
mov sbuf,a
mov a,r3 ; send second date digit
anl a,#0fh
add a,#30h
jnb ti,$
clr ti
mov sbuf,a
mov a,r5 ; send first year digit
anl a,#0f0h
rr a
rr a
rr a
rr a
add a,#30h
jnb ti,$
clr ti
mov sbuf,a
mov a,r5 ; send second year digit
anl a,#0fh
add a,#30h
jnb ti,$
clr ti
mov sbuf,a
setb rs1 ; go back to controller register space
clr rs0
ret
;******************************************
;**** SUBROUTINE TO OPEN TIMEKEEPER *******
;******************************************
;
; This subroutine executes the sequence of reads and writes which
; is required in order to open communication with the timekeeper.
; The subroutine returns with the timekeeper opened for data
; access and with both the accumulator and B register modified.
;
OPEN: ACALL CLOSE ; Make sure it is closed.
MOV B,#4 ; Set pattern period count.
MOV A,#0C5H ; Load first byte of pattern.
OPENA: ACALL WBYTE ; Send out the byte.
XRL A,#0FFH ; Generate next pattern byte.
ACALL WBYTE ; Send out the byte.
SWAP A ; Generate next pattern byte.
DJNZ B,OPENA ; Repeat until 8 bytes sent.
RET ; Return.
;******************************************
;**** SUBROUTINE TO CLOSE TIMEKEEPER ******
;******************************************
;
; This subroutine insures that the registers of the timekeeper
; are closed by executing 9 successive reads of the date and time
; registers. The subroutine returns with both the accumulator
; and the B register modified.
;
CLOSE: MOV B,#9 ; Set up to read 9 bytes.
CLOSEA: ACALL RBYTE ; Read a byte;
DJNZ B,CLOSEA ; Loop for 9 byte reads.
RET ; Return.
;******************************************
;**** SUBROUTINE TO READ A DATA BYTE ******
;******************************************
;
; This subroutine performs a "context switch" to the CE2 data
; space and then reads one byte from the timekeeping device.
; Then it switches back to the CE1 data space and returns
; the byte read in the accumulator, with all other registers
; unchanged.
;
RBYTE: PUSH DPL ; Save the data
PUSH DPH ; pointer on stack.
PUSH MCON ; Save MCON register.
ORL MCON,#4 ; Switch to CE2.
PUSH B ; Save the B register.
MOV DPL ,#4 ; Set up for data input.
MOV DPH, #0 ; Set high address byte.
MOV B, #8 ; Set the bit count.
LI: PUSH ACC ; Save the accumulator.
MOVX A, @DPTR ; Input the data bit.
RLC A ; Move it to carry.
POP ACC ; Get the accumulator.
RRC A ; Save the data bit.
DJNZ B, LI ; Loop for a whole byte.
POP B ; Restore the B register.
POP MCON ; Restore MCON register.
POP DPH ; Restore the data
POP DPL ; pointer from stack.
RET ; Return.
;******************************************
;**** SUBROUTINE TO WRITE A DATA BYTE *****
;******************************************
;
; This subroutine performs a "context switch" to the CE2 data
; space and then writes one byte from the accumulator to the
; timekeeping device. Then it switches back to the CE1 data
; space and returns with all registers unchanged.
;
WBYTE: PUSH DPL ; Save the data
PUSH DPH ; pointer on stack.
PUSH MCON ; Save MCON register.
ORL MCON,#4 ; Switch to CE2.
PUSH B ; Save the B register.
MOV DPH, #0 ; Set high address byte.
MOV B, #8 ; Set the bit count.
LO: PUSH ACC ; Save the accumulator.
ANL A, #1 ; Set up bit for output.
MOV DPL, A ; Set address to write bit.
MOVX A, @DPTR ; Output the data bit.
POP ACC ; Restore the accumulator.
RR A ; Position next bit.
DJNZ B, LO ; Loop for a whole byte.
POP B ; Restore the B register.
POP MCON ; Restore MCON register.
POP DPH ; Restore the data
POP DPL ; pointer from stack.
RET ; Return.
end