
; S00045-A / MPU: SC85203CPT (mask-programmed MC6801), compiler: THRsim11

; Code reverse-engineered from dump taken from an IC borrowed from an LMT-10x main board

; Code most likely excerpted down from S00036; some loose ends are flapping in the breeze

; ---------- Tech notes

; The LMT-1xx series quickly superseded and replaced the original LMT-I units
; These were in turn superseded by the LMT-3000 (later, LCR-3000) in 1993

; LMT-10x units - meter style:
; 5558C01Gxx - Whole unit
; 2D25460Gxx - front subassembly (lid, front, main)
; 5558C17Gxx - display / 5V supply board
; 2D26148Gxx - main board
; 5559C39Gxx - rear subassembly (meter style)
; 2D25922Gxx - transformer / relay board (meter style)
; 2D26120Gxx - rear subassembly (A-base style)
; nnnnnnnGxx - transformer / relay board (A-base style) - possible latched relay style

; LMT-11x units - standard LCR style:
; 7312C01Gxx - Whole unit
; 7312C18Gxx - main board (in door)
; 7312C21Gxx - transformer / relay board (in base)

; 7312C21 board was adopted as an universal transformer / relay board for all ABB LCRs
; 7312C21 board has both Emetcon and Versacom relay designators - A/2, B/3, C/1 (no D)

; ---------- MPU registers - $00 to $1F

; I/O ports, data direction registers / (for the latter, 0 = input, 1 = output)
; DDR settings do not change - they are only refreshed

P1DDR   EQU   0 ; Port 1 data direction register
PORT1   EQU   2 ; Port 1 I/O register
; Pins 20 - 13
; bit 7 - 1 Deadman timer drive
; bit 6 - 0 Relay C / D bypass flag (2D26148: open, 7312C18: grounded)
; bit 5 - 1 Relay D bit (LMT-2 / DCT only)
; bit 4 - 1 Relay C bit (LMT-2 / DCT, optional on 7312C18)
; bit 3 - 1 Relay B bit
; bit 2 - 1 Relay A bit
; bit 1 - 0 Inbound data stream
; bit 0 - 0 Cold Load pickup (default: high, 7.5 minute load shed upon power-up)

P2DDR   EQU   1 ; Port 2 data direction register
PORT2   EQU   3 ; Port 2 I/O register
; Bits 7-5 are virtual and are a snapshot of P2.2 - P2.0 taken at power-up
; These three pins set the chip's operating mode and are readable via P2DDR bits 7-5
; Single-chip mode is Mode 7, so these pins need to be pulled high
; Pins 12 - 8
; bit 4 - 0 Freq select jumper (1 = 12.5, 0 = 9.6)
; bit 3 - 1 (2D26148: open, 7312C18: pull-up)
; bit 2 - 1 pull-up
; bit 1 - 0 NMI drive (pull-up)
; bit 0 - 1 pull-up

P3DDR   EQU   4 ; Port 3 data direction register
PORT3   EQU   6 ; Port 3 I/O register
; Pins 37-30
; bit 7 - 0 Gold address bit 1
; bit 6 - 0 Gold address bit 0
; bit 5 - 0 Silver address bit 5
; bit 4 - 0 Silver address bit 4
; bit 3 - 0 Silver address bit 3
; bit 2 - 0 Silver address bit 2
; bit 1 - 0 Silver address bit 1
; bit 0 - 0 Silver address bit 0
; An addressing comb (with fingers clipped to set address) is inserted into connector
; Intact finger = bit is 0, clipped finger = bit is pulled up to a 1
; Add 1 to binary value to determine programmed address
; Range for Gold (top-level) addresses is 1-4, Silver (2nd level) is 1-60
; LMT-2 / DCT units have Bronze (1-256) and Lead (1-4096) available to them

P4DDR   EQU   5 ; Port 4 data direction register
PORT4   EQU   7 ; Port 4 I/O register
; Pins 29 - 22
; bit 7 - 0 pull-up (NMI testing)
; bit 6 - 1 unused
; bit 5 - 1 unused
; bit 4 - 1 unused
; bit 3 - 1 unused
; bit 2 - 0 aux freq sel resistor (always set as pull-up)
; bit 1 - 0 aux freq sel resistor (always set as pull-up)
; bit 0 - 1 Test (status) LED

; general MPU registers

TMCSR   EQU   8 ; Timer Control and Status register
; Code sets bits: 0000X0001 / Only active bit is 3, rest are read-only or never change
; Bit 7 Input Capture flag - read-only (not used)
; Bit 6 Output Compare Flag - read-only (not used)
; Bit 5 Timer Over flow flag - read-only (not used)
; Bit 4 Input Capture Interrupt enable (0 = inhibited)
; Bit 3 IRQ2 enabled when OCF is set? / 0 inhibits, 1 enables
; Bit 2 Timer Overflow Interrupt enable (0 = inhibited)
; Bit 1 Input Capture Register interrupt transition direction (0 = direction 0 to 1)
; Bit 0 if this bit AND Port 2 DDR sets P2.0 = 1, OCF flag appears at port P2.1 (is 1)

COUNTR  EQU   9 ; (2 byte)
; 16-bit free-running counter, incrementing each half-cycle of clock
; TOF bit in TMCSR is set each time the counter rolls over from FFFF to 0000
; OCF bit in TMCSR is set when the value matches that in OUTCMP

OUTCMP  EQU   11 ; (2 byte) - Output Compare Register
; Interval value is written into this register

