;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 8051dumper.asm: Dump internal ROM contents of 8051 to serial port.
;;
;;

                PROCESSOR 8051
                INCLUDE "SFR.asm"

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GPIOs
;;
OUT_EA          ALIAS   "P1.0"      ; Inverted and connected to EAn pin
OUT_REDLEDn     ALIAS   "P1.1"      ; Red LED in RESET button
OUT_GRNLEDn     ALIAS   "P1.2"      ; Green LED in START button
IN_GRNBTNn      ALIAS   "P1.3"      ; Green START button
IN_R32Kn        ALIAS   "P1.4"      ; Dump size knob: 8058 (32k)
IN_R16Kn        ALIAS   "P1.5"      ; Dump size knob: 8054 (16k)
IN_R8Kn         ALIAS   "P1.6"      ; Dump size knob: 8052 (8k)
IN_R4Kn         ALIAS   "P1.7"      ; Dump size knob: 8051 (4k)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Internal RAM (uninitialized data segment)
;;
                SEGU    "IRAM"

; 0x00..0x1F contains the four register banks.
; 0x20..0x2F is the bit addressable area.
; 0x30..0x7F is the scratch pad area.

                ORG     0x0030
IROMSIZE:       DS      1           ; High byte of internal ROM size
CKSUM:          DS      1           ; Checksum accumulator for line
HINYBBLE:       DS      1           ; Temp buffer for HEXBYTE
LONYBBLE:       DS      1           ; Temp buffer for HEXBYTE

; Intel Hex line buffer for 16 byte record
BUF_START:      DS      1           ; Start code ":"
BUF_BYTECOUNT:  DS      2           ; Byte count "10"
BUF_ADDR:       DS      4           ; Start address of record
BUF_RECTYPE:    DS      1           ; Record type
BUF_DATA:       DS      32          ; Record data
BUF_CKSUM:      DS      2           ; Checksum
BUF_EOL:        DS      3           ; CRLF and terminating NUL



; Rest of RAM is stack. Initial stack pointer should be 1 byte before
; beginning of stack because PUSH operations pre-increment the stack pointer.
STACK:          DS      0x0080-$


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Code segment starting at base of external program memory EEPROM
;;
                SEG     "code"

                ; Reset vector (3 bytes)
                ORG     0x0000
RESET:          LJMP    MAIN

                ; External Request 0 Interrupt Service Routine (8 bytes)
                ORG     0x0003
ER0_ISR:        LJMP    ERROR

                ; Internal Timer/Counter 0 Interrupt Service Routine (8 bytes)
                ORG     0x000B
ITC0_ISR:       LJMP    ERROR

                ; External Request 1 Interrupt Service Routine (8 bytes)
                ORG     0x0013
ER1_ISR:        LJMP    ERROR

                ; Internal Timer/Counter 1 Interrupt Service Routine (8 bytes)
                ORG     0x001B
ITC1_ISR:       LJMP    ERROR

                ; Internal Serial Port Interrupt Service Routine (8 bytes)
                ORG     0x0023
ISP_ISR:        LJMP    ERROR

                ; 8052 TF2 and EXF2 Interrupt Service Routine (8 bytes)
                ORG     0x002B
TF2_ISR:        LJMP    ERROR



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Following code/data will be accessed in a mirror image of the 32k EEPROM
;; at 0x8000. We will raise the EAn pin with a GPIO in order to overlay
;; internal ROM at address 0x0000. We will not use any interrupts because
;; interrupt vectors will contain unpredictable code from internal ROM.
;;
                ORG     0x0032
                RORG    0x8032

IDSTR:          DB      "8051dumper v1.0 by NF6X", 0x0D, 0x0A, 0x00
KNOBSTR:        DB      "ERROR: Could not read ROM size knob!", 0x0D, 0x0A, 0x00
ENDSTR:         DB      ":00000001FF", 0x0D, 0x0A, 0x00

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Main entry point
;;
MAIN:
                MOV     IE, #0x00               ; Disable all interrupts
                MOV     P1, #0xFF               ; GPIOs all input/high
                MOV     SP, #STACK-1            ; Initialize stack pointer
                CLR     OUT_EA                  ; Select INTERNAL ROM at 0x0000


