;***************************************************************************
;
;	Hardware RS232 Test Routine for PIC 16F77 V1.05
;	===============================================
;
;	written by Peter Luethi, 21.03.1999, Dietikon, Switzerland
;	http://www.electronic-engineering.ch
;	last update: 16.01.2005
;
;	V1.05:	Added transmission of status message ('@') every 2.75 s
;		when PIC terminal is idle. Used therefore 24 bit counter.
;		(31.12.2004)
;
;	V1.04:	Added LCDcflag (LCD command/data flag) to comply with
;		latest LCD modules. Changed controller type from PIC16C74A
;		to flash device, i.e. PIC16F77.
;		(18.04.2004)
;
;	V1.03:	Moved LCD and RS232 output from interrupt service routine
;		to normal operation sub-routine, activated through flag.
;		(28.10.2003)
;
;	V1.02:	Fixed issue in RS232 hardware reception service routine.
;		Reads now both data bytes out of double-buffered RX FIFO.
;		Tries to eliminate some sporadic reception hangs under
;		heavy full-duplex traffic scenarios.
;		In case of FIFO overrun errors in the USART receive logic,
;		the service routine performs a partial reset of the USART
;		receive logic. No specific error handling for RS232
;		reception framing errors.
;		(28.10.2003)
;
;	V1.01:	Updated to latest revision of m_lcdv08.asm
;		(28.12.2000)
;
;	V1.00:	Initial release (24.04.1999)
;
;	This code and accompanying files may be distributed freely and
;	modified, provided this header with my name and this notice remain
;	intact. Ownership rights remain with me.
;	You may not sell this software without my approval.
;
;	This software comes with no guarantee or warranty except for my
;	good intentions. By using this code you agree to indemnify me from
;	any liability that might arise from its use.
;
;
;	SPECIFICATIONS:
;	===============
;	Processor:			Microchip PIC 16F77 (16C74A)
;	Clock Frequency:		4.00 MHz (HS mode)
;	Throughput:			1 MIPS
;	RS232 Configuration:		9600 with BRGH = 1
;	RS232 Baud Rate:		9600 baud, 8 bit, no parity, 1 stopbit
;	Required Hardware:		MAX 232, dot matrix LCD display
;	Code Size of entire Program:	approx. 566 instruction words
;	Acquisition Methodology:	Preemptive, interrupt-based RS232
;					data acquisition, with LCD display
;					output and RS232 echo during normal
;					operation
;
;	ABSTRACT:
;	=========
;	Fully hardware controlled RS232 reception and transmission.
;	Display of received ASCII characters sent from PC via RS232 and
;	their corresponding decimal value on the dot matrix LCD.
;	The microcontroller sends feedback of received characters back to
;	the terminal window. When the PIC terminal is idle, it sends a
;	status message '@' to the PC every 2.75 seconds.
;
;
;	DESCRIPTION:
;	============
;	Developed and tested on Microchip PIC 16C74A.
;
;	Although it's not recommended to use BRGH = 1 with the 16C74A,
;	I've tested the device on 9600 baud and it runs without any
;	issues. Referring to the specification, the error using this
;	selection in conjunction with a 4 MHz crystal is 0.16%. This is
;	sufficient for error-free communication within this application.
;
;	This program handles all aspects of transmission and reception
;	through interrupts.
;
;	Program shows the implementation and function of the modules m_bank.asm,
;	m_wait.asm, m_lcd.asm and m_lcdv08.asm on the PIC16F77 or PIC16C74A.
;
;
;	IMPLEMENTED FEATURES:
;	=====================
;	- Bi-directional communication between microcontroller application
;	  and remote RS232 client by hardware-based RS232 transmission.
;	- Display of received character on dot matrix LCD.
;
;***************************************************************************

;***** COMPILATION MESSAGES & WARNINGS *****

	ERRORLEVEL -207 	; found label after column 1
	ERRORLEVEL -302 	; register in operand not in bank 0

;***** PROCESSOR DECLARATION & CONFIGURATION *****

	PROCESSOR 16F77
	#include "p16f77.inc"

	;PROCESSOR 16C74A
	;#include "p16c74a.inc"
	
	; embed configuration data within .asm file
	__CONFIG   _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC & _BODEN_ON

;***** MEMORY STRUCTURE *****

	ORG     0x00			; processor reset vector
  	goto    MAIN			; main program

	ORG     0x04			; interrupt vector
	goto	ISR			; Interrupt Service Routine (ISR)

;***** PORT DECLARATION *****

	LCDtris	equ	TRISB		; LCD on portB
	LCDport	equ	PORTB
	