P3CSR   EQU   15 ; Port 3 Control & Status register
; Additional options in single-chip mode only for Port 3, IS3 (SC1), and OS3 (SC2) pins
; Single-chip mode: SC1 pin (39) is Strobe 3 In / not used
; Single-chip mode: SC2 pin (38) is Strobe 3 Out / 2D26148 (opt jpr), 7312C18 (pull-up)
; These options are not used in this application, so all bits are left at 0

SCIMOD  EQU   16 ; not used but must be written to - SCI Rate / Mode Control
SCICOM  EQU   17 ; not used but must be written to - SCI Transmit / Receive register

RAMCR   EQU   20 ; Bit 6 is the only one under user control, 1 = enables internal RAM

; ---------- On-chip RAM: $80 to $FF

byteNow EQU   128 ; incoming bits being gathered from front-end stage
ctPuls  EQU   129 ; Bit counter to mark completion of one incoming received byte
rlyMsk  EQU   130 ; 00 = all four relay bits active, otherwise Relays A and B only
msgEnd  EQU   131 ; flag to mark end of message
msgRej  EQU   132 ; if NOT zero, current message has been rejected
bitGrp  EQU   133 ; One count per bit to analyze in each message section
P20ctr  EQU   134 ; Counter to change state of P2.0 every 4 counts (is pull-up only)
unitID  EQU   135 ; ID string from message
adrLvl  EQU   136 ; Emetcon address level / 1 = Gold, 2 = Silver (Bronze 3?, Lead 4?)
bchSum  EQU   137 ; (2 byte) BCH checksum - if remains 0, message received OK
;                   Second byte is filler due to loading $89 into index register
ctPhse  EQU   139 ; selects next table in demodulator code (values: 0, 8, 16, 24)
mTblLst EQU   140 ; Previous calculated M-Sum value
bitIntv EQU   141 ; bit interval counter?
byteLst EQU   142 ; Byte being evaluated for bit transition and direction
byteOld EQU   143 ; Previously evaluated byte, saved for one more comparison
valueIi EQU   144 ; Instantaneous demodulator value - 0 degree
valueQi EQU   145 ; Instantaneous demodulator value - 90 degree
mTblOld EQU   146 ; Previously analyzed mTblLst value
MValue  EQU   147 ; Calculated M value?
phsTmp  EQU   148 ; Temporary byte during phase value calculation
g1ValI  EQU   149 ; Group 1: valueIi
g1ValQ  EQU   150 ; Group 1: valueQi
g1VecSm EQU   151 ; Group 1: Vector sum
g1avgI  EQU   152 ; Group 1: I average value (2 bytes)
g1avgQ  EQU   154 ; Group 1: Q average value (2 bytes)
g1MSum  EQU   156 ; Group 1: M value sum
g2ValI  EQU   157 ; Group 2: valueIi
g2ValQ  EQU   158 ; Group 2: valueQi
g2VecSm EQU   159 ; Group 2: Vector sum
g2avgI  EQU   160 ; Group 2: I average value (2 bytes)
g2avgQ  EQU   162 ; Group 2: Q average value (2 bytes)
g2MSum  EQU   164 ; Group 2: M value sum
g3ValI  EQU   165 ; Group 3: valueIi
g3ValQ  EQU   166 ; Group 3: valueQi
g3VecSm EQU   167 ; Group 3: Vector sum
g3avgI  EQU   168 ; Group 3: I average value (2 bytes)
g3avgQ  EQU   170 ; Group 3: Q average value (2 bytes)
g3MSum  EQU   172 ; Group 3: M value sum
g4ValI  EQU   173 ; Group 4: valueIi
g4ValQ  EQU   174 ; Group 4: valueQi
g4VecSm EQU   175 ; Group 4: Vector sum
g4avgI  EQU   176 ; Group 4: I average value (2 bytes)
g4avgQ  EQU   178 ; Group 4: Q average value (2 bytes)
g4MSum  EQU   180 ; Group 4: M value sum
nxtVec  EQU   181 ; (2 byte) Pointer to next vector sum
relJump EQU   183 ; (2 byte) Value for relative jump after a demodulator cycle finishes
msgStrt EQU   185 ; flag to indicate start of received msg
INTV_W  EQU   186 ; (2 byte) interval timer - working value
actByte EQU   188 ; Command byte - in this case, which relays are to be operated
mValTmp EQU   189 ; (2 byte) Temporary storage of index during M value calcs
nmiCt   EQU   191 ; counter for use during NMI code cycle (factory test code)
msgTbl  EQU   192 ; Incoming message buffer (5 bytes / 40 bits)
rcvByt  EQU   196 ; assembled byte after header was processed
latCmd  EQU   197 ; Latched relay command flag / 5 = close, 6 = open
ledFlg  EQU   198 ; Flag to determine whether Test LED is on or off
ldTimA  EQU   199 ; Load A timer
ldTimB  EQU   200 ; Load B timer
ldTimC  EQU   201 ; Load C timer
ldTimD  EQU   202 ; Load D timer
rlyBit  EQU   203 ; relay bits to write to Port 1 outputs (bits 2 - 5)
armTmr  EQU   204 ; timing window for latched relay command
adrCpy  EQU   205 ; copy of adrLvl
ldIntvl EQU   206 ; Load interval length / 0 = 7.5m, 1 = 15m, 2 = 30m, 3 = 60m
flgRmt  EQU   207 ; Remote / local flag from S00036 (0 = remote, 1 = local) - ALWAYS 0
INTV_M  EQU   208 ; (2 byte) interval timer - master value from frequency table
SMPCLK  EQU   210 ; (2 byte) sampling clock interval from frequency table
endVar  EQU   211 ; End of variable space (for cleanup after RAM test finishes)
Stack   EQU   255 ; end of internal RAM / main stack location

