Something useful with a PIC: serial line communication with the outside
05 Sep 2008It's been a while since I've touched my k8048 PIC programmer board. This week I put it on my desk again and hooked it up. The plan was to play around with the builtin USART of my 16F627A.
Because a PIC is difficult (if not impossible) to debug in hardware, I decided to experiment in software first using a simulator. On Ubuntu, the best simulator is gpsim. It can simulate the USART inside the PIC and allows you to hook up a virtual seriele console to it, to see if things work as expected. There is also a 7-segment led display, a scope and all kinds of other plugins you can use.
I write all the programs in assembler and use gpasm (from gputils) to assemble it. The reason for this is that I like to be close to the hardware. After all, it's the hardware part I want to understand and experiment with, so no unnecessary layers of cruft in between!
The program I wrote is very basic. It sets up the USART for asynchronous serial communication at 9600 baud (8N1) and then starts sending the alphabet over and over again.
Output looks like this:
BCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKL...
(Notice how it didn't start with 'A' ? Thats a bug... keep reading)
Here's the code
#include "p16f627a.inc"
#define COUNTER 0x20 ; allocate a var in free space at location 0x20
#define STARTCHAR 0x41 ; first character will be 0x41 which is 'A'
#define ENDCHAR 0x5A ; last character will be 'Z'
; according to the datasheet, both the 1st and 2nd bit of TRISB need to be set to 1, which makes them both inputs
; this doesn't work. the 2nd bit needs to be set to 0 because it is TX and needs to send bits out.
BSF STATUS, 5 ;
BSF TRISB, 1 ; set bit 1 on TRISB
BCF TRISB, 2 ; clear bit 2 on TRISB
BCF STATUS, 5
; set baudrate to 9600baud. For a 20MHz Fosc, this is value 32 == 0x20
MOVLW 0x20
BSF STATUS, 5
MOVWF SPBRG
BCF STATUS, 5
BSF STATUS, 5
BCF TXSTA, SYNC ; clear SYNC
BCF STATUS, 5
BSF RCSTA, SPEN ; enable serial port
BSF STATUS, 5
BSF TXSTA, TXEN ; set TXEN
BCF STATUS, 5
; now the real code begins.
; a variable COUNTER will contain values from 0 to ENDCHAR-STARTCHAR
; the register W will hold the actual byte to be sent over the serial line (W = COUNTER + STARTCHAR)
; if W was ENDCHAR, we reset the COUNTER to 0 and start the whole thing again.
reset: MOVLW 0x0 ; counter = 0
MOVWF COUNTER
start: MOVF COUNTER, 0 ; w = counter
ADDLW STARTCHAR ; w+= startchar
MOVWF TXREG ; write w over serial line
; some hackery to check if W == ENDCHAR: W-ENDCHAR should be 0
SUBLW ENDCHAR ; w -= endchar
BTFSC STATUS, Z ; if w == 0
GOTO reset ; reset
INCF COUNTER, 1 ; else, increment counter
; this tight txloop makes sure that are clear to send the next byte.
; remember that sending bytes over serial line is slow and it is done in the background
; if we don't wait untill the first byte is sent, to send the second byte, then the transmission will be garbled
BSF STATUS, 5
txloop: BTFSS TXSTA, TRMT
GOTO txloop
BCF STATUS, 5
GOTO start
END
And I compiled it like this:
gpasm -p 16f627a test.asm
Which produces a "test.cod" file.
I then used the following gpsim "startup command file" (named it env.conf):
# create a new node 'n'
node n
# load the gpsim modules library. For some reason I can't just load it from /usr/lib
# so to get this working, I had to issue a 'ln -s /usr/lib/libgpsim_modules.so.0 /tmp/libgpsim_modules.so'
module lib /tmp/libgpsim_modules.so
# create an USART called 'U1'
module load usart U1
# attach both the TX bit of the PIC 'portb2' (RB2) and the RX pin of the U1 USART
# to the node n. This means: connect them together
attach n pin(portb2) U1.RXPIN
# make the console from the U1 USART do something. In our case, it will display the characters submitted by the PIC
U1.console = true
# tell the builtin scope in gpsim to monitor portb2
# (in gpsim: Windows -> Scope)
scope.ch0="portb2"
And launched gpsim with:
gpsim -c env.conf -s test.cod
Screenshot of the scope in gpsim:
And the usart console:
Careful readers will see that the alphabet sequence does not start with 'A'. I have been looking at the problem very closely and I can't find out why the 'A' is not showing up. All bytes transmitted start with a start-bit and end with a stop-bit, except for the first character transmitted. So you can step through the program and observe how 'A' gets transmitted by using the scope. But nothing arrived in the console. Then 'B' gets sent and everything goes as planned: the startbit, data and stopbits are transmitted and it shows up in the console.
I haven't figured out yet why this happens. So dear lazyweb, in case you have any pointers as to why this is (not) happening, please let me know.
Some recent links I didn't read through yet completely:
http://zedshaw.com/rants/the_freehackers_union.html
http://www.parallax.com/