Wednesday, 30 November 2011

Serial/UART communication between two PICs (18F and 16F)

It's taken a long time and a lot of different approaches to get even simple serial communication between two PICs working. Perhaps hampered a little by using two different PICs (a 16F and an 18F series chip) but that really shouldn't make any difference. Serial/UART communication shouldn't be platform dependent!

We've tried serial/UART, SPI, I2C and even bit-banging and each time got different (non-working) results. It's too much to go into in this blog post, so we're just going to describe how we actually got a working prototype:

The first thing to note is that although we're using one of our favoured 18F4550 PICs, we couldn't use the USB  and UART together. So for this experiment, we're reading the data back off our 18F (master) chip via serial, using the excellent PICKit2 programmer and software.

In our first (failed) attempt at creating pic-to-pic communication, we had our 16F (slave) device monitoring it's input pins and simply broadcasting the results as an 8-byte message over UART. The idea was for the 18F (master) chip to read this serial stream at intervals, find the start of the message and decode the rest of it, so that it knows the state of the slave chip.

In theory this sounds fine. In practice, sending more than about 4 bytes to the 18F (master) chip caused it to lock up on the Hserin command. We even tried connecting the PICKit2 to the RX pin on the 18F and removed our slave chip altogether. Our tests confirmed the same thing happening - if we sent one or two bytes at a time to the 18F from the PICKit2 terminal, it successfully received them and sent a response message back. If we sent more than about five bytes at a time, the 18F simply stopped responding - apparently locking up at the point where it reads in the serial data
(we proved this by switching an LED on just before the Hserin command, and turning it off again immediately after).

So it appears that we have a set-up whereby we can receive data from our slave, one byte at a time, but the master can't simply "dip in" to a serial stream being "broadcast" by the slave. After reading through the datasheet, it seems that the 18F has only a two (or was it four) byte buffer so blasting more bytes into the buffer than it can handle is causing some sort of buffer overrun error.

We need some way of getting the master chip to ask for data, one byte at a time.
This is what we came up with:

The master chip sends a pin high then requests a byte from the serial buffer.
The slave chip monitors the state of the "handshaking" line. When the line goes high, and the previous line state is low, this represents a request from the master, so send one byte.
Because we're using hardware UART on the master chip, after the line goes high, the code waits until a byte has arrived in the serial buffer. Because the slave only sends a byte on a low-to-high transition on the handshaking pin, we don't have to worry about buffer under-runs or errors in the master chip.



Slave code
'4Mhz external clock
Define CONF_WORD = 0x3f31
Define CLOCK_FREQUENCY = 4

AllDigital
Config PORTB = Input
Config PORTD = Input
Config PORTC = Input
Config PORTA = Input

declarations:
       Dim linestate As Bit
       Dim lastlinestate As Bit
     
       Dim a1 As Byte
       Dim a2 As Byte
       Dim a3 As Byte
       Dim a4 As Byte
       Dim a5 As Byte
       Dim xorval As Byte
       Dim bytecounter As Byte
            
init:
       WaitMs 50
       Hseropen 9600
       WaitMs 50
            
       linestate = 0
       lastlinestate = 0
       bytecounter = 1
     
loop:
       'all the slave does is read a whole load of input pins
       'and write the response back to the master. The master chip
       'will decide which "row" is live, this chip just takes all
       'the "column" data and sends it back to the master.
     
       'RA0-5
       'RB1-7
       'RC0-5 (6&7 are TX/RX)
       'RD0-7
       'RE0-2
            
       'this should give us 6+7+6+8+3 = 30 inputs
       a1 = PORTA And 63 '(mask off bits 7+6)
       a2 = PORTB And 254 '(make off bit 0, handshake line)
       a3 = PORTC And 63
       a4 = PORTD
       a5 = PORTE And 7
                   
       'now stream the values as a response back to the slave
       'all messages begin 255,255 (since this sequence is not
       'possible in the input stream because we bitmask bytes 1,3,5)
     
       linestate = PORTB.0
       If linestate = 1 And lastlinestate = 0 Then
              'the host chip has asked for another byte of data
            
              'create the checksum value
              xorval = 0
              xorval = xorval Xor a1
              xorval = xorval Xor a2
              xorval = xorval Xor a3
              xorval = xorval Xor a4
              xorval = xorval Xor a5
              xorval = xorval And 127

              'decide which byte of the message to send
              '(the host has to ask for each message eight times for all 8 bytes)
              Select Case bytecounter
                     Case 1
                     Hserout 255
                     Case 2
                     Hserout 255
                     Case 3
                     Hserout a1
                     Case 4
                     Hserout a2
                     Case 5
                     Hserout a3
                     Case 6
                     Hserout a4
                     Case 7
                     Hserout a5
                     Case 8
                     Hserout xorval
                     bytecounter = 0
              EndSelect
            
              bytecounter = bytecounter + 1
       Endif
       lastlinestate = linestate
     
Goto loop
End




Master code

AllDigital

declarations:
       Dim a1 As Byte
       Dim a2 As Byte
       Symbol lineshake = PORTA.0
       Config PORTA.0 = Output
     
init:
       Low PORTA.0
       WaitMs 200
       Hseropen 9600
       WaitMs 200
     
loop:

       Gosub getnextbyte
       'two 255 = start of message
       Hserout 255
       Hserout 255
       Hserout a1
            
Goto loop
End

getnextbyte:
       'to get the next byte, we send the lineshaking
       'line high - this tells the slave to "release"
       'another byte. Once we have this byte, we drive
       'the lineshaking pin low to say we're done
       High lineshake
       Hserin a1
       Low lineshake
     
Return


After connecting up the slave to the master (the TX pin of the slave into the RX pin of the master, and RA.0 on the master to RB.0 on the slave) we did actually start receiving intelligible data from the master. So it looks like we've (finally) cracked pic-to-pic communication over serial.

It's probably a bit slower than it needs to be (since the master has to "poll" each byte from the slave) but it's fast enough for what we want - and once we crank the baud rate up from 9600 to about 57600, or even 115200, it should be more than quick enough to scan an entire game board (of not more than 16 x 28 = 448 squares) at a reasonable rate.