;  Final Talking Multimeter Assembly Language code for the SX28AC/DP Version 1.0
;  Using a METEX-3850D and a speakjet vocal synth
;  Code by John Whitfield with extensive help from
;  Peter Verkaik and Guenther Daubach   
   LIST Q = 37
   device  SX28,oschs2,turbo,stackx,optionx,BOR42 
   IRC_CAL IRC_SLOW
   DEVICE  WATCHDOG 
   FREQ    50_000_000
   RESET Main
RAInit            =                              %00000000
RATris            =                              %11111111
RALevel           =                              %00000000
RAPlp             =                              %00000000
ComPort           = rb
RxPin2            = ComPort.2 ; -----------------------+||
TxPin2            = ComPort.3 ; ----------------------+|||  Input to speakjet
RxPin1            = ComPort.6 ; -------------------+||||||  Multimeter
TxPin1            = ComPort.7 ; ------------------+|||||||  Multimeter
                              ;                   ||||||||
ComPortTris       =                              %01110111
ComPortInit       =                              %10001000
ComPortLevel      =                              %00000000
ComPortPlp        =                              %11001100
ComPortST         =                              %11111111
RCInit            =                              %00000000
RCTris            =                              %11111111
RCLevel           =                              %00000000
RCPlp             =                              %00000000
RCST              =                              %11111111
;                    +---------- RTCC at address 0
;                    |+--------- Disable (1) / enable (0) RTCC interrupt
;                    ||+-------- Don't increment (1) / increment (0) RTCC with clock
;                    |||+------- RTCC negative input edge select
;                    ||||+------ Assign prescaler to watchdog
;                    |||||+----- Prescaler 2
;                    ||||||+---- Prescaler 1
;                    |||||||+--- Prescaler 0
;                    ||||||||
INT_OFF           = %11111111
INT_ON            = %10011111
MODE_CMP          = $08
MODE_WKPND        = $09
MODE_WKED         = $0a
MODE_WKEN         = $0b
MODE_ST           = $0c
MODE_LVL          = $0d
MODE_PLP          = $0e
MODE_DIR          = $0f

IntPeriod         = 163

; UART1 1200 Baud for the DMM
;
; Note that the timing used here is for 2400 Baud,
; therefore, the UART1 code will be executed every
; second ISR call only, resulting in half the speed,
; i.e. 1200 Baud.
;
BaudBit1          = 7
StartDelay1       = 128+64+1 ; = 2^BaudBit * 1.5 + 1
RxBitCount1       = 9        ; = 7 data, 2 stop

; UART2 9600 Baud for the SpeakJet
;
BaudBit2          = 5
StartDelay2       = 32+16+1  ; = 2^BaudBit * 1.5 + 1

; Global variables
;
   org            8
Temp              ds 1   
Flags             ds 1
d1		  DS	1	;delay variables
d2		  DS	1	;
d3		  DS	1	;
d4		  DS    1
Temp2		  ds	1
RXAvail1          = Flags.0

; Variables for UARTs
;
   org            $10
Serial            = $
Prescaler1        ds 1
TxHigh1           ds 1
TxLow1            ds 1
TxCount1          ds 1
TxDivide1         ds 1
RxByte1           ds 1
RxCount1          ds 1
RxDivide1         ds 1
TxHigh2           ds 1
TxLow2            ds 1
TxCount2          ds 1
TxDivide2         ds 1
RxCount2  	  ds 1

; Variables for DMM return values
;
    org           $30
DMM               = $   
digit1            ds 1                 
digit2            ds 1                     
digit3            ds 1                      
digit4            ds 1                        
digit5            ds 1
digit6            ds 1
digit7            ds 1
digit8            ds 1
digit9            ds 1
type1             ds 1
type2             ds 1
science           ds 1
units             ds 1
cr		  ds 1
DigitIndex        ds 1
CurrDigit         ds 1

;define Strings 
Strings   equ $600 

    org           0

