;***************************************************************************
;
;	Dual RS232 Software Interface for PIC 16F77 V1.00
;	=================================================
;
;	written by Peter Luethi, 15.01.2005, Urdorf, Switzerland
;	http://www.electronic-engineering.ch
;	last update: 16.01.2005
;
;	V1.00:	Initial release (16.01.2005)
;
;	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:		two MAX 232 for two RS232 interfaces,
;					dot matrix LCD display
;	Code Size of entire Program:	approx. 733 instruction words
;	Acquisition Methodology:	SW-based RS232: Interrupt-based RS232
;					  data acquisition
;					HW-based RS232: Preemptive,
;					  interrupt-based RS232 data acquisition
;					with LCD display output and RS232 echo
;					during normal operation
;
;
;	ABSTRACT:
;	=========
;	Dual RS232 terminal: Two independent RS232 interfaces, with 
;	display of received ASCII characters and corresponding decimal
;	values on the dot matrix LCD. ASCII values entered on one terminal
;	window are transmitted by the first RS232 link to the controller,
;	displayed on the LCD, and further transmitted across the second
;	RS232 link to the other terminal window.
;	The microcontroller sends feedback of received characters back to
;	the issueing terminal window. When the PIC terminal is idle, it
;	sends a status message '@' to both terminals every 2.75 seconds.
;
;
;	DESCRIPTION:
;	============
;	Developed and tested on PIC16F77.
;	This program incorporates two independent RS232 interfaces, one
;	HW-based and one SW-based RS232 interface.
;	The HW-based RS232 interface uses the PIC-internal USART (and
;	interrupts), configured to standard 9600 baud @ 4 MHz PIC clock.
;	The SW-based RS232 interface is based on the module file m_rs096.asm,
;	which performs interrupt-based RS232 reception on PortB0 (INTCON,INTF)
;	at 9600 baud @ 4 MHz PIC clock.
;	
;	This program handles all aspects of RS232
;		Transmission (register TXD) and
;		Reception (register RXD) through interrupts (PortB0 IRQ).
;
;	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.
;
;	Program shows the implementation and function of the modules m_bank.asm,
;	m_wait.asm, m_lcd.asm, m_lcdv08.asm, and m_rs096.asm on the PIC16F77
;	or PIC16C74A.
;
;
;	IMPLEMENTED FEATURES:
;	=====================
;	- Dual RS232: Two independent RS232 interfaces.
;	- Bi-directional communication between microcontroller application
;	  and two remote RS232 clients by both, hardware- and software-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 *****

	#define	TXport	PORTA,0x00	; RS232 output port, could be
	#define	TXtris	TRISA,0x00	; any active push/pull port

	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

;***** 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'5'
	#define	RSflagSW FLAGreg,0x00	; SW-based RS232 data reception flag
	#define	RSflagHW FLAGreg,0x01	; HW-based RS232 data reception flag
	#define RX2flag	FLAGreg,0x02	; two RS232 bytes received (HW RS232)
	#define	LCDbusy	FLAGreg,0x03	; LCD busy flag declared within flag register
	#define	BCflag	FLAGreg,0x04	; blank checker for preceding zeros
	#define	LCDcflag FLAGreg,0x05	; LCD command/data flag

	LO	equ	BASE+d'6'	; binary to decimal output (8 bit)
	LO_TEMP	equ	BASE+d'7'

	TXD	equ	BASE+d'8'	; TX-Data register (SW-based RS232)
	RXD	equ	BASE+d'9'	; RX-Data register (SW-based RS232)

	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_rs096.asm"	; standard SW-based RS232
	#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 *****

RS232init2 macro		; for HW-based RS232 reception
	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

SEND2	macro	send_char
	movlw	send_char
	call	_RSxmit
	endm

SEND2w	macro
	call	_RSxmit
	endm

