A Few Bits of Code

TSBurner (8051 Programmer)

An 8051 programmer that uses a Serial connection to program the popular AT89S52/LP52 series of microcontrollers from Atmel.

Summary

The inspiration for this design came from an 89C51 Programmer by Wichit Sirichote. His design used a parallel programming scheme with a closed-source windows application controlling the device.

This project uses a serial programming scheme to program both the older AT89S52 series as well as the newer AT89LP51/52 series of microcontrollers. The Windows-based control software is also provided which utilizes code from my Serial interface.

Hardware

The hardware for this uses an 8051 to control programming of the target device. Serial programming doesn't need high voltage to enter 'Programming Mode', which greatly simplifies the number of components required. Power is supplied by the host PC via the USB port.

Note that the pull-up resistors are not required and were just added in case of future expansion. With space at a premium on my prototype, I included them underneath the socketed DIP package. If I ever expand the functionality, there will already by pull-ups in place.

TSBurner_Schematic.png
Download PDF (35.15 Kb)

Software

The 8051 that handles the actual programming is straight forward. It justs follows the timing a protocol specified in the AT89S52 datasheet.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; TSBurner                              ;
; Program the AT89S51/51 and AT89LP52   ;
;   series of microcontrollers          ;
;                                       ;
; Author: Tim Shaffer                   ;
;   Inspired by Easy-Downloader V2.0    ;
;   by Wichit Sirichote                 ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;Programmer -> ZIF Socket
;P1.5 -> P1.5 (MOSI)
;P1.6 -> P1.6 (MISO)
;P1.7 -> P1.7 (SCK)
;P1.4 -> RST  Control RST of target
;XTAL2 -> XTAL1 (System clock)

;P1.0 -> LED (PC_PROG enabled)
;P1.1 -> LED (PROG_MODE enabled)

;??????? Future Connections ?????????
;P1.3 -> POL    (NO, POL is same pin as EA connect to VCC)
;P0 -> P0		Parallel programming on AT89LP52

;PC -> Programmer
;TxD -> P3.0 (Rx)
;RxD -> P3.1 (Tx)

;Reserved SPI pins
MOSI		BIT		P1.5
MISO		BIT		P1.6
SCK			BIT		P1.7
RST			BIT		P1.4	;Control RESET of Target Chip

PC_LED		BIT		P1.0	;(Active Low)Set when connected to special PC software
PROG_LED	BIT		P1.1	;(Active Low)Set when "Programming Mode' is enabled