; Configure serial port for Mode 1, 9600 8n1
;   SMOD = 0 ==> K = 1
;   Oscillator frequency = 11.0592 MHz
;   TH1 = 256 - (K * Fosc)/(32 * 12 * Baud)
;   TH1 = 256 - (1 * 11059200)/(32 * 12 * 9600) = 253 = 0xFD
                MOV     PCON, #0x00             ; SMOD = 0
                MOV     SCON, #0x50             ; Mode 1
                MOV     TMOD, #0x21             ; T1: Mode 2, T0: Mode 1
                MOV     TH1,  #0xFD             ; 9600 baud
                MOV     TL1,  #0xFD             ; 9600 baud
                SETB    TCON_TR1                ; Enable timer


; Send ID string 
                MOV     DPTR, #IDSTR
                ACALL   SENDROM


; Blink green LED until green START button pressed.
; Flash LED 2x per second at 20% duty cycle, checking
; button every 100ms.
WAITSTART:      CLR     OUT_GRNLEDn             ; On for 100ms
                ACALL   DELAY100ms
                JNB     IN_GRNBTNn, READKNOB    ; Button pressed?
                SETB    OUT_GRNLEDn             ; Off for 400ms
                ACALL   DELAY100ms
                JNB     IN_GRNBTNn, READKNOB    ; Button pressed?
                ACALL   DELAY100ms
                JNB     IN_GRNBTNn, READKNOB    ; Button pressed?
                ACALL   DELAY100ms
                JNB     IN_GRNBTNn, READKNOB    ; Button pressed?
                ACALL   DELAY100ms
                JNB     IN_GRNBTNn, READKNOB    ; Button pressed?
                SJMP    WAITSTART

; Read dump size knob and begin dumping code.
READKNOB:       SETB    OUT_GRNLEDn             ; Green LED off
 
.TST32K:        JB      IN_R32Kn, .TST16K       ; Knob set to 32k?
                MOV     IROMSIZE, #0x80         ; Yes, 32k
                SJMP    INITBUF
.TST16K:        JB      IN_R16Kn, .TST8K        ; Knob set to 16k?
                MOV     IROMSIZE, #0x40         ; Yes, 16k
                SJMP    INITBUF
.TST8K:         JB      IN_R8Kn, .TST4K         ; Knob set to 8k?
                MOV     IROMSIZE, #0x20         ; Yes, 8k
                SJMP    INITBUF
.TST4K:         JB      IN_R4Kn, .TSTFAIL       ; Knob set to 4k?
                MOV     IROMSIZE, #0x10         ; Yes, 4k
                SJMP    INITBUF
.TSTFAIL:       MOV     DPTR, #KNOBSTR          ; Cannot read knob!
                ACALL   SENDROM
                LJMP    ERROR


; Initialize the line buffer and ROM data pointer
INITBUF:        MOV     BUF_START, #':'
                MOV     BUF_BYTECOUNT, #'1'
                MOV     BUF_BYTECOUNT+1, #'0'
                MOV     BUF_EOL, #0x0D
                MOV     BUF_EOL+1, #0x0A
                MOV     BUF_EOL+2, #0x00
                MOV     DPTR, #0000 


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Dump one Intel Hex record of 16 bytes at a time until done.
;; Toggle red LED for each line to show activity.
;;
DUMPLOOP:       MOV     A, DPH                  ; Has DPTR hit end of IROM?
                CJNE    A, IROMSIZE, DUMPRECORD
                SJMP    EOF                     ; Finished with IROM

; Dump one 16-byte record
DUMPRECORD:
                CPL     OUT_REDLEDn
                MOV     CKSUM, #0x10            ; Init checksum w/ record length
                MOV     R0, #BUF_ADDR           ; Point to address field
                MOV     A, DPH                  ; High address byte
                ACALL   HEXBYTE
                MOV     A, DPL                  ; Low address byte
                ACALL   HEXBYTE
                CLR     A                       ; Record type
                ACALL   HEXBYTE

DUMPBYTE:       CLR     A                       ; Read byte from IROM
                MOVC    A, @A+DPTR
                ACALL   HEXBYTE                 ; Add it to line buffer
                MOV     A, DPL                  ; End of record?
                ANL     A, #0x0F 
                CLR     C
                SUBB    A, #0x0F
                JZ      EOR                     ; Yes, at end of 16-byte record
                INC     DPTR                    ; No, increment IROM pointer
                SJMP    DUMPBYTE                ; Add next byte