;***** CONSTANT DECLARATION *****

	CONSTANT BASE = 0x20		; base address of user file registers
	CONSTANT LCDWAIT = 0x01		; clk in [0..5] MHz
	CONSTANT LCDSPEED = 0x00	; clk in [0..9] MHz

	CONSTANT LF =	d'10'		; line feed
	CONSTANT CR =	d'13'		; carriage return
	CONSTANT TAB =	d'9'		; tabulator
	CONSTANT BS =	d'8'		; backspace

;***** REGISTER DECLARATION *****

	TEMP1	set	BASE+d'0'	; Universal temporary register
	TEMP2	set	BASE+d'1'	; ATTENTION !!!
	TEMP3	set	BASE+d'2'	; They are used by various modules.
	TEMP4	set	BASE+d'3'	; If you use them, make sure not to use
	TEMP5	set	BASE+d'4'	; them concurrently !

	FLAGreg	equ	BASE+d'7'
	#define	LCDbusy	FLAGreg,0x00	; LCD busy flag declared within flag register
	#define	BCflag	FLAGreg,0x01	; blank checker for preceding zeros
	#define	RSflag	FLAGreg,0x02	; RS232 data reception flag
	#define RX2flag	FLAGreg,0x03	; two RS232 bytes received
	#define	LCDcflag FLAGreg,0x04	; LCD command/data flag

	LO	equ	BASE+d'8'	; binary to decimal output (8 bit)
	LO_TEMP	equ	BASE+d'9'

	RXreg	equ	BASE+d'10'	; first RS232 RX-Data register
	RXreg2	equ	BASE+d'11'	; second RS232 RX-Data register
	RXtemp	equ	BASE+d'12'	; intermediate data register for LCD output

	W_TEMP	equ	BASE+d'13'	; context register (ISR)
	STATUS_TEMP equ	BASE+d'14'	; context register (ISR)
	PCLATH_TEMP equ	BASE+d'15'	; context register (ISR)
	FSR_TEMP equ	BASE+d'16'	; context register (ISR)

	LOcnt	equ	BASE+d'17'	; low byte of 24 bit counter
	MEDcnt	equ	BASE+d'18'	; medium byte of 24 bit counter
	HIcnt	equ	BASE+d'19'	; high byte of 24 bit counter

;***** INCLUDE FILES *****

	#include "..\..\m_bank.asm"
	#include "..\..\m_wait.asm"
	#include "..\..\m_lcd.asm"	; standard version (fixed delay)
	;#include "..\..\m_lcd_bf.asm"	; fast, bi-directional version (busy flag)
	#include "..\..\m_lcdv08.asm" 	; 8 bit to decimal conversion for LCD
	
;***** MACROS *****

RS232init macro
	BANK1			; Asynchronous USART assignment:
	bsf	TXSTA,BRGH	; BRGH = 1
	movlw	d'25'		; 9600 baud @ 4.00 MHz, BRGH = 1
	movwf	SPBRG
	BANK0
	bsf	RCSTA,SPEN	; enable serial port
	BANK1
	bsf	PIE1,RCIE	; enable serial reception interrupt
	bsf	TXSTA,TXEN	; enable serial transmission
	BANK0
	bsf	INTCON,PEIE	; enable peripheral interrupts
	bsf	RCSTA,CREN	; enable continuous reception
	endm

SEND	macro	send_char
	movlw	send_char
	call	_RSxmit
	endm

SENDw	macro
	call	_RSxmit
	endm

;***** SUBROUTINES *****

COUNTERinit ; *** Initialize 24 bit counter for status message wait interval ***
	clrf	LOcnt		; init counter
	clrf	MEDcnt		; init counter
	movlw	d'6'
	movwf	HIcnt		; init counter
	RETURN

_RSxmit	BANK1
_RSbusy	btfss	TXSTA,TRMT	; check, if previous transmission
	goto	_RSbusy		; has been terminated
	BANK0
	movwf	TXREG		; send next char
	RETURN

RSservice ; *** RS232 echo & LCD display routine for received RS232 characters ***
	; first byte in RXreg
	movfw	RXreg		; retrieve data
	movwf	RXtemp		; store data
	call	_RSdisp		; call output subroutine
	btfss	RX2flag		; check for second data byte received
	goto	RSdispEND
	; second byte in RXreg2, i.e. RX2flag = 1
	movfw	RXreg2		; retrieve data
	movwf	RXtemp		; store data
	call	_RSdisp		; call output subroutine
	bcf	RX2flag		; reset flag
	btfss	RCSTA,OERR	; check for RX overrun (overrun error bit)
	goto	RSdispEND
	; buffer overrun, severe error, reset USART RX logic
	bcf	RCSTA,CREN	; reset USART RX logic, clear RCSTA,OERR
	bcf	RCSTA,FERR	; reset framing error bit
	bsf	RCSTA,CREN	; re-enable continuous reception
