Serial IO

The Bit Banging Way

There is an excellent tutorial here that I HIGHLY recommend going through. It will teach you how to do serial io with bit banging, which will give you a good background of how RS-232 (the serial protocol) works. At least read it. The code itself doesn't work straight out of the box on the PIC 18F452. There's one instruction I had to change, if I recall correctly, change every occurence of rrf to rrcf. It's just a change in PIC instruction names between generations.

Briefly, RS-232 sends bits with one start bit and one stop bit at +12V and -12V. It uses inverted logic, meaning the voltage goes low for a 1 and high for a 0. (a better explanation of RS-232 here) This is a problem if you're trying to go straight between the computer and the PIC (I'll get to the alternative later). The PIC is built to output using TTL, which is similar, but uses normal logic (not inverted) and goes between 0V and +5V. Luckily, the computer can read TTL. Unluckily, the computer will try to talk back in RS-232, and the 12V will fry your PIC if it's not insulated by a resistor. See Physical Computing or do a google to get the rating for the resistor you need between the computer's TX (shorthand for transfer) and the PIC's RX (receive). I can't remember it off the top of my head.

So, to get the PIC to talk to the computer directly using the scheme in the tutorial above, all you need to do is insulate the PIC's RX with a resistor and invert all the logic. To invert the logic change all the bsf's to bcf's and vice versa in the XMIT and RCV subroutines.

The Highway

This is the easy way to do serial communication with the PIC. It requires a MAX232, which is an integrated circuit that is available everywhere worthwhile for less than a dollar. It was very difficult for me to figure out how to wire it, so here's a bunch of pictures:

Overviewoverview of max232

A bunch from the sides
max232max232max232

The code I will show you here just does output from the PIC. I haven't needed input yet, so I haven't done it. It shouldn't be hard to add to this. This code will send the alphabet to the computer from the PIC.

Let's start with our LED Chaser and build on that skeleton. I did this so that the LED will keep blinking while we're working. This is helpful at this stage to debug and be sure the PIC is getting through its loop. It's also useful because you want to pause between sending each letter, otherwise they flood in at several hundred a second. The LED delays work nicely for that.

; Define Constants
COUNT1	equ	0x00
COUNT2	equ	0x01

;;Subroutines
Main
		bsf	PORTD,RD1	; turn off the LED
		call	Delay		; wait

		bcf	PORTD,RD1	; turn off the LED
		call	Delay		; wait
		goto	Main

Delay
		setf	COUNT1
		setf	COUNT2
Loop1	
                decfsz	COUNT1, F
		goto	Loop1
		setf	COUNT1
		decfsz	COUNT2, F
		goto	Loop1
		return

I think that's basically the LED Chaser, with the headers removed of course. See below for that.

We need to add one variable that will hold the letter we are sending to the computer:

char	equ 0x04

As for port setup, we're just blinking the LED, so setting port D all out will do:

	clrf	TRISD

Most of the rest of what we're going to do is by use of subroutines, so start adding subroutines below the Delay sub.

The first sub is very simple, it just sets char to the ASCII leter 'A'. We'll do this a lot, so we'll make it a sub to save memory.

Reset_char
                movlw   'A'
		movwf	char
		return

Before we go on, let's just add a line before the Main loop, and after the port setup that will initialize char to 'A'. Just call the Reset_char sub for this.

Next we need two more subroutines, one to initialize the Universal Asynchronous Receiver Transmitter, or UART. The UART controls the RX and TX pins to send the RS-232 signal.

UART Initialization

Initializing the UART has 5 steps. There are more advanced features too, but these work. For more information look at the datasheet at p. 174

  1. Set the Transmit Status and Control Register
  2. Set the Receive Status and Control Register
  3. Initialize the baud rate using the SPBRG baud rate generator
  4. Set the TX pin to output
  5. Clear the transmit interrupt

The Transmit Status and Control Register (TXSTA) lets you setup how the TX works, and also allows you to later check the status of the transmission. The TXSTA has 8 bits (see datasheet p. 168 for more info):

so the setting for the Transmit Status and Control Register will look like this:

		movlw	B'00100110'	; See datasheet p 168
		movwf	TXSTA

I'll gloss over the setting for the same register on the receive side. Suffice to say you need to set the first bit to enable the serial port.

		movlw	B'10000000'	; Turn serial port on.
		movwf	RCSTA

Now we set the baudrate. To do this we use the Baudrate Generator. You feed the baudrate generator a decimal number (gotten from the tables in the datasheet on pp. 171-173) and it sets the baudrate for you. What the baudrate is is determined by whether you set the high speed baudrate bit of TXSTA, which we did to gain accuracy. If you look on the tables on p. 171 of the DS you'll notice that there's a % error column. We want to get a 9600 baud, with the smallest margin of error. This means we set the high speed bit and give the baudrate generator (SPBRG) the decimal number 25

		movlw	D'25'		; Set baud rate to 9600 w/ 4 MHz clock. +.16% error
		movwf	SPBRG

Now we set the TX pin to output, just like we set the pins to output previously

		bcf	TRISC, TX	; Set TX to output

The last step of UART initialization is to clear the transmit interrupt. This would make it so when the PIC receives something it can branch from the current routine to deal with the transmission. We're not using this, but it's good to clear it nonetheless

		bcf	PIE1, TXIE	; clear transmit interrupt

Put all this together into a subroutine call UART_Init, and it should look like this:

UART_Init
		movlw	B'00100110'	; See datasheet p 168
		movwf	TXSTA
		movlw	B'10000000'	; Turn serial port on.
		movwf	RCSTA
		movlw	D'25'		; Set baud rate to 9600 w/ 4 MHz clock. +.16% error
		movwf	SPBRG

		bcf	TRISC, TX	; Set TX to output
		bcf	PIE1, TXIE	; clear transmit interrupt

		return

UART Transmission

Actually transmitting over the UART is easy. Basically you just move your number to the TXREG and the PIC sends it for you while the PIC can go on and do other things. The only catch is that you want to make sure the PIC has sent the previous byte, so you put a loop before the transmission to wait until the last one is done (if it isn't already). Note that this implementation assumes the byte to be transferred is in the the WREG.

UART_Put
		btfss 	PIR1, TXIF	; Wait for TXREG to be empty
		goto	UART_Put
		movwf 	TXREG		; Transmit byte in W
		return

Finishing Up

Now you've got all the subs you need. Put them after your main loop (or the PIC will run through them before it gets to Main) and call the UART_Init just before the Main loop, and after you set char.

Now we need to actually do the transfer. To go through the alphabet all we do is add one to our letter 'A'. If it's larger than 'z' (z is represented by a higher number than A in ASCII, 122 and 65 respectively) we reset back to 'A'.

Main	
		bsf	PORTD,RD1	; turn off the LED
		call	Delay		; wait
		
		;; This is the transmission
		movf	char, W
		call	UART_Put
		incf	char, F		; increment char
		movlw	'z'     	; reset char if greater than z
		cpfslt	char
		call    Reset_char

		bcf	PORTD,RD1	; turn off the LED
		call	Delay		; wait
		goto	Main

Schlack the whole thing together and you get the following. If you don't get it or I'm moving too fast, email me.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; rs232_test-1.asm
;	blinks and LED while sending the alphabet to a com port using RS-232
;	by Ian Smith-Heisters, v.1 complete on October 14th, 2004
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	title "RS232 with built in TX"
	List p=18f452,f=inhx32
	#include <p18f452.inc>

;The config aliases are already defined in the 18f452 header file.
	__CONFIG    _CONFIG1H, _OSCS_ON_1H & _XT_OSC_1H ; External Clock on OSC1 & OSC2
	__CONFIG    _CONFIG2L, _BOR_ON_2L & _BORV_20_2L & _PWRT_OFF_2L ; Brown out reset on at 2.0V, no power-up timer
	__CONFIG    _CONFIG2H, _WDT_OFF_2H & _WDTPS_128_2H ; watchdog off, postscaler count to 128
	__CONFIG    _CONFIG3H, _CCP2MX_ON_3H ; CCP2 pin Mux enabled. What is this?
	__CONFIG    _CONFIG4L, _STVR_ON_4L & _LVP_ON_4L & _DEBUG_OFF_4L ; Stack under/overflow reset on, LVP on, debug off
	__CONFIG    _CONFIG5L, _CP0_OFF_5L & _CP1_OFF_5L & _CP2_OFF_5L & _CP3_OFF_5L ; all protection off
	__CONFIG    _CONFIG5H, _CPB_OFF_5H & _CPD_OFF_5H
	__CONFIG    _CONFIG6L, _WRT0_OFF_6L & _WRT1_OFF_6L & _WRT2_OFF_6L & _WRT3_OFF_6L 
	__CONFIG    _CONFIG6H, _WRTC_OFF_6H & _WRTB_OFF_6H & _WRTD_OFF_6H
	__CONFIG    _CONFIG7L, _EBTR0_OFF_7L & _EBTR1_OFF_7L & _EBTR2_OFF_7L & _EBTR3_OFF_7L
	__CONFIG    _CONFIG7H, _EBTRB_OFF_7H

	org	0x0000

; Define Constants
COUNT1	equ	0x00
COUNT2	equ	0x01

; Define variables
char	equ 0x04

; Set port D to all output
	clrf	TRISD

; initialize variables
        call    Reset_char              ; sets char to 'A'

	call	UART_Init
Main	
		bsf	PORTD,RD1	; turn off the LED
		call	Delay		; wait
		
		movf	char, W
		call	UART_Put
		incf	char, F		; increment char
		movlw	'z'     	; reset char if greater than z
		cpfslt	char
		call    Reset_char

		bcf	PORTD,RD1	; turn off the LED
		call	Delay		; wait
		goto	Main

;; Subroutines
Reset_char
                movlw   'A'
		movwf	char
		return

Delay
		setf	COUNT1
		setf	COUNT2
Loop1	
                decfsz	COUNT1, F
		goto	Loop1
		setf	COUNT1
		decfsz	COUNT2, F
		goto	Loop1
		return

UART_Init
		movlw	B'00100110'	; See datasheet p 168
		movwf	TXSTA
		movlw	B'10000000'	; Turn serial port on.
		movwf	RCSTA
		movlw	D'25'		; Set baud rate to 9600 w/ 4 MHz clock. +.16% error
		movwf	SPBRG

		bcf	TRISC, TX	; Set TX to output
		bcf	PIE1, TXIE	; clear transmit interrupt

		return

UART_Put
		btfss 	PIR1, TXIF	; Wait for TXREG to be empty
		goto	UART_Put
		movwf 	TXREG		; Transmit byte in W
		return

	end