top of page

Driving a 1602 LCD with a 286 Processor!

  • Writer: rehsd
    rehsd
  • Oct 18, 2022
  • 4 min read

Updated: Oct 20, 2022

I have been working on a breadboard-based 80286 system. Recently, I made it to a nice milestone -- I can now drive a 1602 LCD!


This is a continuation of Start to an 80286 System, if you are interested in my journey so far (and more of the design details).



Schematic

ree

Decode Logic

ree

Code Used in Video

; *physical memory map*
;-----------------------
;-         ROM         -
;-        0.5 MB       -
;-   0x80000-0xFFFFF   -
;-----------------------
;-         RAM         -
;-        0.5 MB       -
;-   0x00000-0x7FFFF   -
;-----------------------

CPU	286
BITS 	16				

TIMES 0x80000-($-$$) DB 0	;Fill bottom half of ROM with zeros.
					;Bottom half of address space used by RAM. 
					;Controlled with A19 decode.

ORG 0x8000:0x0000			;Usable ROM starts at physical address 0x80000
;ORG 0x0

TOP:					;physically at 0x80000 in ROM
CLI					;disable interrupts

;*** SETUP REGISTERS **********************************
MOV	AX,	0X0
;					;code segment
MOV	DS,	AX			;data segment
MOV	ES,	AX			;extra segment
MOV	SS,	AX			;stack segment
MOV	SP,	AX			;Start at 0, will wrap around to FFFE
;*** /SETUP REGISTERS *********************************

MOV AL, 0b00111000		;Set 8-bit mode; 2-line display; 5x8 font
CALL lcd_instruction
MOV AL, 0b00001110 		;Display on; cursor on; blink off
CALL lcd_instruction
MOV AL, 0b00000110 		;Increment and shift cursor; don't shift display
CALL lcd_instruction
MOV AL, 0b00000001 		;Clear display
CALL lcd_instruction

MOV AL, 'H'
CALL print_char
MOV AL, 'E'
CALL print_char
MOV AL, 'L'
CALL print_char
MOV AL, 'L'
CALL print_char
MOV AL, 'O'
CALL print_char
MOV AL, ' '
CALL print_char
MOV AL, '2'
CALL print_char
MOV AL, '8'
CALL print_char
MOV AL, '6'
CALL print_char
MOV AL, '!'
CALL print_char

HLT

lcd_instruction:
	OUT 0x02, AL			;sta PORTB
	
	MOV AL, 0x0
	OUT 0x04, AL			;lda #0         ;Clear RS/RW/E bits
						;sta PORTA
	
	MOV AL, 0b10000000
	OUT 0x04, AL			;lda #E         ;Set E bit to send instruction
						;sta PORTA

	MOV AL, 0x0
	OUT 0x04, AL			;lda #0         ;Clear RS/RW/E bits
						;sta PORTA
	RET

print_char:
	OUT 0x02, AL			;sta PORTB

	MOV AL, 0b00100000
	OUT 0x04, AL			;lda #RS         ; Set RS; Clear RW/E bits
						;sta PORTA

	MOV AL, 0b10100000		
	OUT 0x04, AL			;lda #(RS | E)   ; Set E bit to send instruction
						;sta PORTA

	MOV AL, 0b00100000		
	OUT 0x04, AL			;lda #RS         ; Clear E bits
						;sta PORTA

	RET

TIMES 0xFFFF0-($-$$) NOP	;Fill ROM with NOPs up to startup address
					;(upper portion of 1 MB addr space)
					;This will get to 0xFFFF0 

RESET:				;at 0xFFFF0			Processor starts reading here
JMP 0x8000:0x0			;EA	00 00 00 80		Jump to TOP: label


TIMES 0x100000-($-$$) DB 1	;Fill the rest of ROM with bytes of 0x01



Updated Decode Logic

I updated the decode logic diagram in PSoC Creator to make it more readable for myself. I also added an I/O read for port 0x02 so that I can read the LCD busy status.

ree


Next Steps

In the coming months, I plan to update the system with the following:

  • 82C284 Clock Generator

  • 82C288 Bus Controller

  • D8255 Programmable Peripheral Interface

  • Keyboard

  • D80287 Math Coprocessor

Further out, I plan to work on:

  • Mouse

  • SPI

  • ISA Bus

  • ISA VGA Card

  • ISA Sound Card


In the above video, I was running a processor clock of 2 MHz. With a little minor tweaking, I now have the processor clock running at 5 MHz.

ree

Latest Code

;Assembler: NASM
;
; *physical memory map*
;-----------------------
;-    ROM  (0.5 MB)    -
;-   0x80000-0xFFFFF   -
;-----------------------
;-    RAM  (0.5 MB)    -
;-   0x00000-0x7FFFF   -
;-----------------------