; ---------- Start of ROM space: $F800

        ORG   $F800

; firmware number and revision

romFW   FDB   45 ; Spec S00045
        FCB   1  ; Revision A

; This section is executed each time the Output Compare interrupt is invoked by the MPU

vec_tmr LDAA  TMCSR ; read and discard bits 5-7
        LDD   OUTCMP
        ADDD  SMPCLK
        STD   OUTCMP
        LDAA  #$80
        ORAA  PORT1 ; do an indirect fetch of incoming serial bit
        STAA  PORT1 ; send Deadman pulse
        RORA
        RORA
        ROL   byteNow ; roll serial bit into incoming byte
        DEC   ctPuls
        LDAA  #$07
        ANDA  ctPuls
        BEQ   tmr_01 ; exit unless 8 bits have been gathered

; Next line serves as a normal exit for above section AND a hook for (4) unused vectors

vec_nul RTI

; Continuation of timer code

; Initialize ports, read settings, set variables

tmr_01  CLI
        LDD   #$40BC
        STAA  RAMCR ; $40 enables internal RAM
        STAB  P1DDR
        LDD   #$0D79
        STAA  P2DDR
        STAB  P4DDR
        LDD   #$0009
        STAA  SCIMOD ; zero out otherwise unused registers
        STAA  SCICOM
        STAA  P3DDR
        STAA  P3CSR
        STAB  TMCSR ; default state: 00001001
        LDAA  PORT2
        ANDA  #$10 ; read primary frequency resistors
        LDAB  PORT4
        ANDB  #$06 ; read aux frequency resistors
        LSRA
        ABA
        ASLA
        LDX   #freqTbl
        TAB
        ABX
        LDD   $00,X ; fetch and save values from the table
        STD   SMPCLK
        LDD   $02,X
        STD   INTV_M
        LDAA  PORT1 ; Get P1.6 status, save in rlyMsk
        ANDA  #$40
        STAA  rlyMsk
        CLR   nmiCt ; reset NMI internal counter
        CLR   flgRmt

; advance counter, change state of P2.0

        LDAA  P20ctr
        BNE   tmr_02
        LDAA  #$03
        BRA   tmr_03
tmr_02  DECA
        BNE   tmr_04
tmr_03  LDAB  PORT2
        EORB  #$01
        STAB  PORT2
tmr_04  STAA  P20ctr

; decrement interval timers

        LDAA  armTmr
        BEQ   tmr_05
        DEC   armTmr
tmr_05  LDX   INTV_W
        DEX
        STX   INTV_W
        BNE   tmr_08
        LDX   INTV_M
        STX   INTV_W
        LDX   #ldTimD ; set pointer to Load D
tmr_06  LDAA  $00,X
        BEQ   tmr_07
        DECA
        STAA  $00,X
tmr_07  DEX
        CPX   #ldTimA-1 ; finished all four loads?
        BPL   tmr_06

tmr_08  LDD   byteNow ; consecutive reads - ctPuls in accB
        STAA  byteLst
        ANDB  #$18
        STAB  ctPhse
        BNE   tmr_09
        JMP   dmd_16

tmr_09  LDAA  msgStrt
        BMI   msgDec
        LDAA  #$FB
        ANDA  PORT2
        STAA  PORT2 ; clear P2.2

; Start of code to process incoming messages (Coherent Phase-Shift Keying algorithm)
; Concept is laid out in US Patent 4311964

; Examine bit stream to find presence / direction of bit transitions
; byteOld is previously examined byte, to compare its bit 0 to current bit 7
; Previous bit 7 (or byteOld bit 0) is XOR'd with current bit 7 to find transitions
; Sign bit comparison determines whether a transition occurred (0: no transition)
; If a transition was found, Bit 7 is then used to determine direction
; If 1, a positive transition (0->1) occurred, else it was negative (1->0)
; Current bit position is then used to look up value and negated if needed
; Cumulative values are saved to valueIi (0 degree) and valueQi (90 degree)

msgDec  CLRA
        CLRB
        STD   valueIi ; write to consecutive bytes - valueQi in AccB
        LDAA  byteOld
        LDX   #sineTb ; we are checking for presence and direction of bit transitions
dmd_01  TAB
        EORB  byteLst
        BPL   dmd_04
        LDAB  $00,X ; read 0-degree value
        TSTA
        BPL   dmd_02
        NEGB
dmd_02  ADDB  valueIi
        STAB  valueIi
        LDAB  $02,X ; read 90-degree value
        TSTA
        BPL   dmd_03
        NEGB
dmd_03  ADDB  valueQi
        STAB  valueQi
dmd_04  LDAA  byteLst
        ROL   byteLst
        DEX
        CPX   #sineTb-8
        BNE   dmd_01
        STAA  byteOld ; save byteLst for bit 7 / bit 0 comparison

; and now for some math
; We cycle through a group of 4 tables, each 8 bytes long:
; byte 0 - current 0-degree value
; byte 1 - current 90-degree value
; byte 2 - current 'vector sum'
; byte 3 (2 bytes) - rolling I average sum
; byte 5 (2 bytes) - rolling Q average sum
; byte 7 - current 'M value'

; set next table group to use

        LDX   #g1valI
        LDAB  ctPhse ; value will be 0, 8, 16, or 24
        ABX

; transfer instantaneous I and Q values into group tables

        LDD   valueIi ; dual load - valueQi in AccB
        STD   $00,X ; dual write - current group valueIi, valueQi

