Close

Implementing bi-directional comms

A project log for Tangible programming

An Arduino-based tangible programming effort

amosAmos 01/14/2019 at 03:440 Comments

Last time I implemented a simple serial communications protocol, allowing the Master Controller (MC) to send commands to connected blocks. While this works well as it is, the MC has no idea which modules are connected, nor their topology. I need a way for the modules to identify themselves to the MC. The serial protocol as currently implemented only listens on one port and sends on the other - now I need to add the ability to reverse the direction on both ports. In order to do this, I need to add some more information to the data packets that are being sent.

At the moment, the data packet simply contains a destination address and a command value. While this worked for the blinky light demo, it really isn't good enough. At a minimum we need to know who sent the message, especially if the message is a request for identification or configuration data. So a source address byte needs to be included in the packet. Further, if the MC is querying the blocks, the blocks need to respond with their function. This could be done by using a special command code, but some blocks will also need to send extra information, such as their identifier and/or current value. For example, a variable block would need to convert not just that it is a variable, but which variable it represents and the value or expression that it contains. For now, I'll simply variable expressions to be a number (or variable), an operator (+, -, *, /) and another number or variable. So a variable block would need to send the MC up to four values - one for the variable identifier and up to three for an expression assignment.

With all the above in mind, I decided the data format should be:

ByteValueDescription
1Destination Address0 == the current block, 254 == MC, 255 == Broadcast
2Source AddressSet to 0 when sending, this value is incremented with each hop
3CommandCommand or response code
4Parameter 1Optional parameter
5Parameter 2Optional parameter
6Parameter 3Optional parameter
7Parameter 4Optional parameter

In the last log I explained how the destination address is used in this protocol. The destination byte is effectively the number of hops a message needs to traverse before it reaches its destination. At each hop, the destination value is decremented, so when it reaches 0, the packet has reached its target. I use a similar approach for the source value but in reverse. At each hop, the source value is incremented, so when the packet reaches its destination, the source value will be the number of hops the message has traversed. Doing this will allow the MC to locate and identify blocks dynamically - simply send a "who are you?" message to all blocks and as each block responds, the source value of each packet will identify the position in the chain of the sending block.

Command values will be defined as needed. In the previous demo, the commands were LEDS_OFF (turn off the LED), RED_ON (turn the red LED on) and GREEN_ON (turn the green LED on). While being able to toggle the block's LED is great, we will need some more commands, the first two I will define are WHO_IS_THERE, which will ask all the connected blocks to identify themselves, and BLOCK_ID, which is the response a block sends along with one or more parameter values identifying the block and its configuration. The parameter values are optional and their use will vary depending on the command being sent. (To keep things simple for now, all data packets will include all seven bytes - a later enhancement to the protocol would be to include a data length in the packet.)

To make all this work, the main loop() will need to be modified. The demo code looped, waiting for incoming data on the hardware serial port and when it received a byte (the destination address), it looped waiting for a command byte and then acted on it. While this worked for the blinky lights demo, it is not really ideal and have several problems. For one, it is only listening for transmissions on one port, but we now need to listen for incoming data on both the hardware and software serial ports. Another problem is that it blocks while waiting for data after receiving the initial byte in a transmission - if data is coming in on both ports simultaneously this runs the risk of dropping data on one of the ports. Yet another issue if how do we really know where the start of a transmission is? What if a byte somehow gets lost in transit? Or if a block starts sending before the next block has fully booted?

To solve this last issue of where a transmission begins, a begin transmission value should probably be used. Then the loop could watch for a BEGIN_TRANS value on a port and start collecting the data. This should help keep message synchronised somewhat. As for the other issues, instead of grabbing an entire message at once, I use two variables to store an in progress message (one for upstream and one downstream) and as a byte is received on a serial port, the appropriate message is updated until it has been completely received. When the message is complete it can be acted upon. In the next project log I will start writing the code to implement this.

Discussions