; The ISR code handles a transmitter/receiver (1) for the DMM at 1200 Baud, 
; 1 start, 7 data, and 2 stop bits
; and another transmitter (2) for the SpeakJet at 9600 Baud,
; 1 start, 8 data, and 1 stop bit.
; 
; As the SpeakJet does not seem to return serial data, the receiver has been
; removed for UART 2.
; 
ISR
    bank    Serial
:Transmit1
    inc   Prescaler1                            ; Execute UART1 on every 2nd
    sb    Prescaler1.0                          ; ISR call for 1200 Baud    
      jmp :Transmit2
    clrb  TxDivide1.BaudBit1                    ; clear xmit timing count flag
    inc   TxDivide1                             ; only execute the transmit routine
    stz                                         ; set zero flag for test
    snb   TxDivide1.BaudBit1                    ;  every 2^baud_bit interrupt
      test  TxCount1                            ; are we sending?
    jz    :Receive1                             ; if not, go to next VP
    stc                                         ; Yes, ready stop bit
    rr    TxHigh1                               ; and shift to next bit
    rr    TxLow1                                ;
    dec   TxCount1                              ; decrement bit counter
    movb  TxPin1, /TxLow1.6                      ; output next bit

:Receive1
    movb  c, RxPin1                             ; get current rx bit
    test  RxCount1                              ; currently receiving byte?
    jnz   :RxBit1                               ; if so, jump ahead
    mov   w, #RxBitCount1                       ; in case start, ready  bits (1 start, 8 data)
    sc                                          ; skip ahead if not start bit
      mov RxCount1, w                           ; it is, so renew bit count
    mov   RxDivide1, #StartDelay1               ; ready 1.5 bit periods
:RxBit1
    djnz  RxDivide1, :Transmit2                 ; middle of next bit?
    setb  RxDivide1.BaudBit1                    ; yes, ready 1 bit period
    dec   RxCount1                              ; last bit?
    sz                                          ; if not
      rr   RxByte1                              ;   then save bit
    snz                                         ; if so
      setb RXAvail1                             ;   then set the flag
:Transmit2
    clrb  TxDivide2.BaudBit2                    ; clear xmit timing count flag
    inc   TxDivide2                             ; only execute the transmit routine
    stz                                         ; set zero flag for test
    snb   TxDivide2.BaudBit2                    ; every 2^baud_bit interrupt
    test  TxCount2                              ; are we sending?
    jz    :ExitISR                              ; if not, we are done
    stc                                         ; Yes, ready stop bit
    rr    TxHigh2                               ; and shift to next bit
    rr    TxLow2                                ;
    dec   TxCount2                              ; decrement bit counter
    movb  TxPin2, /TxLow2.6                      ; output next bit
:ExitISR
    mov  w, #-IntPeriod
    retiw
; The main entry point. We perform a jump to another page here, in order to keep the first half
; of page $000 free for subroutine entry points.
;   
Main
    jmp     @_Main  
GetByte1
    jmp     @_GetByte1  
GetString1
    jmp     @_GetString1
SendString1
    jmp     @_SendString1
SendString2
    jmp     @_SendString2   
SendDigit
    jmp     @_SendDigit
delay_0s5
    jmp     @_delay_0s5
delay_5s5
    jmp	    @_delay_5s5
SendCodes2
    jmp     @_SendCodes2

    org $200
_Main
    mov   !option, #INT_OFF

; Clear the RAM
;
    clr     fsr
ClearRAM
    sb      fsr.4                                  ; Are we on low half of bank?
      setb  fsr.3                                  ; If so, don't touch regs 0-7
    clr     ind                                    ; Clear using indirect addressing
    ijnz    fsr, ClearRAM                          ; Repeat until done

    ; Setup the ports
    ;
    mov     ra, #RAInit
    mov     ComPort, #ComPortInit
    mov     rc, #RCInit
    mode    MODE_DIR
    mov     !ra, #RATris
    mov     !ComPort, #ComPortTris
    mov     !rc, #RCTris
    mode    MODE_LVL
    mov     !ra, #RALevel
    mov     !ComPort, #ComPortLevel
    mov     !rc, #RCLevel   
    mode    MODE_PLP
    mov     !ra, #RAPlp
    mov     !ComPort, #ComPortPlp
    mov     !rc, #RCPlp
    mode    MODE_ST
    mov     !ComPort, #ComPortST
    mov     !rc, #RCST
    mode    MODE_DIR
    mov     !option, #INT_ON
   
    jmp	    MainLoop