RSdispEND
	bcf	RSflag		; reset RS232 data reception flag
	BANK1
	bsf	PIE1,RCIE	; re-enable USART reception interrupt
	BANK0			;  (interrupt flag already cleared in ISR)
	RETURN

_RSdisp	; output subroutine for serial data received
	LCD_DDAdr 0x45
	movfw	RXtemp
	LCDw			; ascii character output
	LCD_DDAdr 0x4D
	movfw	RXtemp
	movwf	LO
	LCDval_08		; numeric ascii value output
	; send echo back to PC
	SEND	TAB
	SEND	'r'
	SEND	'e'
	SEND	'c'
	SEND	'e'
	SEND	'i'
	SEND	'v'
	SEND	'e'
	SEND	'd'	
	SEND	' '
	movfw	RXtemp		; retrieve value
	SENDw			; send across RS232 line
	SEND	' '
	SEND	'o'
	SEND	'n'
	SEND	' '
	SEND	'M'
	SEND	'i'
	SEND	'c'
	SEND	'r'
	SEND	'o'
	SEND	'c'
	SEND	'h'
	SEND	'i'
	SEND	'p'
	SEND	' '
	SEND	'P'
	SEND	'I'
	SEND	'C'
	SEND	'1'
	SEND	'6'
	SEND	'F'
	SEND	'7'
	SEND	'7'
	SEND	CR		; Carriage Return
	SEND	LF		; Line Feed
	RETURN


;***** INTERRUPT SERVICE ROUTINE *****

ISR	;************************
	;*** ISR CONTEXT SAVE ***
	;************************

	bcf	INTCON,GIE	; disable all interrupts
	btfsc	INTCON,GIE	; assure interrupts are disabled
	goto	ISR
	movwf	W_TEMP		; context save: W
	swapf	STATUS,W	; context save: STATUS
	movwf	STATUS_TEMP	; context save
	clrf	STATUS		; bank 0, regardless of current bank
	movfw	PCLATH		; context save: PCLATH
	movwf	PCLATH_TEMP	; context save
	clrf	PCLATH		; page zero, regardless of current page
	bcf	STATUS,IRP	; return to bank 0
	movfw	FSR		; context save: FSR
	movwf	FSR_TEMP	; context save
	;*** context save done ***

	;**************************
	;*** ISR MAIN EXECUTION ***
	;**************************
	
	;*** determine origin of interrupt ***
	btfsc	PIR1,RCIF	; check for USART interrupt
	goto	_ISR_RS232	; if set, there was an USART interrupt
	goto	ISRend		; unexpected IRQ, terminate execution of ISR

	;******************************
	;*** RS232 DATA ACQUISITION ***
	;******************************
_ISR_RS232
	movfw	RCREG		; get RS232 data (first RX FIFO entry)
	movwf	RXreg		; store first data byte
	btfss	PIR1,RCIF	; check flag for second RX FIFO entry
	goto	_ISR_RS232_A	; no second byte received, branch
	movfw	RCREG		; get RS232 data (second RX FIFO entry)
	movwf	RXreg2		; store second data byte
	bsf	RX2flag		; set flag to indicate second byte received
_ISR_RS232_A
	bsf	RSflag		; enable RS232 data reception flag
	BANK1			;  (for display routine)
	bcf	PIE1,RCIE	; disable USART reception interrupt
	BANK0			;  (will be re-enabled in normal subroutine)
	;goto	_ISR_RS232end	; exit ISR

_ISR_RS232end
	;bcf	PIR1,RCIF	; cleared by hardware: USART RX interrupt flag
	;goto	ISRend		; terminate execution of ISR

	;*****************************************
	;*** ISR TERMINATION (CONTEXT RESTORE) ***
	;*****************************************

ISRend	movfw	FSR_TEMP	; context restore
	movwf	FSR		; context restore
	movfw	PCLATH_TEMP	; context restore
	movwf	PCLATH		; context restore
	swapf	STATUS_TEMP,W	; context restore
	movwf	STATUS		; context restore
	swapf	W_TEMP,F	; context restore
	swapf	W_TEMP,W	; context restore
	RETFIE			; enable global interrupt (INTCON,GIE)

;***** END OF INTERRUPT SERVICE ROUTINE *****


;************** MAIN **************

