;*************************************************************************** ; ; 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