; We take rolling average, reduce to 15/16 of current value, then add new value
; We then call one subroutine to calculate current vector sum

        LDAA  g1valI ; table 1 value
        LDAB  g2valI ; table 2 value
        ADDA  g3valI ; table 3 value
        ADDB  g4valI ; table 4 value
        ASRA
        ASRB
        ABA
        STAA  valueIi
        LDAB  $03,X ; current group I average
        JSR   phsClc
        STAA  $02,X ; current group vector sum
        LDAA  g1valQ ; table 1 value
        LDAB  g2valQ ; table 2 value
        ADDA  g3valQ ; table 3 value
        ADDB  g4valQ ; table 4 value
        ASRA
        ASRB
        ABA
        STAA  valueQi
        LDAB  $05,X ; current group Q average
        JSR   phsClc
        ADDA  $02,X
        STAA  $02,X ; current group vector sum

; use sign bit left over from calculations to determine I / Q polarity
; (and change if necessary)

        ASLA ; bump sign bit into carry
        LDD   valueIi ; read of consecutive bytes - valueQi in AccB
        BCC   dmd_05
        NEGA
        NEGB
dmd_05  STAB  valueQi

; now we calculate the M value (valueIi is brought in via AccA)
; Each ASRA / RORB instruction pair divides 16-bit value by 2 (so, 4)

        CLRB
        SUBD  $03,X ; current group I average
        ASRA
        RORB
        ASRA
        RORB
        ADDD  $03,X
        STD   $03,X ; current group I average
        BPL   dmd_06
        NEGA
dmd_06  STAA  $07,X ; current group M value

        LDAA  valueQi
        CLRB
        SUBD  $05,X ; current group Q average
        ASRA
        RORB
        ASRA
        RORB
        ADDD  $05,X
        STD   $05,X ; current group Q average
        BPL   dmd_07
        NEGA
dmd_07  LDAB  $07,X ; current group M value

; Continue with remaining calculations to determine value of received bit
; Compare is done to determine if we need to halve AccA or AccB

        CBA
        BPL   dmd_08
        ASRA
        BRA   dmd_09

; demodulation table, where 244 = -12, 239 = -17

        FCB   17,12,0,244,239,244,0
sineTb  FCB   12,17,12

; Continue with calculations

dmd_08  ASRB
dmd_09  ABA
        STAA  $07,X ; current group M value
        LDX   nxtVec
        LDAA  $07,X ; current group M value
        CMPA  #$20
        BGE   dmd_13
        LDX   #g1valI
        LDAA  $07,X ; current group M value
dmd_10  STX   mValTmp
dmd_11  LDAB  #$08
        ABX
        CPX   #nxtVec
        BGE   dmd_12
        LDAB  $07,X ; current group M value
        CBA
        BPL   dmd_11
        TBA
        BRA   dmd_10

dmd_12  LDX   mValTmp
        STX   nxtVec
        BRA   dmd_14 ; code jump

dmd_13  LDAA  $02,X ; current group vector sum
        STAA  mTblLst
        RTI

dmd_14  LDAA  #$03
        STAA  bitIntv
        CLRA
        STAA  msgStrt
        STAA  mTblLst
        RTI

; Subroutine to set initial message handling parameters

msgInt  CLR   rcvByt
        CLR   msgRej
        LDAA  #$04
        STAA  bitGrp
        LDX   #msg_01
        STX   relJump
        LDAA  #$07
        ANDA  PORT4
        STAA  PORT4 ; clear bits 3-7 (not used anyway)
        RTS

; Continue with calculations

dmd_16  LDD   mTblLst ; dual load / bitIntv in AccB
        DECB
        BEQ   dmd_20
        BPL   dmd_19
        LDAB  #$FF
        STAB  msgStrt
        LDAB  mTblLst
        TST   MValue
        BMI   dmd_17
        COMB

; manipulate P2.2, P2.3, but nothing connects to these in standard LMT-1xx

dmd_17  PSHA
        LDAA  PORT2
        ORAA  #$0C
        TSTB
        BMI   dmd_18
        ANDA  #$F7
dmd_18  STAA  PORT2
        PULA
        BRA   dmd_22

; Continue with calculations

dmd_19  DECB
        BNE   dmd_21
        TAB
        EORB  mTblOld
        BMI   dmd_21
        BRA   dmd_22

dmd_20  TAB
        EORB  mTblOld
        BMI   dmd_22
        STAA  MValue
dmd_21  DEC   bitIntv
dmd_22  STAA  mTblOld
        LDAB  msgStrt
        BMI   dmd_24
        STAB  msgEnd
dmd_23  JMP   msg_17

dmd_24  LDAB  msgEnd
        BNE   dmd_23
        LDAB  MValue
        BMI   dmd_25
        COMA
dmd_25  ROLA

; After all that, we have one bit. Roll it into the message table

        LDX   #msgTbl
        ROL   $04,X ; this is rcvByt
        ROL   $03,X
        ROL   $02,X
        ROL   $01,X
        ROL   $00,X
        LDAA  rcvByt ; fetch the byte of importance
        JSR   bchChk ; perform BCH checksum calculation
        DEC   bitGrp
        BNE   dmd_26 ; not done - return to start of routine (actual jump is down below)
        LDX   relJump
        JMP   $00,X ; ready to examine the next group of bits

; At this point, the first 12 bits of the incoming message have been processed:
; An initial 'heads-up' bit followed by an 11-bit preamble - to wit: 101010101011 (0xAAB)
; What then follows is the actual Emetcon message (A-type), analyzed section by section,
; presented for analysis via AccA

