PIC16 Code to drive a HD44780 display over SPI, using a HC595

; Sample to drive a HD44780 LCD over SPI, using a 74HC595 shift register
; See: https://highfieldtales.wordpress.com/2011/12/04/very-fast-spi-to-parallel-interface-for-netduino/
;
;   PIC	    74HC595 HD44780
;	    15	    D4
;	    1	    D5
;	    2	    D6
;	    3	    D7
;	    4	    RS
;	    5	    RW
;	    6	    E
;	    7
;	    9 - 12
;	    10
;   CLK	    11
;   	    12 - 9
;	    13 - GND
;   DAT	    14
;
; The SS port resets the shift register, via an inverter
	
	list P=16F876A
	#include p16f876a.inc
	__CONFIG    _CP_ALL   & _DEBUG_OFF  & _CPD_OFF & _LVP_OFF & _BODEN_ON & _PWRTE_ON & _WDT_OFF & _HS_OSC

	#define	SS_PORT	PORTB
	#define SS	PORTB,1
	
RES_VECT  CODE    0x0000	; processor reset vector
    GOTO    START		; go to beginning of program

; TODO ADD INTERRUPTS HERE IF USED

MAIN_PROG CODE			; let linker place main program

START
    banksel	TRISC		; Set up pins for SPI
    bcf		TRISC,5		; Data pin 16
    bcf		TRISC,3		; Clock pin 14
    bsf		TRISC,4		; Data in
    
    clrf	TRISB
    
    banksel	SSPCON		; Set up SPI
    bcf		SSPCON,5	; Disable SPI
    movlw	b'00010000'
    movwf	SSPCON
    movlw	b'11000000'	; 1 = Input data sampled at end of data output time
				;  1 = Transmit occurs on transition from active to Idle clock state 
    movwf	SSPSTAT		;
    bsf		SSPCON,4	; CKP 1 = Idle state for clock is a high level 
    bcf		SSPCON,0	; 0000 = SPI Master mode, clock = FOSC/4 
    bcf		SSPCON,1
    bcf		SSPCON,2
    bcf		SSPCON,3
    bsf		SSPCON,5	; SSPEN 1 = Enables serial port and configures SCK, SDO, SDI, and SS as serial port pins 
		

    call	init_lcd

loop:
   
    GOTO loop                   ; loop forever
    
init_lcd		
				; Remember to init ports!
				; Initialise HD44780 for 4-bits, 2 lines

	call	Delay_100ms	; 100ms, step 1
	
	banksel	FLAGS
	movlw	0		; Command
	movwf	FLAGS
	movlw	B'00110000'	; initialise module, step 2
	call	lcd_byte
	call	Delay5ms	; > 4.1ms
	
	movlw	B'00110000'	; initialise module, step 3
	call	lcd_byte
	call	Delay100us	; > 100us
	
	movlw	B'00110000'	; initialise module, step 4
	call	lcd_byte
	call	Delay100us	; > 100us
	
	movlw	B'00100000'	; initialise module, step 5
	call	lcd_byte
	call	Delay100us	; > 100us
	
;x3	goto	x3		; Step 6, the real function select
	movlw	B'00101100'	; display function (4-bits, 2 lines, 5x10 dots)
	call	lcd_byte
	call	Delay100us	; > 53us
	
	movlw	B'00001000'	; Step 7,  display on/off
	call	lcd_byte
	call	Delay100us	; > 53us
	
	movlw	B'00000001'	; Step 8, display clear
	call	lcd_byte
	call	Delay5ms	; > 3ms

				;  |- 0 = Display off, 1 = display on
				; |- 0 = 1 line, 1 = 2 line
	movlw	B'00000110'	; Step 9, entry mode. cursor moves right, display not shifted
	call	lcd_byte
	call	Delay100us	; > 53us
				; Actual init ends here

				;  |- 0 = Display off, 1 = Display on
				;   |- 0 = Cursor off, 1 = Cursor on
				;    |- 0 = Blink off, 1 = Blink on
	movlw	B'00001111'	; display on, cursor on, blink on
	call	lcd_byte
	call	Delay100us	; > 53us
				; Init done, stuff below is just for testing.
	banksel FLAGS
	movlw	1		; Data
	movwf	FLAGS
	movlw	'A'
	call	lcd_byte
	movlw	'B'
	call	lcd_byte
	movlw	'C'
	call	lcd_byte
