PIC16C84 - Outputting to a Serial Device

copyright, Peter H. Anderson, Dept of Electrical Engineering,
Morgan State University, Baltimore, MD 21239, June 7, '97


Introduction

This discussion deals with interfacing a PIC16C84 device with a serial device. It is limited to the PIC transferring data to a serial receiver. The receiver may be a PC COM port, a "dumb" terminal, a serial LCD Backpack or a serial printer.

Such interfacing is useful in displaying screen messages or prompts to the user, in displaying quantities or transferring the content of data logger's EEPROM to a PC.

Typically, the home experimenter doesn't have the resources to purchase an emulator. The ability to interface with a serial device gives you the ability to transfer a register of interest to the w register and displaying it on a terminal or LCD.

A Few Words about the RS232 EIA Interface.

In sending a byte to a terminal the sequence is to send the start bit, fllowed by each of the eight bits in turn, beginning with the least significant bit, followed by a stop bit.

In the following example, the letter "A" (0100 0000) is sent to the terminal.

	START	0123 4567 STOP
	0	0000 0010 1

Note that a start bit is a logic zero and a stop bit is a logic one.

Thus, on receipt of the start bit, the terminal sees a logic one (from the stop bit of the previous character to zero transition.

Each bit occupies an equal time slot. For example, at 9600 Buad, each time slot is 1/9600 = 104.167 usecs. At 2400 baud, each is four times as long or 416.67 usecs. The stop bit may be any length longer than one bit time.

An ASCII character occupies the lower seven bits. The highest bit, bit 7 may be used for parity checking. In this discussion, parity is not used. Rather the bit is always 0.

The RS232 EIA logic levels are different than TTL. A logic one is less than -3.0 V and a logic zero is greater than +3.0.

Thus, in interfacing with a PIC, one might use an EIA driver IC which converts a TTL logic one (greater than 2.8 V) to an EIA logic one (less than -3.0 V) and a TTL logic zero (less than 0.4) to an EIA logic zero (greater than +3.0). Typical devices are the 1488 and Maxim's 230 series.

However, over they years designers considered the negative supply to be an added expense and thus many transmitting devices were designed to send a logic one as near ground and a logic zero as greater than +3.0. Thus, most serial receivers will respond to near ground and greater than +3.0. Note that this is pretty close to TTL levels except the states are inverted.

Therefore, in this application, no EIA line driver is used. Rather the TTL is sent inverted. Not using an EIA driver may limit the distance between the PIC and the terminal. However, I have used the direct connection with no problems at distances of four feet.

Program SERIAL_1.ASM.

All this routines does is to continually send the character "7" to the terminal every 250 msecs.

Note that in SEROUT a counter is set to nine (start bit plus eight data bits). The start bit (logic zero) is implemented by clearing the carry bit.

In SEROUT1, the carry bit is checked and either a logic one or zero is sent. Note that is is here that I implemented the inversion discussed above; when a logic one should be sent (if an EIA driver was used), I really send a zero.

Continuing, the data is shifted to the right, causing the least significant bit to shift into the carry which then determines the state of the TX bit on PORTB. This continues until the counter is decremented to zero, and the stop bit is then sent. Note that it is inverted as I am not using an EIA driver.

Note that the time from beginning SEROUT1 to again beginning it is 416 instructions;

	1 + 1 + 1 + 1 + 1 + SEROUT_TIME * (1 + 2) + 1 + 1 + 2
or	9 + 136 * 3 = 417

You can easily calculate the loop constant for other baud rates. For example; for 9600 Baud;

	t = 1/9600 = 104 instructions
	SEROUT_TIME = (104 - 9) / 3 = 31.67 or 32

By adding NOP instruction outside the loop, the timing may be made precise. However, one or two microseconds out of 104 usecs over ten bits certainly isn't going to cause a problem.

; SERIAL_1.ASM
;
;
; Continually sends the character '7' to the terminal at 2400 Baud.  
; 250 msec delay.
;
; PORTB2 (terminal 8) ------------------ RX on terminal
;
; Peter H. Anderson, MSU, May 29, '97
;

	LIST p=16c84
#include <p16c84.inc>	; 
	__CONFIG 11h

CONSTANT 	TX=2	; PORTB bit used for TX

SEROUT_DATA	EQU 0CH
SEROUT_LOOP	EQU 0DH
SEROUT_TIME	EQU 0EH

DELAY_MS_LOOP	EQU 10H
DELAY_100MS_LOOP EQU 11H

	ORG 000H	;program code to start at 000H

MAIN:

	BCF STATUS, RP1
	BSF STATUS, RP0		; switch to bank 1 and
				; make all bits on Portb outputs

	BCF TRISB, TX		; make TX an output

	BCF STATUS, RP0		; back to data bank 0

TOP:
	MOVLW	.7
	ADDLW	'0'		; character 7
	CALL OUT_SER_CHAR	; output to terminal
	CALL DELAY_250_MS
	GOTO TOP
;;;;;;	

OUT_SER_CHAR:	; transmits content of W at 2400 Baud
	MOVWF	SEROUT_DATA
	MOVLW 	.9
	MOVWF	SEROUT_LOOP
	BCF	STATUS, C	; set C to 0, start bit

SEROUT1:
	BTFSC	STATUS, C				;1 cycles
	BCF	PORTB, TX	; send a one		
	BTFSS	STATUS, C				;1
	BSF	PORTB, TX	; or a zero		;1

	MOVLW   .136					;1
        MOVWF	SEROUT_TIME     ; bit time delay, 408uS at 2400 baud ;1
SEROUT2:
	DECFSZ  SEROUT_TIME, F				;1 cycles in loop
        GOTO SEROUT2					;2 cycles in loop
        NOP						;1
	
	RRF	SEROUT_DATA, F	; least sign bit now in C	;1
	DECFSZ	SEROUT_LOOP, F	; does not affect status	;1
	GOTO 	SEROUT1		; next character		;2

	BCF	PORTB, TX	; send stop bit
	MOVLW   .136
        MOVWF	SEROUT_TIME    	; bit time delay, 408uS at 2400 baud
SEROUT3:
	DECFSZ  SEROUT_TIME, F
        GOTO SEROUT3
        NOP

	RETURN

DELAY_250_MS:			; provides 250 ms loop
	MOVLW	.250
	MOVWF	DELAY_100MS_LOOP
DELAY_MS_1
	MOVLW	.110	; close to 1.0 msec delay when set to .110
	MOVWF 	DELAY_MS_LOOP
DELAY_MS_2
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	DECFSZ	DELAY_MS_LOOP, F 
	GOTO DELAY_MS_2
	DECFSZ  DELAY_100MS_LOOP, F
	GOTO DELAY_MS_1
	
	RETURN
        
	END

Program SERIAL_2.ASM.

This program includes the OUT_SER_CHAR routine discussed above. Any character may be displayed on the terminal by calling OUT_SER_CHAR with the character in the W register.

In addition, OUT_1_DIG converts a single digit to a character and displays it . OUT_2_DIG first converts the high nibble to a character and displays it, followed by the low nibble. Note that both of these routines are capable of printing the digits as hexadecimal characters.

That is, if 1EH is passed to OUT_2_DIG, it will be displayed as "1E". If the user desires the quantity to be displayed in decimal, the quantity 1E must be first converted to decimal and the two nibble decimal quantity, in this case; 30, is then passed to OUT_2_DIG.

CONVERT_TO_BCD is provided to convert a byte in natural binary to BCD. This is limited to quanties in the range of 0 to 255.

In SERIAL_2.ASM, the quantity "1E" is converted to BCD and displayed as 030 on the terminal followed by a "new line".

; SERIAL_2.ASM
;
; 
;
; PORTB2 (terminal 8) ------------------ RX on terminal
; 
; Peter H. Anderson, MSU, May 29, '97
;

	LIST p=16c84
#include <c:\mplab\p16c84.inc>	
	__CONFIG 11h

CONSTANT TX = 2		; bit on PORTB	

CONSTANT VARS=0CH	; location of first variable

BCD_HI	EQU VARS+0
BCD_LO  EQU VARS+1

SEROUT_LOOP	EQU VARS+2
SEROUT_TIME	EQU VARS+3

TEMP 	EQU VARS+4
TEMP1 	EQU VARS+5

;;;;;;;

	ORG 000H

MAIN:

	BCF STATUS, RP1
	BSF STATUS, RP0		; switch to bank 1 and
				; make all bits on Portb outputs

	CLRF TRISB		; make all PortB bits outputs

	BCF STATUS, RP0		; back to data bank 0
TOP:
	; in this routine, i simply used a data value of 1E
	; however, this data might be the result of a temperature
	; measurement, data in eeprom or any other variable you
	; wish to display
	MOVLW 1EH		; this is raw data byte to be displayed
	CALL CONVERT_TO_BCD
	MOVF BCD_HI, W
	CALL OUT_1_DIG		; output low byte only
	MOVF BCD_LO, W
	CALL OUT_2_DIG		; output high and low nibbles
	CALL OUT_CR_LF		; followed by a CR and line feed
DONE:
	GOTO DONE:

;;;;; Subroutines

CONVERT_TO_BCD:
; takes natural 8-bit natural binary quantity in W and converts to
; to BCD.  result is returned in BCD_HI and BCD_LO.  W is destroyed
; requires variables TEMP and TEMP1

	MOVWF TEMP		; save value
	CLRF BCD_HI
	CLRF BCD_LO

HUNDS:	; determine the number of 100s	
	MOVLW .100 
	SUBWF TEMP, W		; W = TEMP - 100
	BTFSS STATUS, C 	; then W >= 0, if TEMP >= 100
	GOTO TENS		; TEMP < 100
	
	MOVWF TEMP		; save the result
	INCF BCD_HI, F
	MOVF TEMP, F
	BTFSC STATUS, Z		; if zero
	GOTO TENS
	GOTO HUNDS
	
TENS:	; determine the number of tens
	MOVLW .10 
	SUBWF TEMP, W		; W = TEMP - 10
	BTFSS STATUS, C 	; then W >= 0, if TEMP >= 10
	GOTO TENS_1		; TEMP < 10
	
	MOVWF TEMP		; save the result
	INCF BCD_LO, F
	MOVF TEMP, F
	BTFSC STATUS, Z		; test for zero
	GOTO TENS_1
	GOTO TENS
TENS_1:
	SWAPF BCD_LO, F		; tens now in high nibble

UNITS:	; determine number of untis
	MOVLW .1 
	SUBWF TEMP, W		; W = TEMP - 1
	BTFSS STATUS, C 	; then W >= 0, or TEMP >= 1
	GOTO UNITS_1		; TEMP < 1
	
	MOVWF TEMP		; save the result
	INCF BCD_LO, F
	GOTO UNITS	

UNITS_1:
	RETURN

;;;;;

OUT_2_DIG:
	; outputs byte in W.  high nibble and then low nibble to serial 
	; terminal uses variable TEMP1.  W is destroyed.

	MOVWF TEMP1	; save to TEMP
	SWAPF TEMP1, W	; high nibble now in low nibble of W
	CALL OUT_1_DIG	; convert high nibble to a charcter and output
	MOVF TEMP1, W	; low nibble
	CALL OUT_1_DIG	; convert low nibble to character and output
	RETURN

;;;;;

OUT_1_DIG:
	; converts numeric quantity in low byte of W to a hex character
	; outputs to serial port.  W is destroyed.  Uses variable TEMP
	ANDLW 0F	; low nibble
	MOVWF TEMP	; copy to TEMP
	SUBLW .9	; W = 9 - TEMP		
	BTFSS STATUS, C	; TEMP > 9
	GOTO ALPHA	 

NUMERIC:
	MOVF TEMP, W	; get the original
	ADDLW '0'	; add character 0
	GOTO OUT_1_DIG_1

ALPHA:
	MOVF TEMP, W
	ADDLW '0'+7	; for 'A', 'B', 'C', 'D', 'E' and 'F'
	
OUT_1_DIG_1:

	CALL OUT_SER_CHAR
	RETURN

;;;;;;;

OUT_CR_LF	; outputs CR and LF, destroys W
	MOVLW 0AH
	CALL OUT_SER_CHAR
	MOVLW 0DH
	CALL OUT_SER_CHAR
	RETURN				

;;;;;;

OUT_SER_CHAR: 	; outputs character in W on serial port.  Uses variables 
		;TEMP, SEROUT_LOOP and SEROUT_TIME,  destroys W.

	MOVWF TEMP

	MOVLW 	.9
	MOVWF	SEROUT_LOOP
	BCF	STATUS, C	; set C to 0, start bit

SEROUT1:
	BTFSC	STATUS, C
	BCF	PORTB, TX   	; send a one.  note inversion.
	BTFSS	STATUS, C
	BSF	PORTB, TX    	; or a zero.  note inversion

	MOVLW   .135
        MOVWF	SEROUT_TIME     ; bit time delay, 416 uS at 2400 baud
SEROUT2:
	DECFSZ  SEROUT_TIME, F
        GOTO SEROUT2
        NOP
	
	RRF	TEMP, F		; least sign bit now in C
	DECFSZ	SEROUT_LOOP, F	; does not affect status
	GOTO 	SEROUT1		; next character

	BCF	PORTB, TX	; send stop bit
	MOVLW   .135
        MOVWF	SEROUT_TIME    	; bit time delay, 416 uS at 2400 baud
SEROUT3:
	DECFSZ  SEROUT_TIME, F
        GOTO SEROUT3
        NOP

	RETURN

	END