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
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.