;***** SUB-ROUTINES *****

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			; for HW-based RS232 transmission
_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 ***
	; display on LCD
	LCD_DDAdr 0x07
	movfw	RXD
	LCDw			; ascii character output on LCD
	LCD_DDAdr 0x0D
	movfw	RXD
	movwf	LO
	LCDval_08		; numeric ascii value output
	; send echo back to PC
	SEND	TAB
	SEND	't'
	SEND	'r'
	SEND	'a'
	SEND	'n'
	SEND	's'
	SEND	'm'
	SEND	'i'
	SEND	't'
	SEND	't'
	SEND	'e'
	SEND	'd'	
	SEND	' '
	movfw	RXD		; get received RS232 data
	SENDw			; transmit across RS232 line
	movfw	RXD		; get received RS232 data
	SEND2w			; transmit across other RS232 line
	SEND	' '
	SEND	't'
	SEND	'o'
	SEND	' '
	SEND	'l'
	SEND	'i'
	SEND	'n'
	SEND	'k'
	SEND	' '
	SEND	'2'
	SEND	CR		; Carriage Return
	SEND	LF		; Line Feed
	; end of RS232 service (echo & display)
	bcf	RSflagSW	; reset RS232 data reception flag
	bsf	INTCON,INTE	; re-enable RB0/INT interrupt
	RETURN

RSservice2 ; *** 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	RSflagHW	; 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
	; display on LCD
	LCD_DDAdr 0x47
	movfw	RXtemp
	LCDw			; ascii character output on LCD
	LCD_DDAdr 0x4D
	movfw	RXtemp
	movwf	LO
	LCDval_08		; numeric ascii value output
	; send echo back to PC
	SEND2	TAB
	SEND2	't'
	SEND2	'r'
	SEND2	'a'
	SEND2	'n'
	SEND2	's'
	SEND2	'm'
	SEND2	'i'
	SEND2	't'
	SEND2	't'
	SEND2	'e'
	SEND2	'd'	
	SEND2	' '
	movfw	RXtemp		; retrieve value
	SEND2w			; transmit across RS232 line
	movfw	RXtemp		; retrieve value
	SENDw			; transmit across other RS232 line
	SEND2	' '
	SEND2	't'
	SEND2	'o'
	SEND2	' '
	SEND2	'l'
	SEND2	'i'
	SEND2	'n'
	SEND2	'k'
	SEND2	' '
	SEND2	'1'
	SEND2	CR		; Carriage Return
	SEND2	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	INTCON,INTF	; check for RB0/INT interrupt
	goto	_ISR_RS232_SW	; if set, there was a keypad stroke

	btfsc	PIR1,RCIF	; check for USART interrupt
	goto	_ISR_RS232_HW	; if set, there was an USART interrupt

	; catch-all
	goto	ISRend		; unexpected IRQ, terminate execution of ISR

	;***************************************
	;*** SW-BASED RS232 DATA ACQUISITION ***
	;***************************************
_ISR_RS232_SW
	; first, disable interrupt source
	bcf	INTCON,INTE	; disable RB0/INT interrupt
	; second, acquire RS232 data
	RECEIVE			; macro of RS232 software reception
	bsf	RSflagSW	; enable RS232 data reception flag
	goto	_ISR_RS232end	; terminate RS232 ISR properly

	;***********************************
	;*** CLEARING OF INTERRUPT FLAGS ***
	;***********************************
	; NOTE: Below, I only clear the interrupt flags! This does not
	; necessarily mean, that the interrupts are already re-enabled.
	; Basically, interrupt re-enabling is carried out at the end of
	; the corresponding service routine in normal operation mode.
	; The flag responsible for the current ISR call has to be cleared
	; to prevent recursive ISR calls. Other interrupt flags, activated
	; during execution of this ISR, will immediately be served upon
	; termination of the current ISR run.
_ISR_RS232error
	bsf	INTCON,INTE	; after error, re-enable IRQ already here
_ISR_RS232end
	bcf	INTCON,INTF	; clear RB0/INT interrupt flag
	goto	ISRend		; terminate execution of ISR

	;***************************************
	;*** HW-BASED RS232 DATA ACQUISITION ***
	;***************************************
_ISR_RS232_HW
	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_HW_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_HW_A
	bsf	RSflagHW	; 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_HW ; exit ISR