EOR:            MOV     A, CKSUM                ; Add checksum to record
                CPL     A
                ADD     A, #1
                ACALL   HEXBYTE
                ACALL   SENDRECORD              ; Send out this record
                INC     DPTR

                SJMP    DUMPLOOP


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Send out end of file record, turn green LED on and red LED off,
;; and loop forever.
;;
EOF:            MOV     DPTR, #ENDSTR
                ACALL   SENDROM
                CLR     OUT_GRNLEDn             ; Green LED on
                SETB    OUT_REDLEDn             ; Red LED off
                SJMP    $

               
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Add hex representation of A to buffer at R0 and accumulate checksum.
;;
HEXBYTE:        MOV     HINYBBLE, A             ; Add byte to checksum
                MOV     LONYBBLE, A
                ADD     A, CKSUM
                MOV     CKSUM, A

                MOV     A, HINYBBLE             ; Isolate high nybble
                SWAP    A
                ANL     A, #0x0F
                ADD     A, #'0'
                MOV     HINYBBLE, A
                CLR     C
                SUBB    A, #':'
                JC      .LO
                MOV     A, HINYBBLE
                ADD     A, #7
                MOV     HINYBBLE, A

.LO:            MOV     A, LONYBBLE             ; Isolate low nybble
                ANL     A, #0x0F
                ADD     A, #'0'
                MOV     LONYBBLE, A
                CLR     C
                SUBB    A, #':'
                JC      .STHI
                MOV     A, LONYBBLE
                ADD     A, #7
                MOV     LONYBBLE, A

.STHI:          MOV     A, HINYBBLE             ; Store high nybble hex char
                MOV     @R0, A
                INC     R0
                MOV     A, LONYBBLE             ; Store low nybble hex char
                MOV     @R0, A
                INC     R0

                RET


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Send out the current record.
;;
SENDRECORD:     MOV     R0, #BUF_START          ; Init pointer
.SENDCHAR:      MOV     A, @R0                  ; Get next char
                JZ      .DONE                   ; Done if it is NUL
                CLR     SCON_TI                 ; Send byte
                MOV     SBUF, A
                JNB     SCON_TI, $              ; Loop until character sent
                INC     R0
                SJMP    .SENDCHAR
.DONE:          RET


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Error handler
;; Turn on red LED and loop forever.
;;
ERROR:          SETB    OUT_GRNLEDn             ; Green LED off
                CLR     OUT_REDLEDn             ; Red LED on
                SJMP    $




;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Delay 100ms
;; Uses Timer 0, delaying 50ms twice
;; Oscillator frequency = 11.0592 MHz
;;   ==> 50ms = 11059200 / (12 * 20) = 46080 = 0xB400 counts
;;   ==> (TH0,TL0) = 0x10000 - 0xB400 = 0x4C00
;; This all ignores subroutine call overhead and so forth.
;;
DELAY100ms:     CLR     TCON_TR0                ; Timer off
                MOV     TH0, #0x4C              ; 50ms delay
                MOV     TL0, #0x00
                CLR     TCON_TF0                ; Clear overflow
                SETB    TCON_TR0                ; Timer on
                JNB     TCON_TF0, $             ; Wait for overflow
                CLR     TCON_TR0                ; Timer off
                MOV     TH0, #0x4C              ; 50ms delay
                MOV     TL0, #0x00
                CLR     TCON_TF0                ; Clear overflow
                SETB    TCON_TR0                ; Timer on
                JNB     TCON_TF0, $             ; Wait for overflow
                CLR     TCON_TR0                ; Timer off
                RET


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Send NUL-terminated string in ROM pointed to by DPTR.
;; Trashes A, DPTR
;;
SENDROM:        CLR     A                       ; Get byte from ROM
                MOVC    A, @A+DPTR
                JZ      .DONE                   ; Done if it is NUL
                CLR     SCON_TI                 ; Send byte
                MOV     SBUF, A
                JNB     SCON_TI, $              ; Loop until character sent
                INC     DPTR                    ; Send next byte
                SJMP    SENDROM
.DONE:          RET

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
                END
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