; First segment - unit type (4 bits) / LMT-1xx string: 1000

msg_01  SUBA  #$08
        BMI   msg_07 ; value was 0XXX - reject message (possibly LMT-I units)
        CMPA  #$04
        BGE   msg_07 ; value was 1XXX - reject message (two-way units - i.e., LMT-2 etc)
        ASRA ; should be LSRA
        STAA  unitID ; value was 1000 - target value, stored as 0
        LDX   #msg_02
        LDAA  #$08
        BRA   msg_05 ; code reuse

; Second segment - eight '1' bits

msg_02  LDAB  unitID
        BEQ   msg_03

; Unit type was NOT 1000 - reject and discard remainder of message

        INC   msgRej
        LDAA  #$87
        ANDA  PORT4
        ORAA  #$20 ; set P4.5 high (nothing there)
        STAA  PORT4
        LDX   #msg_11
        LDAA  #$16
        BRA   msg_05 ; code reuse

; Unit type is is good, set up for next segment

msg_03  LDAA  #$87
        ANDA  PORT4
        ORAA  #$10 ; set P4.4 high (nothing there)
        STAA  PORT4
        LDX   #msg_08
msg_04  LDAA  #$06
msg_05  STAA  bitGrp
        STX   relJump
        CLR   rcvByt
dmd_26  JMP   msgDec ; line doubles as the return point after incoming bit is received

; Message was rejected, reset and wait for start of the next message

msg_07  BRA   msg_16 ; code reuse

; Third segment - address (6 bits)
; Incoming message values: 00-3B = Silver addresses 1 - 60, 3C-3F = Gold addresses 1 - 4
; Address comb bits 0 - 5 = Silver address, 6, 7 = Gold address

msg_08  LDAB  #$02
        STAB  adrLvl
        LDAB  PORT3 ; check against comb to see if incoming address is Silver 1-60
        ANDB  #$3F
        CBA
        BEQ   msg_10
        LDAB  PORT3 ; check against comb to see if incoming address is Gold 1-4
        ROLB
        ROLB
        ROLB
        ORAB  #$3C
        ANDB  #$3F
        CBA
        BEQ   msg_09
        INC   msgRej
        BRA   msg_10 ; code reuse

; Gold address on comb matches received address

msg_09  DEC   adrLvl

; Silver address on comb matches received address

msg_10  LDX   #msg_14
        BRA   msg_04 ; code reuse

; Count off remaining bits after current message was rejected

msg_11  LDX   #msg_12
        LDAA  #$0A
        BRA   msg_05 ; code reuse

        BRA   msg_16 ; vestige of developer code?

msg_12  LDX   #msg_13
        LDAA  #$02
        BRA   msg_05 ; code reuse

msg_13  LDX   #msg_15
        BRA   msg_04 ; code reuse

; Fourth segment - interval length (2 bits) and command (4 bits)

msg_14  TAB
        ANDA  #$30
        STAA  ldIntvl ; load shed interval (7.5, 15, 30, or 60 min)
        ASRB ; should be LSRB
        ANDB  #$07 ; now translate the command to table lookup
        LDX   #rlyTbl
        ABX
        LDAB  $00,X
        STAB  actByte
        BRA   msg_13 ; code reuse

; Relay command table - this table is also present in S00036 and S00175
; 0 = restore all / 1, 2, 4, 8 = shed Relay A - D respectively
; 65 (0x41) = open latched rly / 66 (0x42) = close latched rly / 15 (0x0F) = shed all

rlyTbl  FCB   0,1,2,4,8,65,66,15

; Fifth and final segment - BCH checksum validation (6 bits)
; If checksum is good, test LED lights to indicate a valid message was recieved

msg_15  TST   bchSum
        BNE   msg_16
        LDAA  #$FF
        STAA  ledFlg ; Set LED flag 'on'
        TST   msgRej
        BNE   msg_17
        BRA   msg_18

; Reset parameters after processing or rejecting message

msg_16  LDAA  #$01
        STAA  msgEnd
msg_17  CLRA
        STAA  adrLvl
        STAA  bchSum
        JSR   msgInt
        JMP   msgDec

; Unit type, address, and command are all valid - now, action.

msg_18  LDAB  actByte
        BEQ   msg_19
        TBA
        LSRA
        LSRA
        LSRA
        LSRA
        BEQ   msg_20
        SUBA  #$04
        BEQ   msg_22
        BRA   msg_16 ; code reuse

; restore loads / AccB is 0, value gets carried into load interval routine call
; the effect is to shed all, then set the remaining time to 0 to cause restore
; Test LED goes out to indicate unit has responded and is ready for next message

msg_19  LDAA  #$0F
        STAA  actByte
        CLRB
        STAB  ledFlg ; Set LED flag 'off'
        BRA   msg_21 ; code reuse

; shed indicated load, AccB is 30, carried into load interval routine call

msg_20  LDAB  #$1E
msg_21  JSR   ld_tim
        BRA   msg_16 ; code reuse

; latched relay command

msg_22  LDAA  #$06
        CMPB  #$41
        BEQ   msg_23
        CMPB  #$42
        BNE   msg_16
        DECA
msg_23  STAA  latCmd
        LDAA  #$FF
        STAA  armTmr
        BRA   msg_16 ; code reuse

; Set timer for load(s) based on requested interval length

ld_tim  PSHX
        LDAA  ldIntvl
        BEQ   msg_26
msg_25  ASLB
        SUBA  #$10
        BNE   msg_25
msg_26  STAB  ldIntvl
        LDAB  adrLvl
        STAB  adrCpy
        LDAB  actByte
        TST   rlyMsk
        BEQ   msg_27
        ANDB  #$03