;------------------------------------------------------------------------------------    
 
MainLoop
    call    @delay_0s5
    clr     !wdt
    call    @GetString1        

    cje     type1, #'D', :dc
    cje     type1, #'A', :ac
    cje     type1, #'C', :cap
    cje     type1, #'T', :temp
    cje     type1, #'R', :resist

    jmp     MainLoop

:dc
    mov     w, #_dc
    call    @_SendCodes2
    jmp     :SendDigits
:ac
    mov     w, #_ac
    call    @SendCodes2
    jmp     :SendDigits
:cap
    mov     w, #_cap
    call    @_SendCodes2
    jmp     :SendDigits
:temp   
    mov     w, #_temp
    call    @_SendCodes2
    jmp     :SendDigits
:resist
    mov     w, #_resist
    call    @_SendCodes2

:SendDigits				;This section looks bad and it is
    call    @delay_0s5			;But it is necessary for a user friendly operation
    bank    DMM				;It could probably be improved
    cjne    digit2, #'-',:nonn		;it removes the unnecessary zeros
    mov	    w, #_negative
    call    @_SendCodes2
    jmp     :nonn
:nonn
    bank    DMM
    cje     digit4, #'.',:point1
    cje     digit5, #'.',:point2
    cje     digit6, #'.',:point3
    cje     digit7, #'.',:point4

    cje     digit3, #'0',:skipzero
    cjne    digit3, digit4,:normal
:normal
    bank    DMM
    mov     CurrDigit, digit3
    call    @_SendDigit    
:skipzero
    bank    DMM
    cje     digit4, #'0',:skipzero0
    cjne    digit4, digit5,:normal0
:normal0
    bank    DMM
    mov     CurrDigit, digit4
    call    @_SendDigit    
:skipzero0
    bank    DMM
    cje     digit5, #'0',:skipzero00
    cjne    digit5, digit6,:normal00
:normal00
    bank    DMM
    mov     CurrDigit, digit5
    call    @_SendDigit  
:skipzero00
    bank    DMM
    cje     digit6, #'0',:skipzero000
    cjne    digit7, digit6,:normal000
    jmp	    :doScience
:normal000
    bank    DMM
    mov     CurrDigit, digit6
    call    @_SendDigit  
:skipzero000
    bank    DMM
    mov     CurrDigit, digit7
    call    @_SendDigit 
    jmp	    :doScience

:point1
    bank    DMM
    mov     CurrDigit, digit3
    call    @_SendDigit 
    bank    DMM
    mov     CurrDigit, digit4
    call    @_SendDigit   
    bank    DMM
    mov     CurrDigit, digit5
    call    @_SendDigit  
    bank    DMM
    mov     CurrDigit, digit6
    call    @_SendDigit  
    bank    DMM
    mov     CurrDigit, digit7
    call    @_SendDigit 
    jmp	    :doScience

:point2
    cje     digit3, #'0',:skipzero2
    bank    DMM
    mov     CurrDigit, digit3
    call    @_SendDigit
:skipzero2
    bank    DMM
    mov     CurrDigit, digit4
    call    @_SendDigit   
    bank    DMM
    mov     CurrDigit, digit5
    call    @_SendDigit  
    bank    DMM
    mov     CurrDigit, digit6
    call    @_SendDigit  
    bank    DMM
    mov     CurrDigit, digit7
    call    @_SendDigit 
    jmp	    :doScience

:point3
    cje     digit3, #'0',:skipzero3
    bank    DMM
    mov     CurrDigit, digit3
    call    @_SendDigit
:skipzero3
    cje    digit5, digit4,:skipzero33
