I’m almost done with what I’d hoped to do! I just finished soldering up the last of the five boards. I only have four Diavolinos, so I tested the last one by hooking it up to a regular Arduino. All it takes is two wires for clock and data for the I2C plus ground and 5V. OK – the button is another digital pin to the Arduino. I didn’t bother to hook that one up.
You can see the others in the background.
The other thing I wanted to contribute to the platform was a working library to talk to the UARTs and some simple example code showing how to use the library. I also wanted at least a basic UDP-like datagram protocol with checksum. I got most of that.
There were really only two routines I had to write. There’s a writeMsg() function that takes a buffer of payload bytes and packages up a message – sync byte, length, payload, checksum and sends that to the UART. And a poll received data function. I hoped to have that one run by a timer interrupt, but couldn’t get that to work. The application now has to call that routine frequently. The example program does it every 10ms. That routine reads bytes coming in and assembles and check messages. When it’s got a good one, it sets a flag so the main app can grab the message.
I also put a simple app together that loops reading both channels and periodically writing a random message (one of 7 canned strings) out both sides, and loaded on all the Diavolino/IR board combos. If you point either end of one at another which is connected serial to the IDE, it displays the message, who sent it, which side they sent it from, and which side it was received on. And if you push the button on the IR board it lights the Arduino standard LED. But its main claim to fame is showing how to use the send and receive routines. It’s far from perfect, and needs tuning (Tx vs Rx), but it’s a good start.
The biggest disappointment (besides not being able to get the time interrupt to work with the receive poll routine) is that I couldn’t get the two routines into the library and have them still work. It was just dumb C++ problems, and I wasn’t good enough to over come them. The example program has both routine in the main source file. Ugh. I hope somebody else can figure out how to put them in the lib.
I even wrote up a readme with most of the relevant info – see it below.
It’s great to be about done. I’ve been off for two weeks, but have done almost nothing but work on the boards and the software. Done is good.
W88MultiSerial library README ver 0.9 4/15/11 jw --------------------------------------------------- BACKGROUND ---------- This code is to allow an Arduino (or clone) to talk to the custom IR comms board used in the Workshop 88 Educube project. The comms board uses an NXP SC16IS752 chip with the following relevant features: - 2 independent UARTs - talks I2C to the host Arduino - UARTs have appropriate pulse shaping hardware to be used with IrDA IR transceivers such as the Vishay TFDU4101 on the board - UARTs each have a 64-byte receive and a 64 byte transmit FIFO - chip has 8 bits of General Purpose IO - the chip is capable of generating Tx and Rx interrupts, but we don't have interrupt lines available Hackstrich Labs is in the process of producing an Arduino shield which implements 2 serial ports using the SC16IS752. They have graciously made their code open-source, and that code has provided an excellent starting point for the W88 code. This includes initialization, register access, single-byte read and write functions, and a non-blocking "is anything available on receive" function. OVERVIEW -------- The example program IrBoard3.pde works with the W88 IR comms boards attached to Diavolino Arduino clone hosts. It's a suitable starting point for developing real applications, providing: - initialization of the UARTs for IrDA - a higher level function to write a message - a poll function that reads from both channels, assembles and checks messages, and provides notification when a valid message is available - access to a GPIO pin with an LED on the IR comms board - an application level read, write, and message handling loop structure The initial message structure provided is as follows: sync byte | length byte | variable number of data bytes | 2 byte checksum The sync byte is 0x88 (ooow!). Length is limited by a #defined buffer size of 70 bytes. The current checksum is the most basic sum-of-all-bytes-truncated-to-16-bits. Checksum includes the length byte, but not the sync byte. Zero length messages may not work. Timing is critical, particularly in light of the facts that the IR channels are necessarily half-duplex, and transmitted characters reliably appear in the receive buffer. This means the receive buffer on each UART MUST be flushed immediately after transmitting a message. If this is not done, the transmitted characters will be comingled with the received characters, invalidating the message checksum. The example program transmits once for every 10 reads. This timing tuning may not be optimal. There's also a hardware button on the IR board, connected to D10 on the Diavolino. Very basic, but works. I also wrote a unique identifier into EEPROM of each Diavolino. They somehow have to know who they are, and this one-byte value, 0-6 does that. It may also be useful for randomizing delays so the cubes don't end in a pathological lockstep mode not able to communicate. FUNCTIONS --------- The init is just begin() call for each channel, and a call to set one GPIO pin as output (for the LED). void writeMsg(byte chan, char len, unsigned char* buf) chan is 0 or 1 len is number of data bytes to send buf is a pointer to a buffer containing the data payload The function prepends the sync and length bytes, and computes and appends the 2 checksum bytes. It writes the message to the specified UART's transmit FIFO. void rxpoll() Call this often - the example program does it every 10 ms. It takes care of reading to exhaustion, resyncing using the sync byte, doing sanity check on length, and verifying the checksum. When a valid message has been received, it copies the data payload to msgbuf[channel]. To tell the main application when a message is available, it puts the length in msglen[channel]. To allow the main program to deal with the message before it gets overwritten by the next message, rxpoll will not construct a new message until it sees that the main program has set msglen[channel] back to zero. It is the obligation of the main program to do this. SAMPLE OUTPUT ------------- Output of example program - R|L msg means message received on right|left channel - B|b= means sent from the right|left side of device 2 (B) - . means chars were available - x means bad checksum - digit at end of message is a sequence number (0-9) to check for lost msgs I took a device on the battery pack and pointed each end of it at each end of the receiver. Output is from receiver to IDE serial monitor. IR Board Test I am device 4 ...R msg of len 14 b=Hello mom!0 ...R msg of len 27 b=Arduino clone Diavolino2 ...R msg of len 54 b=The quick brown fox jumps over the lazy dog's back3 ...R msg of len 14 B=Hello mom!7 .....L msg of len 15 b=hackerspace0 ....x.....L msg of len 13 B=Educubes!8 ...R msg of len 27 B=Arduino clone Diavolino9 ....L msg of len 15 hackerspapace0 STATE OF THE CODE ----------------- It all works, but isn't optimal yet. - both rxpoll() and writeMsg() should be in the library, but I couldn't get them to work when I put them there. Some programmer more facile with C++ should figure out how to put them in the lib. For now, you must include those functions in the file with loop(). Sorry about that. - It would be very nice if rxpoll could be hooked to a timer interrupt so the main line didn't have to mess with it. Using MsTimer2 I could call rxpoll, but it didn't work when called that way. I hope somebody smarter than me can fix that. - The reported length on received messages isn't always right, although there's never garbage at the end or anything. - rxpoll() could reassemble the next message in its private buffer and just not write to the public buffer until msglen was reset. This might throw away fewer messages. - There are functions like word length that are stubbed out in the lib that should be populated. But I hacked the begin() function so it does exactly the inits we need for IrDA, so that's a lower priority. - There should be some versioning of the lib .cpp and .h. - It should be put in github or some such. - Fixes for known problems should be reported to the original author. - The original code from Hackstrich Labs is in the "original" directory. - All remain bugs should be removed :) ...end...