PORTB EQU 0x02
PORTA EQU 0x04

E 	EQU 0b10000000
RW 	EQU 0b01000000
RS	EQU 0b00100000

ORG 	0x0
CPU	286
BITS 	16	

STRING_HELLO DB 'Hello at 4 MHz!', 0x0			

TIMES 0x80000-($-$$) DB 0	;Fill bottom half of ROM with zeros.
					;Bottom half of address space used by RAM. 
					;Controlled with A19 decode.

;ORG 0x8000:0x0000

TOP:					;physically at 0x80000 in ROM
CLI					;disable interrupts

;*** SETUP REGISTERS **********************************
MOV	AX,	0X0
MOV	DS,	AX			;data segment
MOV	ES,	AX			;extra segment
MOV	SS,	AX			;stack segment
MOV	SP,	AX			;Start at 0, will wrap around to FFFE
;*** /SETUP REGISTERS *********************************

MOV AL, 0b00111000		;Set 8-bit mode; 2-line display; 5x8 font
CALL lcd_instruction
MOV AL, 0b00001110 		;Display on; cursor on; blink off
CALL lcd_instruction
MOV AL, 0b00000110 		;Increment and shift cursor; don't shift display
CALL lcd_instruction
MOV AL, 0b00000001 		;Clear display
CALL lcd_instruction

CALL delay

CALL print_message

HLT

print_message:
	PUSHA

	MOV AL, 'H'
	CALL print_char
	MOV AL, 'E'
	CALL print_char
	MOV AL, 'L'
	CALL print_char
	MOV AL, 'L'
	CALL print_char
	MOV AL, 'O'
	CALL print_char
	MOV AL, ' '
	CALL print_char
	MOV AL, '2'
	CALL print_char
	MOV AL, '8'
	CALL print_char
	MOV AL, '6'
	CALL print_char
	MOV AL, ' '
	CALL print_char
	MOV AL, ' '
	CALL print_char
	MOV AL, ' '
	CALL print_char
	MOV AL, '5'
	CALL print_char
	MOV AL, 'M'
	CALL print_char
	MOV AL, 'H'
	CALL print_char
	MOV AL, 'z'
	CALL print_char

	;MOV AL, 0b10101000 		;move cursor to line 2  **not working  ??!
	;CALL lcd_instruction
	
	POPA
	RET

lcd_wait:
	PUSHA
.lcd_busy:
	MOV AL, RW				;lda #RW
	OUT PORTA, AL			;sta PORTA
	MOV AL, (RW|E)			;lda #(RW | E)
	OUT PORTA, AL			;sta PORTA
	IN AL, PORTB			;lda PORTB
	AND AL, 0b10000000		;and #%10000000
	JNZ .lcd_busy			;bne lcdbusy

	MOV AL, RW				;lda #RW
	OUT PORTA, AL			;sta PORTA
	
	POPA
	RET

lcd_instruction:
	PUSHA
	CALL lcd_wait

	OUT PORTB, AL			;sta PORTB
	
	MOV AL, 0x0
	OUT PORTA, AL			;lda #0         ;Clear RS/RW/E bits
						;sta PORTA
	
	MOV AL, E				;0b10000000
	OUT PORTA, AL			;lda #E         ;Set E bit to send instruction
						;sta PORTA

	MOV AL, 0x0
	OUT PORTA, AL			;lda #0         ;Clear RS/RW/E bits
						;sta PORTA
	POPA
	RET

print_char:
	PUSHA
	CALL lcd_wait

	OUT PORTB, AL			;sta PORTB

	MOV AL, RS				;0b00100000
	OUT PORTA, AL			;lda #RS         ; Set RS; Clear RW/E bits
						;sta PORTA

	MOV AL, (E|RS)			;0b10100000		
	OUT PORTA, AL			;lda #(RS | E)   ; Set E bit to send instruction
						;sta PORTA

	MOV AL, 0b00100000		
	OUT PORTA, AL			;lda #RS         ; Clear E bits
						;sta PORTA
	POPA
	RET

delay:
	PUSHA
	mov bp, 0x0000
	mov si, 0x0001
	.delay2:
		dec bp
		nop
		jnz .delay2
		dec si
		cmp si,0    
		jnz .delay2
	POPA
	RET

TIMES 0xFFFF0-($-$$) NOP	;Fill ROM with NOPs up to startup address
					;(upper portion of 1 MB addr space)
					;This will get to 0xFFFF0 

RESET:				;at 0xFFFF0			Processor starts reading here
JMP 0x8000:0x0			;EA	00 00 00 80		Jump to TOP: label


TIMES 0x100000-($-$$) DB 1	;Fill the rest of ROM with bytes of 0x01



 
 
 

Comments


bottom of page