:normal33
    bank    DMM
    mov     CurrDigit, digit4
    call    @_SendDigit
:skipzero33
    bank    DMM
    mov     CurrDigit, digit5
    call    @_SendDigit
    bank    DMM
    mov     CurrDigit, digit6
    call    @_SendDigit
    bank    DMM
    mov     CurrDigit, digit7
    call    @_SendDigit    
    jmp     :doScience

:point4
    bank    DMM
    cje     digit3, #'0',:skipzero4
    mov     CurrDigit, digit3
    call    @_SendDigit
:skipzero4
    cje    digit3, digit4,:skipzero44
    bank    DMM
    mov     CurrDigit, digit4
    call    @_SendDigit
:skipzero44
    cje    digit3, digit4,:skipzero444
    bank    DMM
    mov     CurrDigit, digit5
    call    @_SendDigit
:skipzero444
    bank    DMM
    mov     CurrDigit, digit6
    call    @_SendDigit
    bank    DMM
    mov     CurrDigit, digit7
    call    @_SendDigit    
    jmp     :doScience

:doScience
    call   @delay_0s5
    bank    DMM

    cje     Science, #' ', :doUnits
    cje     Science, #'n', :nano
    cje     Science, #'u', :micro
    cje     Science, #'m', :milli
    cje     Science, #'M', :mega
    cje     Science, #'k', :kilo
 
:kilo
    mov     w, #_kilo
    call    @_SendCodes2
    jmp     :doUnits   
:nano
    mov     w, #_nano
    call    @_SendCodes2
    jmp     :doUnits
:micro
    mov     w, #_micro
    call    @_SendCodes2
    jmp     :doUnits
:milli
    mov     w, #_milli
    call    @_SendCodes2
    jmp     :doUnits
:mega
    mov     w, #_mega
    call    @_SendCodes2
    jmp     :doUnits

:doUnits
    bank    DMM
    cje     Units, #'V', :volts
    cje     Units, #'A', :amps  
    cje     Units, #'H', :hertz
    cje     Units, #'O', :ohms          
    cje     Units, #'F', :farad
    cje     Units, #'C', :celcius
    jmp     :Done                    ; Exit on illegal characters
    
:amps
    mov     w, #_amps
    call    @_SendCodes2
    jmp     :Done
:hertz
    mov     w, #_hertz
    call    @_SendCodes2
    jmp     :Done
:ohms
    mov     w, #_ohms
    call    @_SendCodes2
    jmp     :Done
:farad
    mov     w, #_farad
    call    @_SendCodes2
    jmp     :Done
:volts
    mov     w, #_volts
    call    @_SendCodes2
    jmp     :Done
:celcius   
    mov     w, #_degrees
    call    @_SendCodes2 
    mov     w, #_celcius
    call    @_SendCodes2
    jmp	    :Done
:Done
    call    @delay_5s5
    jmp     MainLoop

; Subroutines
;
    org $400

_SendCodes2			;this routine sends codes that stabilize the speakjet operation
	bank	serial
	mov	Temp2, w
	mov	w, #_speakjet
        call    @_SendString2
	mov	w, Temp2
        call    @_SendString2
	retp
; This routine sends the required strings for digits to the SpeakJet. It expects the ASCII code of
; the digit in CurrDigit.

_SendDigit

    bank    DMM
    cje     CurrDigit, #' ', :Space
    cje     CurrDigit, #'.', :Point
    cje     CurrDigit, #'-', :Negative
    
    ; Now, there only should be digits left
    ;
    cje	    CurrDigit, #'0', :Zero
    cje	    CurrDigit, #'1', :One
    cje	    CurrDigit, #'2', :Two
    cje	    CurrDigit, #'3', :Three
    cje	    CurrDigit, #'4', :Four  
    cje	    CurrDigit, #'5', :Five
    cje	    CurrDigit, #'6', :Six
    cje	    CurrDigit, #'7', :Seven
    cje	    CurrDigit, #'8', :Eight
    cje	    CurrDigit, #'9', :Nine

