the input buffer — this is the sender node. If the receive
node was designated as global (address 0), the program
ensures that the sender was the master; in my system only
the master node is allowed to send global commands.
Finally, the message number and packet length bytes are
pulled from the stream.
Since it is possible for a transmission to be interrupted
and then restarted, we must test every byte that comes in
for BIT7 being set. By doing this check, we can always
re-sync the node with the start of a new header.
If the command or request includes data, the packet
length byte will be one or greater. I don’t expect to have
long packets in my home control system so the buffers are
fairly small. Here’s how we receive any data bytes.
idx = 0
DO WHILE idx < packLen
tmpB1 = RX_BYTE
IF tmpB1.7 = 1 THEN
rxNode = tmpB1
fifo(idx) = tmpB1
As above, each new byte is checked to ensure it’s not
a header start byte; if not, it gets moved into a temporary
array called fifo().
I know what you’re thinking: “If we can’t use BIT7, how
do we transmit values greater than 127?” We’re going to
borrow another strategy from MIDI and use two bytes: The
first byte will contain the lower seven bits of the eight-byte
value and the second byte will hold BIT7. Remember, we
don’t always need all eight bits for a given command, so we
only use this scheme when an eight-bit value is required.
After receiving the packet and any data bytes, we will
use a simple routing section to process the incoming
transmission. By doing this, we end up simplifying the
IF msgNum = QRY_REQ THEN Unit_Acknowledge
IF msgNum = DEV_RST THEN Device_Reset
IF msgNum = SET_BIT THEN Set_One_Bit
IF msgNum = GET_BIT THEN Get_One_Bit
IF msgNum = WR_PORT THEN Write_Port
IF msgNum = RD_PORT THEN Read_Port
‘ if we get here, message is not used by this node
msgNum = MSG_NAK
packLen = 0
No mystery here: If the message is known to be used by
this node, then the program is routed to the appropriate handler, otherwise the response MSG_NAK is returned to the
sender. Since we’re now dealing with messages, let’s have a
look at what’s defined and explain the logic behind them.
The first message, QRY_REQ, is used by the sender to
“ping” the receiver; if the receiver is present then it
responds with QRY_ACK. The next four messages are used
to respond to commands or requests for data from the
receiver. If node is able to complete a request and there is
no data to be returned, then it will respond with MSG_ACK.
If the message sent isn’t used by the node, then the
response is MSG_NAK. If a valid message is sent with bad
data (e.g., a bad port number), then the response will be
MSG_FAIL. The final message in this lower group,
DEV_RST, will usually be issued by the master to tell a node
to reset itself.
For a simple I/O node, I’ve defined three sets of commands: one for bit-level control, one for port-level control,
and a third for setting values (called channels) within the
program space. The set and write commands will respond
with MSG_ACK, MSG_NAK, or MSG_FAIL as appropriate.
The get and read commands have dedicated responses for
the return data; the logic being this aids the “sender” side
of the exchange when a lot of packets are flying around.
Let’s have a look at a few of the handlers.
msgNum = QRY_ACK
packLen = 0
The Unit_Acknowledge handler is the simplest: it
sets the message to QRY_ACK, the return packet length to
zero, and then sends the reply. The reason for this process
is to allow a master to poll all the expected slave devices
to ensure that they’re actually online; there is no reason
for sending command messages to a node that is not
Now for something a little more interesting. We’ll
accept a level for one of the I/O pins on the node. I
happened to find a 10-segment bar-graph LED in my junk
drawer so I soldered that onto the PCB. With just 10 LEDs
on the node, the handler will only accept bit numbers
between 0 (on RB.0) and 9 (on RC.1) — if you use more
outputs, be sure to adjust the code accordingly.
January 2008 17