;Reserved RAM Map (starts after register bank 0)
;00h - 07h (R0 - R7) reserved for temporary storage
CHIP		DATA	09h		;Stores target's model number (2nd signature byte)
MFG			DATA	0Ah		;Stores target chip's Manufacturer ID (1Eh = Atmel)
CMD			DATA	0Bh		;Stores that last command sent by the user
CHKSUM_H	DATA	0Dh		;Stores a running total of the bytes being read/written
CHKSUM_L	DATA	0Ch		;[A variable's MSB is indicated by _H, the LSB by _L]
COUNT_H		DATA	0Fh		;Stores how many bytes the next read/write will act on
COUNT_L		DATA	0Eh		;TODO: Ending address of read/write operation
NONBLANK_H	DATA	11h		;Stores the number of non-blank bytes for the current chip
NONBLANK_L	DATA	10h

START_H		DATA	13h		;TODO: Starting address of read/write operation
START_L		DATA	12h

SPI_WR		DATA	14h		;Holds the byte to write to the SPI bus (MOSI)
SPI_RD		DATA	15h		;Holds the last byte read from the SPI bus (MISO)

BITMEM		DATA	20h		;A byte of bit addressable memory (0x20-0x2F)
PC_PROG		BIT   BITMEM.0	;No (text) user feedback
PRINT_ZERO	BIT   BITMEM.1	;Print remaining zeros in PUTNUM16
NEED_HELP   BIT   BITMEM.2	;Display the help message to the user (or NAK to software)
CONV_ERR	BIT   BITMEM.3	;ASCII to HEX conversion error
PROG_MODE	BIT   BITMEM.4	;Enter Programming mode once
PAGE_MODE	BIT   BITMEM.5	;TODO: Read/Write in 'Page Mode'


;Flow Control Bytes
XON		EQU		11h		;Start sending data
XOFF	EQU		13h		;Stop sending data


;Host PC Special control byte 
;Sending this byte will let the programmer know that the host PC
;is using our software which does not need user (text) feedback 
ENQ		EQU		05h		;software controlled (no human interaction)
ACK		EQU		06h		;Signal to our software that an operation is complete
NAK		EQU		15h		;Error, command not found


;Reset ISR
ORG 0000h
	LJMP INIT

INIT: 
	MOV SP, #BITMEM 	;Set Stack Pointer after the last (highest) reserved variable
	;MOV SP, #2Fh		;Set Stack Pointer after Bit Memory
	
	MOV COUNT_L, #00h	;'count' = 0
	MOV COUNT_H, #00h
	MOV BITMEM, #00h
	
	;Initialize the Timers and Serial Port UART	
	MOV TMOD, #21h	;Timer1 (8-bit auto-reload) for UART, Timer0 (16-bit Timer) for delay routines
	MOV TH1, #0FDh	;9600 baud
	MOV TL1, #0FDh
	SETB TR1		;Start Timer1
	MOV SCON, #52h	;8-bit UART set by Timer1, Receive Enabled, TI set
	
	SETB MISO	;Set as Input
	CLR MOSI	;Set as Output
	CLR SCK		;Set as Output
	CLR RST		;Set as Output (Lets Target Chip run from Flash)
	
	SETB PC_LED		;(Active Low)
	SETB PROG_LED	;(Active Low)
	
	;Display a welcome message
	;MOV DPTR, #STR_TITLE
	;LCALL PUTSTR		;send the title message out to UART
	;MOV DPTR, #STR_PROMPT
	;LCALL PUTSTR		;Prompt for user input
	
	
	;Wait for a command on the UART, then check it against every function.
MAIN: 
	LCALL GET_COMMAND	;Get the User's input and try all commands
	LCALL SYNC_PC		;ENQ
	LCALL ENTER_PROG	;'p'
	LCALL SET_COUNTER	;'s'
	LCALL ERASE			;'e'
	LCALL WRITE			;'w'
	LCALL READ			;'r'
	LCALL LOCK			;'l'
	LCALL READ_LOCK     ;'b'
	LCALL PRINT_CHKSUM	;'c'
	LCALL INFO			;'i'
	LCALL HELP			;catch any other keypress here
	SJMP MAIN			;continue MAIN loop forever


;----------------------------------------------------------------------;
;                                                                      ;
;                      Menu/UI/Helper Functions                        ;
;                                                                      ;
;----------------------------------------------------------------------;

;Wait for a command to be received on the UART
GET_COMMAND:	
	LCALL GETCH		;waits for a command to be received on the UART, then saves it
	MOV CMD, A		;save 'command'
	SETB NEED_HELP	;Assume we will display the help prompt unless we match another command
	RET
	

;Sync with a host PC using our software
SYNC_PC:
	MOV A, CMD
	XRL A, #ENQ		;Is it our software?
	JNZ EXIT_SYNC
	
	CLR NEED_HELP
	SETB PC_PROG	;No user (text) feedabck
	CLR PC_LED		;(Active Low)
	LCALL PUTOK
EXIT_SYNC:
	RET


;Enter Programming Mode
ENTER_PROG:
	MOV A, CMD
	XRL A, #'p'
	JNZ EXIT_PROG
	
	CLR NEED_HELP
	JB PROG_MODE, PROG_SUCC		;Skip if we are already in programming mode

	;Raise Target RST pin to allow ISP programming
	SETB RST
	LCALL DELAY1MS
	
	;Write special "Enter Programming Mode" bytes
	MOV SPI_WR, #0ACh
	LCALL SPI_WR_RD
	MOV SPI_WR, #53h
	LCALL SPI_WR_RD
	MOV SPI_WR, #00h
	LCALL SPI_WR_RD
	MOV SPI_WR, #00h
	LCALL SPI_WR_RD
	
	;Check that chip sent correct reply
	MOV A, SPI_RD
	CJNE A, #69h, PROG_FAIL
PROG_SUCC:
	SETB PROG_MODE
	CLR  PROG_LED	;(Active Low)
	LCALL PUTOK
	SJMP EXIT_PROG
PROG_FAIL:
	CLR RST
	SETB PROG_MODE	;(Active Low)
	LCALL PUTERR
EXIT_PROG:
	RET
	

;Sets the number of bytes to read/write ('count') for other routines in this program.
;Accepts upto 4 ASCII Hex digits from the UART as input. Receiveing '\n' will terminate further input. 
;If anything other than 0-9,a-f,A-F or '\n' is received, then 'count' is set to zero.
;R4/R3 are used to store the running total before saving it to COUNT_H/COUNT_L
;R2 holds the number of digits read from the user (stop after 4 have been read)
;R0 holds a copy of the last byte input by the user
SET_COUNTER: 
	MOV A, CMD		;get 'command' variable
	XRL A, #'s'
	JNZ EXIT_SET_COUNTER

	CLR NEED_HELP	;Matched a command, no help needed
	;The max value we can use is 65535. Therefore only store the last 5 characters received on the UART.
	;Wait for '\n' to indicate the number input is finished.
	MOV R4, #00h
	MOV R3, #00h	;Init values
	MOV R2, #00h
	
GETNUM_LOOP:
	LCALL GETCH		;Get 'possible' ascii hex value
	MOV R0, A		;Save a copy
	LCALL ASC_HEX	;Try to convert to hex value
	JB CONV_ERR, NOT_NUMBER
	LCALL SHIFT_NIBBLES		;Shift in the new nibble
	INC R2
	CJNE R2, #4, GETNUM_LOOP
	SJMP GETNUM_FINISHED
	
NOT_NUMBER:
	;Check if it was a '\n' (finished sending)
	MOV A, R0			;Get original byte back
	XRL A, #0Ah			;Was it '\n'?
	JZ GETNUM_FINISHED	;Jump to exit if we got a '\n'
	;ERROR: We received a bad character, set 'count' to zero
	MOV R4, #00h
	MOV R3, #00h
	
GETNUM_FINISHED:
	MOV B, R4			;Check which mode to use 
	MOV A, R3
	CLR PAGE_MODE		;Assume 'byte mode'
	LCALL COMP_MODE
	JZ STORE_COUNT
	SETB PAGE_MODE		;Set 'page mode'
STORE_COUNT:	
	MOV COUNT_H, R4		;store in 'count'
	MOV COUNT_L, R3
	LCALL PUTOK			;Finished
EXIT_SET_COUNTER:
	RET


;Erase the entire chip
ERASE: 
	MOV A, CMD		;get 'command'
	XRL A, #'e'
	JNZ EXIT_ERASE
	
	CLR NEED_HELP	;Matched a command, no help needed
	
	MOV SPI_WR, #0ACh
	LCALL SPI_WR_RD
	MOV SPI_WR, #80h
	LCALL SPI_WR_RD
	MOV SPI_WR, #00h
	LCALL SPI_WR_RD
	MOV SPI_WR, #00h
	LCALL SPI_WR_RD
	
	;Erase takes a maximum of 500ms to perform
	;TODO: During chip erase, a serial read from any address location will return 00H at the data output.
	MOV R0, #250
	LCALL DELAY
	MOV R0, #250
	LCALL DELAY

	
	LCALL PUTOK
EXIT_ERASE:
	RET
	

;Write data to the chip (Program the Flash memory)
;R4/R3 	maximum address to write
;R6/R5 	loop counter 'i' (holds current address)
;R7 	the current byte being written to flash
WRITE: 
	MOV A, CMD		;get 'command'
	XRL A, #'w'
	JNZ EXIT_WRITE
	
	CLR NEED_HELP	;Matched a command, no help needed
	;Begin Write routine	
	MOV CHKSUM_L, #00h		;Reset 'chksum'
	MOV CHKSUM_H, #00h
	MOV R5, #00h		;store 'i' = 0
	MOV R6, #00h

WRITE_LOOP: 
	MOV A, R5			;get 'i'
	MOV B, R6
	MOV R3, COUNT_L		;get 'count'
	MOV R4, COUNT_H
	LCALL COMP16_LT		;check 'for' loop conditions, if (B/A < R4/R3) then A=1
	JZ WRITE_FINISHED	;jump out of 'for' loop ('i' >= 'count')
	
	;Start of 'for' block
	LCALL GETCH			;Get upper nibble
	LCALL ASC_HEX		;Convert to real hex value
	SWAP A				;Store in upper nibble
	MOV R7, A
	LCALL GETCH			;Get lower nibble
	LCALL ASC_HEX
	ADD A, R7			;Add nibbles together
	MOV R7, A			;Save for later
	
	LCALL ADD_CHKSUM	;Update the runnng total
	
	;Write the data 
	MOV SPI_WR, #40h
	LCALL SPI_WR_RD
	MOV SPI_WR, R6		;Set A12-A8 (using i)
	LCALL SPI_WR_RD
	MOV SPI_WR, R5		;Set A7-A0 (using i)
	LCALL SPI_WR_RD
	MOV SPI_WR, R7		;Set Data
	LCALL SPI_WR_RD
	
	;Wait maximum write time (64*tclck)+400us = ~406us @ 11.0592MHz
	LCALL DELAY1MS
	
	;TODO: The Data Polling feature is also available in the serial mode. In this mode, during
	;      a write cycle an attempted read of the last byte written will result in the complement of the MSB
	;      of the serial output byte on MISO.
	
	;TODO: Read back the Data and verify it? Send error message if incorrect?
	
	;End of 'for' block, do post loop statement (i++)
	MOV A, R5		;get 'i' value
	MOV B, R6
	LCALL INC16		;i++ 16-bit increment (B high byte, A low byte)
	MOV R5, A		;store new 'i' value
	MOV R6, B
	
	SJMP WRITE_LOOP	;Jump back up to check loop conditions
	
WRITE_FINISHED: 
	;Finished writing up to Address 'count' 
	
	LCALL PUTOK		;Indicate success
EXIT_WRITE:
	RET


;Read up to Address 'count' and print it to the UART
;R4/R3 and R6/R5 used as temp storage
READ: 
	MOV A, CMD		;get 'command'
	XRL A, #'r'
	JNZ EXIT_READ
	
	CLR NEED_HELP	;Matched a command, no help needed
	
	;Start Read routine		
	MOV CHKSUM_L, #00h	;Reset 'chksum'
	MOV CHKSUM_H, #00h
	MOV R5, #00h		;'i' = 0
	MOV R6, #00h
	
	;Allow host PC to send data during the read (in case it needs to send XOFF)
	MOV A, #XON
	LCALL PUTCH
	
READ_LOOP:
	MOV A, R5			;get 'i' value
	MOV B, R6		
	MOV R3, COUNT_L		;get 'count'
	MOV R4, COUNT_H
	LCALL COMP16_LT		;check 'for' loop conditions (i < count), if (B/A < R4/R3) then A=1
	JZ READ_FINISHED	;'i' >= 'count', exit 'for' loop (go to putok())

	;Begin 'for' block
	;Check if the host (PC) is busy and wants us to stop reading data
	LCALL CHK_XOFF
	
	MOV SPI_WR, #20h
	LCALL SPI_WR_RD
	MOV SPI_WR, R6		;get 'i' value (A12-A8)
	LCALL SPI_WR_RD
	MOV SPI_WR, R5		;get 'i' value (A7-A0)
	LCALL SPI_WR_RD
	MOV SPI_WR, #00h
	LCALL SPI_WR_RD
	
	MOV A, SPI_RD		;Get the read data
	
	LCALL ADD_CHKSUM	;Update running total with value in A
	LCALL PRINT_HEX		;Print the Data (still in A) to the UART
	
	;end of the 'for' block, do post instruction (i++)
	MOV A, R5		;get 'i' value
	MOV B, R6
	LCALL INC16		;i++, 16-bit increment
	MOV R5, A		;store new 'i' value
	MOV R6, B
	
	SJMP READ_LOOP	;Jump back up to check 'for' loop conditions again
	
READ_FINISHED:
	;Stop host PC from sending any data until we are ready
	MOV A, #XOFF
	LCALL PUTCH
	LCALL PUTOK		;After all Data has been read indicate success
EXIT_READ:
	RET


;Check for XOFF command (only used by READ routine above)
;If we are sent XOFF (from host PC), then wait until we get XON before continuing.
CHK_XOFF: 
	JNB RI, EXIT_CHK_XOFF	;exit if nothing has been received	
	LCALL GETCHR			;Save UART data in A
	XRL A, #XOFF			;#13h = XOFF
	JNZ EXIT_CHK_XOFF
	;We received XOFF so wait until we receive an XON to continue
WAIT_XON:
	LCALL GETCHR	;Wait for data on Rx
	XRL A, #XON		;#11h = XON
	JNZ WAIT_XON
EXIT_CHK_XOFF:
	RET

	
;Program ALL of the Lock Bits, i.e. Protection Mode 4 (disabled further program, verify, and external execution)
LOCK: 
	MOV A, CMD		;get 'command'
	XRL A, #'l'
	JNZ EXIT_LOCK
	
	CLR NEED_HELP	;Matched a command, no help needed
	;Skip Locking the device. Why would you want to?
	SJMP EXIT_LOCK

	;TODO: Implement Lock routine

	LCALL PUTOK		;Report Success
EXIT_LOCK:
	RET
	

;Read ALL of the Lock Bits
READ_LOCK: 
	MOV A, CMD		;get 'command'
	XRL A, #'b'
	JNZ EXIT_READ_LOCK
	
	CLR NEED_HELP	;Matched a command, no help needed
	
	MOV SPI_WR, #24h
	LCALL SPI_WR_RD
	MOV SPI_WR, #00h
	LCALL SPI_WR_RD
	MOV SPI_WR, #00h
	LCALL SPI_WR_RD
	MOV SPI_WR, #00h
	LCALL SPI_WR_RD
	
	;Display the bit status
	MOV DPTR, #STR_LBMODE
	LCALL PUTSTR
	
	;;SPI_RD now holds the lock bits xxx(lb3)(lb2)(lb1)xx
	MOV A, SPI_RD
	ANL A, #1Ch		;Get just those three bits
	
	CJNE A, #00h, LB2
	MOV A, #'1'
	LCALL PUTCH
	LCALL PUTOK		;Report Success
	SJMP EXIT_READ_LOCK
LB2:
    CJNE A, #04h, LB3
	MOV A, #'2'
	LCALL PUTCH
	LCALL PUTOK		;Report Success
	SJMP EXIT_READ_LOCK
LB3:
    CJNE A, #0Ch, LB4
	MOV A, #'3'
	LCALL PUTCH
	LCALL PUTOK		;Report Success
	SJMP EXIT_READ_LOCK
LB4:
	CJNE A, #1Ch, LB5
	MOV A, #'4'
	LCALL PUTCH
	LCALL PUTOK		;Report Success
	SJMP EXIT_READ_LOCK
LB5:
	;Error
	MOV A, #'?'
	LCALL PUTCH
	LCALL PUTOK		;Report Success
EXIT_READ_LOCK:
	RET


;The 'chksum' variable holds a running total of all the bytes read/written so far.
;Send to the UART the actual checksum (2's complement of the running total in 'chksum')
PRINT_CHKSUM: 
	MOV A, CMD		;get 'command'
	XRL A, #'c'
	JNZ EXIT_PRINT_CHKSUM
	
	CLR NEED_HELP	;Matched a command, no help needed
	;Start Print Checksum routine
	MOV DPTR, #STR_CHKSUM
	LCALL PUTSTR
	
	MOV A, CHKSUM_L		;get 'chksum' running total
	MOV B, CHKSUM_H
	LCALL TWOS_COMPLEMENT	;A checksum is the 2's complement of the running total
	LCALL PRINT_HEX16	;Print 16-bit checksum as hex
	
	LCALL PUTOK		;Print success
EXIT_PRINT_CHKSUM:
	RET

  
;Read AT89S51/52 Signature Bytes. Get: Mfg. ID, Model #1, and Model #2
;Only used by TEST_BLANK routine
READ_SIGNATURE:
    ;Read MFG ID
	MOV SPI_WR, #28h
	LCALL SPI_WR_RD
	MOV SPI_WR, #00h
	LCALL SPI_WR_RD
	MOV SPI_WR, #00h
	LCALL SPI_WR_RD
	MOV SPI_WR, #00h
	LCALL SPI_WR_RD
	
	MOV MFG, SPI_RD		;Save the data
	
	;Read Device ID #1 (e.g. 52h means AT89S52)
	MOV SPI_WR, #28h
	LCALL SPI_WR_RD
	MOV SPI_WR, #01h
	LCALL SPI_WR_RD
	MOV SPI_WR, #00h
	LCALL SPI_WR_RD
	MOV SPI_WR, #00h
	LCALL SPI_WR_RD
	
	MOV CHIP, SPI_RD	;Save the Data
	
	;Read Device ID #2 (06h) __NEVER USED__
	MOV SPI_WR, #28h
	LCALL SPI_WR_RD
	MOV SPI_WR, #02h
	LCALL SPI_WR_RD
	MOV SPI_WR, #00h
	LCALL SPI_WR_RD
	MOV SPI_WR, #00h
	LCALL SPI_WR_RD
	
	MOV A, SPI_RD		;Trash the Data
	
	RET


;Finds out how many bytes are blank, and calculates the checksum for all bytes
;R4/R3 and R6/R5 are used as temporary storage in this routine. (R4/R3 = 'bytes')
TEST_BLANK:
	;Test 'chip' against all known model numbers
	LCALL READ_SIGNATURE	;Read 'chip' from target
	MOV A, CHIP		;get 'chip'
CASE1:	;89S51
	CJNE A, #51h, CASE2	
	MOV R3, #00h	;'bytes' = 4096d
	MOV R4, #10h
	SJMP END_SWITCH
CASE2:	;89S52
	CJNE A, #52h, DEFAULT	
	MOV R3, #00h	;'bytes' = 8192d
	MOV R4, #20h
	SJMP END_SWITCH
DEFAULT:	;unknown
	;CJNE A, #0FFh, END_SWITCH
	MOV R3, #00h	;Assume 8k flash (8052)
	MOV R4, #20h	;for everything else
END_SWITCH:
;testblank() continued
	
	MOV CHKSUM_L, #00h		;Reset 'chksum' running total;
	MOV CHKSUM_H, #00h
	MOV NONBLANK_L, #00h	;Reset 'nonblank' counter;
	MOV NONBLANK_H, #00h
	
	MOV R6, #00h	;store 'i' = 0
	MOV R5, #00h

TEST_BLANK_LOOP:
	MOV A, R5		;get 'i' value. 'i' is the Address we are reading.
	MOV B, R6		;We are reading every Address from 0 to 'bytes'
	;R4/R3 already holds 'bytes' variable (set by above Switch/Case section)
	LCALL COMP16_LT	;if('i' < 'bytes'), if (B/A < R4/R3) then A=1
	JZ TEST_BLANK_FINISHED	;'i' >= 'bytes', jump to exit
	
	;Begin 'for' block
	
	MOV SPI_WR, #20h
	LCALL SPI_WR_RD
	MOV SPI_WR, R6		;get 'i' value (A12-A8)
	LCALL SPI_WR_RD
	MOV SPI_WR, R5		;get 'i' value (A7-A0)
	LCALL SPI_WR_RD
	MOV SPI_WR, #00h
	LCALL SPI_WR_RD
	
	MOV A, SPI_RD		;Get the read data
	
	LCALL ADD_CHKSUM	;Add byte (in A) to 'chksum'
	
	;Test for a blank/erased byte (0xFF is the erased state) 
	XRL A, #0FFh
	JZ IS_BLANK
	;Count how many bytes have been programmed 
	;This is only an estimate as opcode 0xff (MOV R7, A) is not counted as being programmed.
	;TODO: Maybe create a function to inc 'nonblank'?
	MOV A, NONBLANK_L	;get 'nonblank' value
	MOV B, NONBLANK_H
	LCALL INC16			;nonblank++, 16-bit increment
	MOV NONBLANK_L, A	;store new 'nonblank' value
	MOV NONBLANK_H, B
IS_BLANK: 

	;end of 'for' block, do post condition (i++)
	MOV A, R5		;get 'i' value
	MOV B, R6
	LCALL INC16		;i++, 16-bit increment
	MOV R5, A		;store new 'i' value
	MOV R6, B

	SJMP TEST_BLANK_LOOP	;jump back up to check 'for' condition
	
TEST_BLANK_FINISHED: 
	RET	
	
	
;Print variables ('mfg','chip','nonblank','count') in a non-friendly format
INFO: 
	MOV A, CMD		;get 'command' variable
	XRL A, #'i'
	;JNZ is out of range for this function so jump to the start or LJMP to the exit
	;JNZ EXIT_PARAMS
	JZ START_INFO
	LJMP EXIT_INFO

START_INFO:	
	CLR NEED_HELP	;Matched a command, no help needed
	;Start Params routine
	;TESTBLANK calls READ_SIGNATURE which gets the chip's mfg. & model number ('chip'). 
	;It also tests for blank bytes and sets the 'nonblank' counter.
	;'count' is initialized to zero at startup, or by the user with SET_COUNTER.
	LCALL TEST_BLANK
	
	JNB PC_PROG, INFO_TEXT
	;printf("%2x,%2x,%4x,%4x",mfg,chip,nonblank,count) => mfg,chip,nonblank,count
	MOV A, MFG		;get 'mfg' variable
	LCALL PRINT_HEX
	MOV A, #','
	LCALL PUTCH
	MOV A, CHIP		;get 'chip' variable
	LCALL PRINT_HEX
	MOV A, #','
	LCALL PUTCH
	MOV A, NONBLANK_L	;get 'nonblank' variable
	MOV B, NONBLANK_H
	LCALL PRINT_HEX16	;LCALL PUTNUM16
	MOV A, #','
	LCALL PUTCH
	MOV A, COUNT_L		;get 'count' variable
	MOV B, COUNT_H
	LCALL PRINT_HEX16	;LCALL PUTNUM16
	SJMP FINISH_INFO

INFO_TEXT:
	;Print variables (Mfg. ID, model #, non-blank bytes, byte counter) in a user-friendly format	
	MOV DPTR, #STR_MFG	;"Mfg: "
	LCALL PUTSTR
	MOV A, MFG
	CJNE A, #0FFh, MFG_FOUND
	MOV DPTR, #STR_UNKNOWN	;Not Found
	LCALL PUTSTR
	SJMP INFO_CHIP
MFG_FOUND:
	MOV A, MFG		;get MFG variable
	LCALL PRINT_HEX
	
INFO_CHIP:
	MOV DPTR, #STR_MODEL	;"Model: "
	LCALL PUTSTR
	MOV A, CHIP		;get 'chip' variable
	CJNE A, #0FFh, CHIP_FOUND
	MOV DPTR, #STR_UNKNOWN	;Not Found
	LCALL PUTSTR
	SJMP INFO_BYTES
CHIP_FOUND:	
	MOV A, CHIP		;get 'chip' varaible
	LCALL PRINT_HEX

INFO_BYTES:
	;"Nonblank bytes: 0x"
	MOV DPTR, #STR_NONBLANK
	LCALL PUTSTR
	MOV A, NONBLANK_L	;get 'nonblank' variable
	MOV B, NONBLANK_H
	LCALL PRINT_HEX16		;Send as ASCII numbers	
	
	;"Bytes counter: 0x"
	MOV DPTR, #STR_COUNTER
	LCALL PUTSTR
	MOV A, COUNT_L		;get 'count' variable
	MOV B, COUNT_H
	LCALL PRINT_HEX16

FINISH_INFO:	
	LCALL PUTOK			;Indicate success
EXIT_INFO: 
	RET
	

;Display the title and a list of the available commands
HELP: 
	;If NEED_HELP is still set then we havn't matched any command,
	;so dispaly the help message (or NAK if software controlled)
	JNB NEED_HELP, EXIT_HELP
	
	JNB PC_PROG, HELP_TEXT
	MOV A, #NAK		;Software control gets a NAK (command not recognized)
	LCALL PUTCH
	SJMP EXIT_HELP
	
HELP_TEXT:
	MOV DPTR, #STR_TITLE
	LCALL PUTSTR
	MOV DPTR, #STR_HELP
	LCALL PUTSTR
	MOV DPTR, #STR_PROMPT
	LCALL PUTSTR
EXIT_HELP:
	RET




;----------------------------------------------------------------------;
;                                                                      ;
;                Math/Compare/Increment/Shift Functions                ;
;                                                                      ;
;----------------------------------------------------------------------;

;Shift nibbles in R4/R3 left and move A into the lower nibble of R3
;R4/R3 holds the 4 nibbles
SHIFT_NIBBLES:
	MOV B, A	;Save a copy of A that we will shift in later
	MOV A, R4
	ANL A, #0Fh
	SWAP A
	MOV R4, A
	MOV A, R3
	ANL A, #0F0h
	SWAP A
	ADD A, R4
	MOV R4, A
	MOV A, R3
	ANL A, #0Fh
	SWAP A
	ADD A, B
	MOV R3, A
	RET
	
	
;16-bit increment by 1 (A is low byte, B is high byte)
INC16:
	ADD A, #01h	 ;Add 1
	XCH A,B
	ADDC A, #00h ;Inc B if A rolled over
	XCH A,B
	RET
	
	
;16-bit increment by 256 (A is low byte, B is high byte)
INC16_64:
	XCH A, B
	ADD A, #01h	 ;Add 1
	XCH A,B
	RET

	
;16-bit 2's complement. (Invert all bits, then add one)
;Input is in B/A. Output is in B/A
TWOS_COMPLEMENT:
	CPL A
	XCH A, B
	CPL A		;Complement B
	XCH A, B
	LCALL INC16	;Add 1
	RET
	

;Increment 'chksum' by the value in A 
ADD_CHKSUM:
	PUSH ACC	;Save the initial value of A
	ADD A, CHKSUM_L	;Add A to low byte
	MOV CHKSUM_L, A	;Store the new low byte
	CLR A
	ADDC A, CHKSUM_H	;Increment high byte if low byte rolled over
	MOV CHKSUM_H, A
	POP ACC		;Restore original A
	RET


;Unsigned (16-bit) [Less Than] Compare (compare B/A to R4/R3)
;if B/A < R4/R3 then A=1
;otherwise (if B/A >= R4/R3) A=0
COMP16_LT: 
	XCH A, B
	CLR C
	SUBB A, R4	;A = B - R4
	JNZ CHECK_LT	;if (B != R4) then skip to less than (Carry) check
	;B and R4 are equal, now check A and R3
	MOV A, B	;get original value of A back, Carry should still be 0 if we got here
	SUBB A, R3	;A = A - R3
CHECK_LT:	
	JC BA_LT	;jump if (B/A < R4/R3)
	
	CLR A		;(B/A >= R4/R3)
	RET
BA_LT:
	MOV A, #01h	;(B/A < R4/R3)
	RET


;Compare B/A to 64d (0x40), if greater read/write in 'page mode'
;'byte mode' is only faster if writing 64 bytes or fewer.
;Return value: A=0 then 'byte mode', A=1 then 'page mode'
COMP_MODE:
	XCH A, B
	JNZ PAGE_WRITE	;If high byte != 0 then 'page mode'
	XCH A, B
	CLR C
	SUBB A, #41h	;65d
	JNC PAGE_WRITE	;Carry only set if number was less than 65d
	CLR A			;'byte mode'
	RET
PAGE_WRITE:
	MOV A, #01h		;'page mode'
	RET


; Convert ASCII digit to HEX value
; A is used for Input AND Output
ASC_HEX:
	CLR CONV_ERR
	CJNE A, #'0', Ch_1	;Set C if less than 0
Ch_1:		
	JC Ch_Bad			; Character is less than '0'
	CJNE A, #'9'+1, Ch_2	;Test for value between 0 and 9
Ch_2:		
	JC VAL_NUM_09		;Character is between 0 and 9..

	CJNE A, #'A', Ch_3	; Test for upper case hex letter..
Ch_3:		
	JC Ch_Bad		; Character is less than 'A'
	CJNE A, #'F'+1, Ch_4	; Test value range..
Ch_4:		
	JC VAL_UPPER_AF		; Character is between A and F
	
	CJNE A, #'a', Ch_5	; Test for upper case hex letter..
Ch_5:		
	JC Ch_Bad		; Character is less than 'A'
	CJNE A, #'f'+1, Ch_6	; Test value range..
Ch_6:		
	JC VAL_LOWER_AF		; Character is between A and F

Ch_Bad:	
	SETB CONV_ERR		; Character is not a Hex number..
	LJMP EXIT_ASC_HEX

VAL_LOWER_AF:
	CLR C
	SUBB A, #'a'-10
	RET
VAL_UPPER_AF:	
	CLR C
	SUBB A, #'A'-10
	RET
VAL_NUM_09:	
	CLR C
	SUBB A, #'0'
EXIT_ASC_HEX:
	RET


;----------------------------------------------------------------------;
;                                                                      ;
;                        Timing/Delay Functions                        ;
;                                                                      ;
;----------------------------------------------------------------------;

;Delay ~1 millisecond (1.00043403 ms)
;921.6 machine cycles = 1 ms delay @ 11.0592 MHz
;912 = FC6Eh
DELAY1MS:  ;LCALL	;+2
	CLR TF0			;+1
	MOV TH0, #0FCh	;+2
	MOV TL0, #6Eh	;+2
	SETB TR0		;+1
	JNB TF0, $		;+0?
	RET				;+2 
					;= 922 MC 
	
	
;Delay time = 1ms * R0
;Actual = (1.00260417 ms * R0) + 0.00434027778 ms
DELAY:
	LCALL DELAY1MS
	DJNZ R0, DELAY	;DJNZ doesn't work with A (use ACC)
	RET
	
	
	
	
;----------------------------------------------------------------------;
;                                                                      ;
;                       SPI/UART/Print Functions                       ;
;                                                                      ;
;----------------------------------------------------------------------;
	
;Simultaneously Write and Read a byte from the SPI bus
;A, R0 used for temp storage
SPI_WR_RD:
	MOV SPI_RD, #00h
	MOV R0, #8		;8 bits to write/read
	CLR C
SPI_LOOP:
	MOV A, SPI_WR	
	RLC A			;Get bit to write
	MOV SPI_WR, A	
	JC SET_MOSI		;Set output for this bit
	CLR MOSI
	SJMP SPI_READ
SET_MOSI:
	SETB MOSI
SPI_READ:
	SETB SCK		;Latch data
	MOV A, SPI_RD
	RL A
	MOV SPI_RD, A
	JNB MISO, SPI_CLOCK
	ORL SPI_RD, #01h	;"Rotate in" the read bit
SPI_CLOCK:
	CLR SCK			;Output the next bit
	DJNZ R0, SPI_LOOP
	RET
	
;Send "ok" to the UART (for user feedback)
PUTOK: 
	JB PC_PROG, PUTACK
	MOV DPTR, #STR_OK
	LCALL PUTSTR
	MOV DPTR, #STR_PROMPT
	LCALL PUTSTR
	RET
;Send ACK to the UART (for software feedback)
PUTACK:
	MOV A, #ACK
	LCALL PUTCH
	RET
	
;Send "error" to the UART (for user feedback)
PUTERR:
	JB PC_PROG, PUTNAK
	MOV DPTR, #STR_ERROR
	LCALL PUTSTR
	MOV DPTR, #STR_PROMPT
	LCALL PUTSTR
	RET
PUTNAK:
	MOV A, #NAK
	LCALL PUTCH
	RET
	

;Prints the _two_ hex bytes stored in B/A using four ascii digits ('0'-'F')
PRINT_HEX16:
	PUSH ACC
	MOV A, B
	LCALL PRINT_HEX
	POP ACC
	LCALL PRINT_HEX
	RET
	
	
;Prints the hex byte stored in A using two ascii digits (e.g. 0xA7 is printed as 'A' and '7')
;Uses R4 as temp storage
PRINT_HEX:
	MOV R4, A	;Save a copy of the original value
	SWAP A		;Get the upper bits
	ANL A, #0FH	;Just work with one nibble at a time
	MOV B, A	;Save a copy of the upper nibble
	CLR C		;Clear C for SUBB
	SUBB A, #0AH	;Set Carry bit if nibble is less than 10d
	MOV A, B	;Get back the nibble before SUBB
	JC LT10U	;Jump if less than 10d
	ADD A, #37H	;Convert to ascii alpha value ('A' - 'F')
	SJMP NEXT_NIBBLE
LT10U:
	ADD A, #30H	;Convert to ascii numeric value ('0' - '9')
NEXT_NIBBLE:
	XCH A, R4	;Put the original byte in A and save the converted upper nibble in R4
	ANL A, #0FH	;Get the lower nibble by itself
	MOV B, A	;Save a copy
	CLR C		;Get ready for SUBB
	SUBB A, #0AH	;Set Carry bit if nibble is less than 10d
	MOV A, B	;Get back the nibble before SUBB
	JC LT10L	;Jump if less than 10d
	ADD A, #37H	;Convert to ascii alpha value ('A' - 'F')
	SJMP TRANSMIT
LT10L:
	ADD A, #30H	;Convert to ascii numeric value ('0' - '9')
TRANSMIT:
	XCH A, R4
	LCALL PUTCH
	XCH A, R4
	LCALL PUTCH
	RET


;Prints a string to the UART
;DPTR must point to a null-terminated string
PUTSTR:
	CLR A
	MOVC A, @A+DPTR	;Get the character DPTR is pointing to
	JZ EXIT_PUTSTR	;Check for end of string, exit if we hit '\0'
	LCALL PUTCH		;Tx the char to the UART
	INC DPTR
	SJMP PUTSTR
EXIT_PUTSTR:
	RET
	

;Send the byte in A to the UART
PUTCH:
	JNB TI, $		;Wait for the prev Tx to finish
	CLR TI			;Clear the Tx flag
	MOV SBUF, A		;Send the byte
EXIT_PUTCH:
	RET

;Send XON. Wait until a (single) byte is received. Send back XOFF.
;Send back the received character if PC_PROG isn't set (so the user can see their input)
;Uses flow control to stop the host PC from sending data too quickly
;Character returned in A. '\r' is never returned, it is converted to '\n' before returning
GETCH:
	;Check for previously received byte (slipped through before XOFF was sent?)
	JB RI, SKIP_XON	
	;Let the host PC know that it can transmit a byte
	MOV A, #XON	
	LCALL PUTCH
SKIP_XON:
	JNB RI, $		;Wait for a byte on Rx
	MOV A, SBUF
	CLR RI
	MOV B, A		;Save the received byte
	;Send XOFF to suspend any further transmission from the host PC
	MOV A, #XOFF
	LCALL PUTCH
	MOV A, B		;Restore the received byte
	
	XRL A, #ENQ		;Check if we got the special Sync byte
	JNZ GETCH_PRINT	;Print the chatacter received if it was not 
	MOV A, B		;Restore the received byte again
	SJMP EXIT_GETCH	;Exit without printing the byte
	
GETCH_PRINT:
	;Send received byte back to the host PC (for display to the user)
	MOV A, B		;Restore the received byte again
	JB PC_PROG, EXIT_GETCH	;Exit without user feedback if being programmed by software
	LCALL PUTCH		;Print the byte received to the user for feedback
	CJNE A, #0Dh, EXIT_GETCH	;Exit unless we got a '\r'
	MOV A, #0Ah		;Replace '\r' with '\n'
EXIT_GETCH:
	RET
		

;Wait for Rx data on the UART and save it in A
;No flow control overhead
GETCHR:
	JNB RI, $
	CLR RI
	MOV A, SBUF
	RET


;----------------------------------------------------------------------;
;                                                                      ;
;                       Null-terminated Strings                        ;
;                                                                      ;
;----------------------------------------------------------------------;
STR_TITLE: 
DB 0Dh, 0Ah, "TSBurner (v0.1) for ATMEL 89S51/52"
DB 0Dh, 0Ah, "(Inspired by Easy-Downloader V2.0 by Wichit Sirichote)"
DB 0Dh, 0Ah, "Type ? for help", 0
STR_HELP: 
DB 0Dh, 0Ah
DB 0Dh, 0Ah, "<cmd>  Description"
DB 0Dh, 0Ah, "   p    Enter 'Programming Mode' (must be run before any other operation)"
DB 0Dh, 0Ah, "   s    Set 'count', the number of bytes to be read or written"
DB 0Dh, 0Ah, "   e    Erase the entire chip"
DB 0Dh, 0Ah, "   w    Write 'count' number of bytes to the chip"
DB 0Dh, 0Ah, "   r    Read 'count' number of bytes from the chip"
DB 0Dh, 0Ah, "   l    (Disabled) Set all three Lock Bits (Don't ever use this! It will disable further programming of the target chip.)"
DB 0Dh, 0Ah, "   b    Read the Lock Bits"
DB 0Dh, 0Ah, "   c    Print the checksum"
DB 0Dh, 0Ah, "   i    Get information about the chip and the current 'count'"
DB 0Dh, 0Ah, "   ?    Display this help message", 0
STR_PROMPT: DB 0Dh, 0Ah, ">", 0
STR_OK: DB 0Dh, 0Ah, "ok", 0
STR_ERROR: DB 0Dh, 0Ah, "error", 0
STR_CHKSUM: DB 0Dh, 0Ah, "CHKSUM = ", 0
STR_MFG: DB 0Dh, 0Ah, "Mfg: ", 0
STR_MODEL: DB 0Dh, 0Ah, "Model: ", 0
STR_NONBLANK: DB 0Dh, 0Ah, "Nonblank bytes: 0x", 0
STR_COUNTER: DB 0Dh, 0Ah, "Byte counter: 0x", 0
STR_LBMODE: DB 0Dh, 0Ah, "Lock Bit Mode: ", 0
STR_UNKNOWN: DB "Unknown", 0

END				

Download Source (29.82 Kb)

The programmer also makes use of a host Windows application to make sending data easier. Technically, the programmer can work without any host application via a UART connection to the board, but typing data bytes into the Console window would get awfully tedious after a while.

The Windows application is styled after the Atmel Flip interface (as shown in the screenshot below).

TSBurner_screenshot.png

The entire Visual Studio project is here: TSBurner.zip (809.21 Kb).

The source code for both parts of the project can also be found on my personal git repo: http://git.timothyshaffer.com.

Prototype

Below is a picture of an early prototype. It still contained a boost converter at the bottom for 12V "High Voltage" programming which I later removed.

TSBurner_prototype_small.jpg
Download Full-Size

Future

If I find another series of 8051-based micros that I want to program, then expect the source code to expand. Until then, this project is done.