:Zero
    mov     w, #_zero
    call    @_SendCodes2
    jmp     :DigitDone
:One
    mov     w, #_one
    call    @_SendCodes2
    jmp     :DigitDone
:Two
    mov     w, #_two
    call    @_SendCodes2
    jmp     :DigitDone
:Three
    mov     w, #_three
    call    @_SendCodes2
    jmp     :DigitDone
:Four
    mov     w, #_four
    call    @_SendCodes2
    jmp     :DigitDone
:Five
    mov     w, #_five
    call    @_SendCodes2
    jmp     :DigitDone
:Six
    mov     w, #_six
    call    @_SendCodes2
    jmp     :DigitDone
:Seven
    mov     w, #_seven
    call    @_SendCodes2
    jmp     :DigitDone
:Eight
    mov     w, #_eight
    call    @_SendCodes2
    jmp     :DigitDone
:Nine
    mov     w, #_nine
    call    @_SendCodes2
    jmp     :DigitDone
:Point
    mov     w, #_point
    call    @_SendCodes2
    jmp     :DigitDone
:Negative
    mov     w, #_negative
    call    @_SendCodes2
    jmp     :DigitDone
:Space
:DigitDone
    retp           
    
; This routines sends a string to the DMM - it expects the index into the
; string table in w.
_SendString1
    mov     Temp, w             ; Save index into string table
    bank    Serial
:WaitTxDone1 
    test    TxCount1            ; Test if transmitter is busy
    sz
    jmp   :WaitTxDone1
    mov     w, #Strings >> 8    ; Copy the upper 4 bits of the
    mov     m, w                ; string table into the lower 4
                                ; bits of m
    mov     w, Temp             ; Restore the index
    iread                       ; Read one byte indirectly
    test    w                   ; Test for end of string
    snz
    jmp     :out1
    mov     TxHigh1, w          ; Put character into transmit register high
    clr     TxLow1              ; Clear transmit register low for start bit
    mov     w, #10 
    mov     TxCount1, w         ; 1 start, 8 data, 1 stop (this also starts the
                                ; transmitter)
    inc     Temp                ; Point to next charater in the string table
    jmp     :WaitTxDone1         ; Loop back

:out1 
    mov w,#$1F   ; reset the mode register
    mov m,w
    retp

; This routines sends a string to the SpeakJet - it expects the index into the
; string table in w.
_SendString2
    bank    Serial
    mov     Temp, w             ; Save index into string table

:WaitTxDone2
	test	TxCount2
	sz
	jmp	:WaitTxDone2
;------------------------------------------------------------------------
    mov     w, #Strings>>8    ; Copy the upper 4 bits of the
    mov	    m, w                ; string table into the lower 4
                                ; bits of m
    mov     w, Temp             ; Restore the index
    iread                       ; Read one byte indirectly
    test    w
    snz
    jmp     :out2
    mov     TxHigh2, w          ; Put character into transmit register high
    clr     TxLow2              ; Clear transmit register low for start bit
    mov	    w, #10 
    mov     TxCount2, w         ; 1 start, 8 data, 1 stop (this also starts the
                                ; transmitter)
    inc     Temp                ; Point to next charater in the string table

    jmp     :WaitTxDone2         ; Loop back

:out2
	mov	w,#$1F			; reset the mode register
	mov	m,w
	retp
; This routine waits until a byte has been received from the DMM, and then
; returns the received character in w.
_GetByte1
    bank    Serial
    sb      RXAvail1            ; Check if the DMM has sent a byte
    jmp     _GetByte1
    clrb    RXAvail1            ; Clear the flag for the next byte
    mov     w, RxByte1
    and     w, #%01111111       ; Clear the 2nd stop bit
    retp