msg_27  LDX   #ldTimA
msg_28  LSRB
        BCC   msg_30
        LDAA  ldIntvl
        BEQ   msg_29
        LDAA  COUNTR
        LDAA  COUNTR+1
        ANDA  #$0F
        ADDA  ldIntvl
msg_29  STAA  $00,X
msg_30  INX
        TSTB
        BNE   msg_28
        PULX
        RTS

; Phase calculation subroutine (identical code in S00036, S00060, S00175)

phsClc  STAA  phsTmp
        BPL   phs_01
        NEGA
phs_01  TSTB
        BPL   phs_02
        NEGB
        NEG   phsTmp
phs_02  MUL
        ASLD
        ASLD
        TST   phsTmp
        BPL   phs_03
        NEGA
phs_03  RTS

; BCH checksum calculation subroutine / if 0 is returned, data is good
; Similar to code in S00036, but S00060 has improved version that doesn't use carry bit

bchChk  LDX   #bchSum
        LDAB  $00,X
        ASLB
        PSHA
        LSRA
        TBA
        EORA  #$43
        BITA  #$40
        BNE   bch_01
        BCC   bch_02
        CLC
bch_01  BCS   bch_02
        TBA
bch_02  ANDA  #$3F
        STAA  $00,X
        PULA
        RTS

; Start of main application code

vec_rst LDS   #stack ; place stack at end of RAM
        LDAA  P2DDR ; fetch state of PC0-PC2
        ANDA  #$E0
        CMPA  #$E0 ; and ensure it is set up correctly

; Configuration incorrect - wait for deadman reset

hold1   BNE   hold1

; WHAT IS THIS DOING HERE???
; This code is used in S00036 as a hook for S00033 (when used in a DCT) Why???

        LDAA  $E000
        CMPA  #$55
        BNE   app_02
        JMP   $E001

; Anyway... continue with app setup

app_02  CLI
        LDD   #$40BC
        STAA  RAMCR ; $40 enables internal RAM
        STAB  P1DDR
        LDD   #$0D79
        STAA  P2DDR
        STAB  P4DDR
        LDD   #$0001
        STAA  SCIMOD ; zero out otherwise unused registers
        STAA  SCICOM
        STAA  P3DDR
        STAA  P3CSR
        STAB  TMCSR
        CLR   PORT4 ; turn test LED off
        LDAA  PORT3 ; check for presence of address comb
        INCA
        BNE   app_05

app_03  LDAB  #$3C
        LDAA  PORT1 ; Get P1.6 status (rlyMsk)
        ANDA  #$40
        BEQ   app_04
        ANDB  #$0C
app_04  LDAA  PORT1
        ANDA  #$80
        ABA
        STAA  PORT1 ; send Deadman pulse, set relay statuses
        BRA   app_07

; Is cold load pickup enabled? (it is by default, so P1.0 should be 1)
; This option causes all loads to be shed for 7.5 minutes after power-up or reset

app_05  LDAA  PORT1
        ANDA  #$01
        BEQ   app_06
        BRA   app_03

; Pull-up resistor is not present, so we write 0 to relay bits to restore loads instead
; bit 5 = Load D ... bit 2 = Load A

app_06  LDAA  #$C3
        ANDA  PORT1
        STAA  PORT1

; Now perform memory test

app_07  JMP   ramchk

; Return from successful memory test

app_08  LDAA  PORT1 ; Get P1.6 status, save in rlyMsk
        ANDA  #$40
        STAA  rlyMsk
        LDAA  PORT3 ; check for presence of address comb
        INCA
        BNE   app_09
        LDD   #$0202 ; starting values for relay timers
        STD   ldTimA ; save in Load A / B
        STD   ldTimC ; save in Load C / D
        TST   rlyMsk ; do we ignore Relays C / D?
        BEQ   app_10
        CLR   ldTimC ; forget Load C
        CLR   ldTimD ; forget Load D
        BRA   app_10

; Checking again for cold load pickup option

app_09  LDAA  PORT1
        ANDA  #$01
        BEQ   app_10

; Cold load pickup also includes a 4-minute random interval to prevent post-outage surge
; This interval length is 'calculated' by grabbing 4 random bits from serial input pin
; Resultant value (in AccB), divided by 4 = time between 15 seconds and 3.5 minutes
; 7.5 minute interval length * 4 = 30
; Similar code is present in S00036

        CLRB
        LDAA  PORT1
        RORA
        RORA
        ROLB
        LDAA  PORT1
        RORA
        RORA
        ROLB
        LDAA  PORT1
        RORA
        RORA
        ROLB
        LDAA  PORT1
        RORA
        RORA
        ROLB
        ADDB  #$1E

; Now write this into Load A / B timers (if flag is set accordingly, Loads C / D too)

        LDX   #ldTimA
        STAB  $00,X ; Load A
        STAB  $01,X ; Load B
        TST   rlyMsk
        BNE   app_10
        STAB  $02,X ; Load C
        STAB  $03,X ; Load D

; Read primary (and secondary) frequency resistors to determine place in lookup table
; Secondary resistors (on Port 4 pins) were for never-implemented frequencies
; Therefore, resistors attached to Port 2 pin are only ones that ever change