xy	movlw	'D'
	call	lcd_byte
	call	Delay_100ms
	movlw	'E'
	call	lcd_byte
	return
	
; Send byte in W to LCD as command or data, depending on FLAGS
; 0 in flags as command or 1 as data
lcd_byte
	banksel D_STO
	movwf	D_STO
	swapf	D_STO,w			; Swap high and low nibbles, result in W
	andlw	b'00001111'		; Mask out
	call	lcd_push_nibble		; Push high nibble
	banksel D_STO
	movf	D_STO,w			; get byte
	andlw	b'00001111'		; Mask ou
	call	lcd_push_nibble		; Push low nibble
	call	Delay100us		; > 100us delay
	return

; Push high nibble in w as data or cmd
; Input: Nibble in W, 0 in flags as command or 1 as data
lcd_push_nibble				; Push high nibble in w as data or cmd
	
	btfsc	FLAGS,0			; flag set=data, set Q4 in that case
	iorlw	b'00010000'
					; Not set leave as is
	iorlw	b'10000000'		; Set high bit
	call	send_w_on_spi		; Send nibble, E is still low
	iorlw	b'11000000'		; Set E bit
	call	send_w_on_spi		; Send nibble, E is high
	andlw	b'10111111'		; E -> low
	call	send_w_on_spi		; Send nibble, E is low
	return
	
send_w_on_spi
	;banksel	SS_PORT
	bcf	SS			; Assert SS (low)
	movwf	SSPBUF			; begin transmission
WAIT	btfss	PIR1, SSPIF		; send completed? if Yes skip next
	goto	WAIT
	bcf	PIR1, SSPIF		; yes, clear flag
	;banksel	SS_PORT
	bsf	SS			; Unassert SS (high)
	return
;-----------------------------------------------
; Delay = 0.0001 seconds
; Clock frequency = 20 MHz

; Actual delay = 0.0001 seconds = 500 cycles
; Error = 0 %
Delay100us
	;banksel STORE1
	;499 cycles
	movlw	0xA6
	movwf	STORE1
Delay100us_0
	decfsz	STORE1, f
	goto	Delay100us_0

			;1 cycle
	nop
	return
;-----------------------------------------------
; Delay = 0.005 seconds
; Clock frequency = 20 MHz

; Actual delay = 0.005 seconds = 25000 cycles
; Error = 0 %
Delay5ms
	;banksel STORE1
			;24998 cycles
	movlw	0x87
	movwf	STORE1
	movlw	0x14
	movwf	STORE2
Delay5ms_0
	decfsz	STORE1, f
	goto	$+2
	decfsz	STORE2, f
	goto	Delay5ms_0

			;2 cycles
	goto	$+1
	return	
	
Delay_100ms
;-----------------------------------------------
; Delay = 0.1 seconds
; Clock frequency = 20 MHz

; Actual delay = 0.1 seconds = 500000 cycles
; Error = 0 %

	banksel STORE1
			;499994 cycles
	movlw	0x03
	movwf	STORE1
	movlw	0x18
	movwf	STORE2
	movlw	0x02
	movwf	STORE3
Delay_100ms_0
	decfsz	STORE1, f
	goto	$+2
	decfsz	STORE2, f
	goto	$+2
	decfsz	STORE3, f
	goto	Delay_100ms_0

			;6 cycles
	goto	$+1
	goto	$+1
	goto	$+1
	return
	
	udata
STORE1	res 1
STORE2	res 1
STORE3	res 1
FLAGS	res 1
D_STO	res 1
    END