; This routine reads a string from the DMM and places the result in the
; variables located in the DMM bank.
_GetString1
    bank    DMM
    clr     type1
    clr     type2
    clr     digit1
    clr     digit2
    clr     digit3
    clr     digit4
    clr     digit5
    clr     digit6
    clr     digit7
    clr     digit8
    clr     digit9
    clr     science
    clr     units
    clr     cr
    mov     w, #_dmmcom
    call    @SendString1
    call    @GetByte1
    bank    DMM
    mov     type1, w
    call    @GetByte1
    bank    DMM
    mov     type2, w
    call    @GetByte1
    bank    DMM
    mov     digit1, w
    call    @GetByte1
    bank    DMM
    mov     digit2, w
    call    @GetByte1
    bank    DMM
    mov     digit3, w
    call    @GetByte1
    bank    DMM
    mov     digit4, w
    call    @GetByte1
    bank    DMM
    mov     digit5, w
    call    @GetByte1
    bank    DMM
    mov     digit6, w
    call    @GetByte1
    bank    DMM
    mov     digit7, w
    call    @GetByte1
    bank    DMM
    mov     digit8, w
    call    @GetByte1
    bank    DMM
    mov     digit9, w
    call    @GetByte1
    bank    DMM
    mov     science, w
    call    @GetByte1
    bank    DMM
    mov     units, w
    call    @GetByte1
    bank    DMM
    mov     cr, w		;carriage return
    retp
    
_delay_0s5
Delay:			;delay function
			;24999987 cycles
	mov	w, #$70
	mov	d1, w
	mov	w, #$2A
	mov	d2, w
	mov	w, #$27
	mov	d3, w
Delay_0:
	decsz	d1
	jmp	$+2
	decsz	d2
	jmp	$+2
	decsz	d3
	jmp	Delay_0

			;7 cycles
	jmp	$+1
	jmp	$+1
	nop
			;6 cycles (including call)
	retp

_delay_5s5
	mov d4,#11
:delay
	call @delay_0s5
	djnz d4,:delay
	retp

    org    Strings

_dmmcom     dw 'D',0	
_speakjet   dw 20, 96, 21, 114, 22, 88, 23, 5,0		
_zero       dw 167, 7, 128, 7, 149, 164, 0
_one        dw 147, 14, 136, 8, 141, 0
_two        dw 8, 191, 162, 0
_three      dw 8, 190, 148, 8, 128, 0
_four       dw 186, 7, 137, 153, 0
_five       dw 186, 157, 166, 0
_six        dw 8, 187, 129, 14, 194, 7, 187, 0
_seven      dw 8, 187, 7, 131, 166, 131, 141, 0
_eight      dw 154, 8, 191, 0
_nine       dw 141, 14, 157, 141, 0 
_point      dw 7, 199, 7, 156, 15, 141, 15, 191, 0
_dc         dw 174, 128, 128, 187, 128, 128,0
_ac         dw 154, 14, 187, 128, 128, 0
_volts      dw 166, 164, 145, 193,0
_amps       dw 132, 132, 140, 198, 8, 187, 0
_temp       dw 191, 7, 131, 140, 7, 198, 7, 148, 7, 134, 8, 191, 139, 148,0 
_milli      dw 140, 129, 145, 145, 128, 128, 0
_micro      dw 140, 140, 157, 196, 196, 7, 148, 136, 8, 137, 0
_kilo       dw 194, 7, 128, 14, 146, 137, 0
_mega       dw 140, 130, 14, 179, 8, 133,0
_nano       dw 14, 141, 7, 131, 141, 141, 164,0
_hertz      dw 183, 14, 148, 191, 193, 0
_negative   dw 141, 130, 178, 133, 191, 129, 166, 0
_henries    dw 184, 131, 131, 141, 141, 148, 148, 128, 15, 193, 0
_celcius    dw 187, 130, 145, 187, 7, 128, 7, 134, 187, 0
_farad      dw 186, 186, 132, 132, 14, 148, 132, 132, 15, 175, 187, 0
_ohms       dw 164, 140, 15, 193, 0
_cap        dw 7, 195, 132, 132, 199, 132, 187, 129, 191, 132, 141, 187, 0
_degrees    dw 174, 129, 129, 178, 148, 128, 128, 187,0
_resist     dw 148, 128, 128, 167, 129, 187, 191, 130, 141, 187, 0