app_10  LDAA  PORT2 ; primary frequency resistors
        ANDA  #$10
        LDAB  PORT4 ; aux frequency resistors
        ANDB  #$06
        LSRA
        ABA
        ASLA
        LDX   #freqTbl
        TAB
        ABX
        LDD   $00,X ; fetch and save values from the table
        STD   SMPCLK
        LDD   $02,X
        STD   INTV_M
        STD   INTV_W

        LDAA  #$FE
        ANDA  PORT2
        STAA  PORT2 ; clear P2.0
        INC   P20ctr
        JSR   msgInt
        LDAA  #$05
        STAA  bitIntv
        LDD   #g1avgI ; point to first I average value
        STD   nxtVec
        LDD   COUNTR
        ADDD  SMPCLK
        STD   OUTCMP
        LDAA  TMCSR
        ORAA  #$08
        STAA  TMCSR
        BRA   app_11

; frequency table - resistor table is based on 2D26148Gxx designators
; Designators on 7312C18Gxx are R20/R16, R19/R15, R18/R14

freqTbl FDB   425,4412 ; R3, R5, R16
        FDB   400,4687 ; R2, R5, R16
        FDB   255,7353 ; R3, R4, R16
        FDB   410,4573 ; R2, R4, R16 = 12.5kHz
        FDB   462,4058 ; R3, R5, R17
        FDB   396,4735 ; R2, R5, R17
        FDB   297,6313 ; R3, R4, R17
        FDB   429,4371 ; R2, R4, R17 = 9.615kHz

; Continue with main code

app_11  LDX   #ldTimD
        TST   rlyMsk
        BEQ   app_12
        DEX ; skip Loads C / D
        DEX
app_12  CLRA
        LDAB  flgRmt
        BEQ   app_13
        CMPB  adrCpy
        BNE   app_15
app_13  TST   $00,X ; store indicated relay timer
        BEQ   app_14
        SEC
app_14  ROLA
        DEX
        CPX   #ldTimA-1 ; are we done?
        BNE   app_13
app_15  ASLA
        ASLA

; Latched relay code - a different set of main and relay boards were likely required
; Relay C / D bits are used to activate the trip / close coils on the latched relay

        TST   rlyMsk
        BEQ   app_20
        TST   armTmr
        BEQ   app_18
        LDAB  #$06
        CMPB  latCmd
        BNE   app_17
        LDAB  #$20
app_16  ABA
        STAA  rlyBit
        CLR   latCmd
        BRA   app_21

app_17  DECB
        CMPB  latCmd
        BNE   app_19
        LDAB  #$10
        BRA   app_16

app_18  ANDA  #$CF
        BRA   app_20

app_19  LDAB  rlyBit
        ANDB  #$30
        ABA
app_20  STAA  rlyBit

; Manipulate deadman bit, then update Test LED status
; Test LED lights after seeing a message, goes out after 64 minutes or a restore cmd

app_21  LDAB  PORT1
        ANDB  #$80
        ADDB  rlyBit
        STAB  PORT1 ; deadman ping + relay bits
        LDAA  PORT4
        ORAA  #$01 ; turn test LED on
        TST   ledFlg ; but first, check to see if it should be on!
        BNE   app_22
        ANDA  #$FE ; turn test LED off
app_22  STAA  PORT4 ; set Test LED status

        CLR   flgRmt
        LDAA  #$7F
        ANDA  PORT1
        STAA  PORT1 ; send Deadman pulse
        JMP   app_11

; Memory tests (on-chip RAM / ROM)

; RAM test - first push $FF into address $FF, continue accordingly to $80
; Then we read back all addresses and ensure value in RAM matches counter value

ramchk  LDAB  PORT1
        LDS   #stack
        LDAA  #$FF
chk_01  EORB  #$80
        STAB  PORT1 ; deadman ping
        PSHA
        DECA
        CMPA  #$7F
        BNE   chk_01
        INCA
        TSX
        LDS   #stack
chk_02  EORB  #$80
        STAB  PORT1 ; deadman ping
        CMPA  $00,X
        BEQ   chk_03
        JMP   chk_fl ; failed
chk_03  INX
        INCA
        BNE   chk_02

; Now we repeat the process as before, but inverting the value before writing to RAM

        LDS   #stack
        TBA
        LDAB  #$FF
chk_04  EORA  #$80
        STAA  PORT1 ; deadman ping
        COMB
        PSHB
        COMB
        DECB
        CMPB  #$7F
        BNE   chk_04
        INCB
        TSX
        LDS   #stack
chk_05  EORA  #$80
        STAA  PORT1 ; deadman ping
        COMB
        CMPB  $00,X
        BNE   chk_fl ; failed
        COMB
        INX
        INCB
        BNE   chk_05

; RAM test complete. Clear $D3 to $80 - these locations are needed for variables

        LDS   #endVar ; Stack is reset to $D3
        CLRB
chk_06  EORA  #$80
        STAA  PORT1 ; deadman ping
        PSHB
        TSX
        CPX   #byteNow ; start of RAM
        BNE   chk_06

; Now we take the checksum of ROM from $F800 to $FE29

        LDS   #stack
        LDX   #romFW ; start of ROM
        CLR   byteNow
        CLRB
chk_07  LDAA  PORT1
        EORA  #$80
        STAA  PORT1 ; deadman ping
        LDAA  byteNow
        ADDD  $00,X
        STAA  byteNow
        INX
        INX
        CPX   #chkSum ; end of code before padding
        BNE   chk_07

; And we add in the addresses from the vector table

        LDX   #vectors ; last 16 bytes of memory
chk_08  ADDD  $00,X
        INX
        INX
        BNE   chk_08

; Take the grand total and compare to checksum in ROM

        SUBD  chkSum
        BNE   chk_fl ; failed