MAIN	
	clrf	INTCON		; reset interrupts (disable all)	
	LCDinit			; LCD initialization
	RS232init		; RS232 initialization

	;*** ENABLE INTERRUPTS ***
	movlw	b'11111000'
	andwf	INTCON,F	; clear all interrupt flags
	clrf	PIR1		; clear all interrupt flags
	clrf	PIR2		; clear all interrupt flags
	bsf	INTCON,GIE	; enable global interrupt

	clrf	FLAGreg		; initialize all flags

	LCDchar	'R'
	LCDchar	'S'
	LCDchar	'2'
	LCDchar	'3'
	LCDchar	'2'
	LCDchar	' '
	LCDchar	'C'
	LCDchar	'o'
	LCDchar	'm'
	LCDchar	'm'
	LCDchar	'u'
	LCDchar	'n'
	LCDchar	'i'
	LCDchar	'c'
	LCDchar	'a'
	LCDchar	'-'

	LCDline	2
	
	LCDchar	't'
	LCDchar	'i'
	LCDchar	'o'
	LCDchar	'n'
	LCDchar	' '
	LCDchar	'o'
	LCDchar	'n'
	LCDchar	' '
	LCDchar	'P'
	LCDchar	'I'
	LCDchar	'C'
	LCDchar	'1'
	LCDchar	'6'
	LCDchar	'F'
	LCDchar	'7'
	LCDchar	'7'
	
	;*** START-UP MESSAGE to RS232 ***
	; this is done by reading a look-up table
	; define amount of table items for start-up message
	#define	tab_size4 d'48'
	movlw	tab_size4	; store amount of table items in counter
	movwf	TEMP5
	; transmit message
_ILOOP1	movlw	HIGH WelcomeTable ; get correct page for PCLATH
	movwf	PCLATH		; prepare right page bits for table read
	movfw	TEMP5		; get actual count-down value
	sublw	tab_size4	; table offset: w = tab_size4 - TEMP6
	call	WelcomeTable	; call lookup table
	SENDw			; RS232 output
	decfsz	TEMP5,f		; decrement counter
	goto	_ILOOP1

	WAITX	0x1A,  b'00000111'	; wait some time

	; a little bit animation...
	SEND	'a'
	SEND	'n'
	SEND	'i'
	SEND	'm'
	SEND	'a'
	SEND	't'
	SEND	'i'
	SEND	'n'
	SEND	'g'
	SEND	' '
	SEND	'L'
	SEND	'C'
	SEND	'D'
	SEND	'.'
	SEND	'.'
	SEND	'.'
	SEND	CR		; Carriage Return
	SEND	LF		; Line Feed

	movlw	d'16'
	movwf	TEMP5
_SHL1	LCDcmd	LCDSL		; shift left LCD display content
	WAIT	0xC0
	decfsz	TEMP5,f
	goto	_SHL1

	; finally, reset/clear LCD
	LCDcmd	LCDCLR
	
	LCDchar	'R'
	LCDchar	'S'
	LCDchar	'2'
	LCDchar	'3'
	LCDchar	'2'
	LCDchar	' '
	LCDchar	'R'
	LCDchar	'e'
	LCDchar	'c'
	LCDchar	'e'
	LCDchar	'p'
	LCDchar	't'
	LCDchar	'i'
	LCDchar	'o'
	LCDchar	'n'
	LCDchar	':'

	LCDline	2
	
	LCDchar	'C'
	LCDchar	'h'
	LCDchar	'a'
	LCDchar	'r'
	LCD_DDAdr 0x47
	LCDchar	'V'
	LCDchar	'a'
	LCDchar	'l'
	LCDchar	'u'
	LCDchar	'e'

	SEND	'r'
	SEND	'e'
	SEND	'a'
	SEND	'd'
	SEND	'y'
	SEND	'.'
	SEND	'.'
	SEND	'.'
	SEND	CR		; Carriage Return
	SEND	LF		; Line Feed

	call	COUNTERinit	; initialize 24 bit counter

	;******************************
_MLOOP	btfsc	RSflag		; check RS232 data reception flag
	call	COUNTERinit	; reset 24 bit counter
	btfsc	RSflag		; check RS232 data reception flag
	call	RSservice	; if set, call RS232 echo & LCD display routine

	; status message wait counter
	decfsz	LOcnt,f		; decrement low byte of 24 bit counter
	goto	_MLOOP		; loop
	decfsz	MEDcnt,f	; decrement medium byte of 24 bit counter
	goto	_MLOOP		; loop
	decfsz	HIcnt,f		; decrement high byte of 24 bit counter
	goto	_MLOOP		; loop

	; now, all bytes of 24 bit counter are zero,
	; i.e. RS232 has been idle for approx. 2.75 s,
	; therefore, transmit now status message...

	; send status message
	SEND	'@'
	SEND	CR
	SEND	LF
	call	COUNTERinit	; initialize 24 bit counter
	goto	_MLOOP		; loop forever
	;******************************

	;ORG	0x230		; if necessary, move look-up tables

WelcomeTable
	addwf	PCL,F		; add offset to table base pointer
	retlw	CR
	retlw	LF
	DT	"Microchip PIC16F77 connected and stand-by..." ; create table
	retlw	CR
WTableEND retlw	LF
	IF (HIGH (WelcomeTable) != HIGH (WTableEND))
		ERROR "WelcomeTable hits page boundary!"
	ENDIF

	END