_ISR_RS232end_HW
	;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 (SW-based RS232)
	RS232init2		; RS232 initialization (HW-based RS232)

	;*** 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

	;*** 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_size1 d'92'
	movlw	tab_size1	; 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_size1	; table offset: w = tab_size4 - TEMP6
	call	WelcomeTable	; call lookup table
	movwf	TEMP4
	SENDw			; RS232 output (on SW-based RS232)
	movfw	TEMP4
	SEND2w			; RS232 output (on HW-based RS232)
	decfsz	TEMP5,f		; decrement counter
	goto	_ILOOP1

	LCDchar	'D'
	LCDchar	'u'
	LCDchar	'a'
	LCDchar	'l'
	LCDchar	' '
	LCDchar	'R'
	LCDchar	'S'
	LCDchar	'2'
	LCDchar	'3'
	LCDchar	'2'
	LCDchar	' '
	LCDchar	'R'
	LCDchar	'e'
	LCDchar	'c'
	LCDchar	'e'
	LCDchar	'p'
	LCDline	0x2
	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'
	WAITX	0x1A,  b'00000111'	; wait some time

	; finally, reset/clear LCD
	LCDcmd	LCDCLR
	LCDchar	'R'
	LCDchar	'X'
	LCDchar	'1'
	LCDchar	' '
	LCDchar	'C'
	LCDchar	'h'
	LCD_DDAdr 0x09		; goto specific LCD position
	LCDchar	'V'
	LCDchar	'a'
	LCDchar	'l'

	LCDline	0x2		; line 2 on LCD
	LCDchar	'R'
	LCDchar	'X'
	LCDchar	'2'
	LCDchar	' '
	LCDchar	'C'
	LCDchar	'h'
	LCD_DDAdr 0x49		; goto specific LCD position
	LCDchar	'V'
	LCDchar	'a'
	LCDchar	'l'

	; define amount of table items for start-up message
	#define	tab_size2 d'27'
	movlw	tab_size2	; store amount of table items in counter
	movwf	TEMP5
	; transmit message
_ILOOP2	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_size2	; table offset: w = tab_size4 - TEMP6
	call	ReadyTable	; call lookup table
	movwf	TEMP4
	SENDw			; RS232 output (on SW-based RS232)
	movfw	TEMP4
	SEND2w			; RS232 output (on HW-based RS232)
	decfsz	TEMP5,f		; decrement counter
	goto	_ILOOP2

	call	COUNTERinit	; initialize 24 bit counter

	;******************************
_MLOOP	; first software-based RS232 link (no USART, just IRQs)
	btfsc	RSflagSW	; check RS232 data reception flag
	call	COUNTERinit	; reset 24 bit counter
	btfsc	RSflagSW	; check RS232 data reception flag
	call	RSservice	; if set, call RS232 echo & LCD display routine

	; second hardware-based RS232 link (USART & IRQs)
	btfsc	RSflagHW	; check RS232 data reception flag
	call	COUNTERinit	; reset 24 bit counter
	btfsc	RSflagHW	; check RS232 data reception flag
	call	RSservice2	; if set, call RS232 echo & LCD display routine

	; status message wait counter for idle transmission (approx. 2.75 s)
	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	'@'		; to SW-based RS232
	SEND	CR
	SEND	LF
	SEND2	'@'		; to HW-based RS232
	SEND2	CR
	SEND2	LF
	call	COUNTERinit	; initialize 24 bit counter
	goto	_MLOOP		; loop forever
	;******************************
	
	;ORG	0x260		; if necessary, move look-up tables

WelcomeTable
	addwf	PCL,F		; add offset to table base pointer
	retlw	CR
	retlw	LF
	DT	"Dual RS232 Reception"
	retlw	CR
	retlw	LF
	DT	"===================="
	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

	;ORG	0x260		; if necessary, move look-up tables

ReadyTable
	addwf	PCL,F		; add offset to table base pointer
	DT	"ready for transmission..." ; create table
	retlw	CR
RTableEND retlw	LF
	IF (HIGH (ReadyTable) != HIGH (RTableEND))
		ERROR "ReadyTable hits page boundary!"
	ENDIF

	END