; post-test cleanup (address $81 should be cleared as well, but it's not as critical)

        CLR   byteNow
        LDS   #stack

; Check for special addressing comb (programmed for otherwise invalid Silver 62 address)

        LDAA  PORT3
        ANDA  #$3F
        CMPA  #$3E
        BEQ   chk_09 ; return to main loop without flashing Test LED

; Memory check passed, blink Test LED 3 times before returning to main application code

        LDAA  #$02 ; slow flash
        JSR   ledFlsh
chk_09  JMP   app_08

; Memory test failed, flash the Test LED 3 times and re-run the test
; This results in Test LED flashing continuously to indicate internal memory failure

chk_fl  LDS   #stack ; reset stack pointer
        LDAA  #$01 ; rapid flash
        JSR   ledFlsh
        JMP   ramchk

; Routine to blink Test LED 3 times
; AccA value from initial call is carried through to delay sub-subroutine

ledFlsh LDX   #$0003
        LDAB  PORT4
app_35  ORAB  #$01 ; turn test LED on
        STAB  PORT4
        JSR   dlyLED
        ANDB  #$FE ; turn test LED off
        STAB  PORT4
        JSR   dlyLED
        DEX
        BNE   app_35
        RTS

; Delay routine for LED flasher
; AccA value passed down from initial call sets delay length / 1 = 250mS, 2 = 500mS

dlyLED  PSHA
        PSHB
        PSHX
        LDAB  PORT1
app_37  LDX   #$5161
app_38  EORB  #$80
        STAB  PORT1 ; deadman ping
        DEX
        BNE   app_38
        DECA
        BNE   app_37
        PULX
        PULB
        PULA
        RTS

; end of main code

; NMI code - this is probably a factory test routine
; P4.7 is normally high and code also starts with nmiCt at 0

vec_nmi TST   nmiCt
        BNE   nmi_01

; Now we check P4.7 - if it is pulled up, code falls through to exit via RTI

        LDAA  PORT4
        BITA  #$80
        BEQ   nmi_02
        RTI

; P4.7 is low, so we check nmiCt and redirect as necessary

nmi_01  LDAB  nmiCt
        CMPB  #$01
        BEQ   nmi_04
        CMPB  #$02
        BEQ   nmi_06
        RTI

; First pass through after P4.7 check - bump nmiCt to 1 and continue

nmi_02  INC   nmiCt

; set all ports as output (except P1.1), then write a 1 to those pins

        LDAA  #$FD
        STAA  P1DDR
        LDAA  #$FF
        STAA  P3DDR
        STAA  P4DDR
        LDAA  #$1F
        STAA  P2DDR
        LDAA  #$FF
        STAA  PORT1
        STAA  PORT2
        STAA  PORT3
        STAA  PORT4

; Now wait for deadman reset

hold2   BRA   hold2

; Second pass through after P4.7 check - bump nmiCt to 2 and continue

nmi_04  INC   nmiCt

; write a 0 to all pins configured as output

        LDAA  #$00
        STAA  PORT1
        STAA  PORT2
        STAA  PORT3
        STAA  PORT4

; Now wait for deadman reset

hold3   BRA   hold3

; Final pass through after P4.7 check - restore port settings

nmi_06  INC   nmiCt

; Now we reset ports to normal configuration

        LDD   #$BC0D
        STAA  P1DDR ; default state: 10111100
        STAB  P2DDR ; default state: 00001101
        LDD   #$0079
        STAA  P3DDR ; default state: 00000000
        STAB  P4DDR ; default state: 01111001

; Check for unclipped or missing address comb

nmi_07  LDAA  PORT3
        BEQ   nmi_08
        CMPA  #$FF
        BEQ   nmi_09
        BRA   nmi_10 ; looks good, jump ahead

; Address comb was inserted with no clipped teeth

nmi_08  LDAA  PORT1 ; check P1.1 and P1.6
        BITA  #$42
        BNE   nmi_10
        LDAA  PORT2
        BITA  #$10 ; read primary frequency resistors
        BNE   nmi_10
        LDAA  PORT4
        BITA  #$06 ; read aux frequency resistors
        BNE   nmi_10
        LDAA  #$04
        STAA   PORT2 ; set P2.2
        BRA   nmi_07

; Address comb is missing!

nmi_09  LDAA  PORT1 ; check P1.1 and P1.6
        COMA
        BITA  #$42
        BNE   nmi_10
        LDAA  PORT2
        COMA
        BITA  #$10 ; read primary frequency resistors
        BNE   nmi_10
        LDAA  PORT4
        COMA
        BITA  #$06 ; read aux frequency resistors
        BNE   nmi_10
        LDAA  #$05
        STAA  PORT2 ; set P2.0, P2.2
        BRA   nmi_07

; We're done checking - initiate another NMI

nmi_10  CLR   PORT2 ; set all outputs on P2 to 0
        BRA   nmi_07

; checksum values

        FCB   255
chkSum  FDB   42261 ; ROM code + vector table (no filler)

; Next byte should land at address FE2A - confidence check when doing round-trip compile
; Also, space from FE2A to FFEF (454 bytes) is padded with 3F (SWI)

        SWI

; Vectors - addresses should match as a further confidence check

        ORG   $FFF0

vectors FDB   vec_nul ; F81F - idle SCI call
        FDB   vec_nul ; F81F - idle Timer Overflow
        FDB   vec_tmr ; F803 - Output Compare
        FDB   vec_nul ; F81F - idle Input Capture
        FDB   vec_nul ; F81F - idle IRQ
        FDB   vec_rst ; FB49 - SWI
        FDB   vec_nmi ; FD91 - NMI
        FDB   vec_rst ; FB49 - Reset
