In embedded control systems there is often a need to distribute processing over a many processors at different locations. The advantages of this architecture are many and include:
Described here is a software library I have developed that allows multiprocessor control and sensor networks on low cost microcontrollers using the micro's UART for the lower network layer. It has been ported to Motorola 6805s, 08s, Intel 8051s and x86s, and Hatachi microprocessors/controllers. A multitasking OS is not required for its operation and it forms the basis of a simple non-preemptive multitasking system on its own.
System level software comprises only about 2k of code on each micro. The network is not peer to peer. It has one assigned host through which all messages must be passed. All communication data is transmitted and received at interrupt level into and out of FIFOs. Data received in the receiver buffer is processed by protocol state machines. The protocol checks packet integrity using CCITT-16 and automatically retransmits corrupted packets as required.
Packets are variable length up to 256 bytes per packet and have the following form:
typedef struct tagPACKET
{
unsigned char ucLength; // packet length in bytes not including the length byte
unsigned char ucUnit; // unit address for which this packet is from or directed to
unsigned char ucCode; // function code
unsigned char ucaData[MAX_DATA]; // optional variable length data meaning defined by function code
} PACKET;
Two priority queues are maintained on each node for data communication. One for outgoing messages, a second for the data portion of valid incoming messages. The application program communicates with all nodes through these queues. Incoming messages are parsed, then directed to call back functions within the application program. The parsing uses address to function assignments that are made at system startup.
The protocol process is a state machine that handles on a host, poll requests to slave nodes, slave node status packets, slave node responses and messages to slave units. On polls from a host the protocol process on a slave node handles responding with a queued packet if one is ready and waiting, or responding with a status response. The protocol process in each case handles packet acknowledgments and retransmissions on not acknowledgments. It has a built in timeout when waiting for reception of packets to account for down network nodes and the rare packet collision. It must be called from within the application programs at reqular intervals at a rate adequate to keep up with message traffic.
A transmit function inserts sync characters and a CRC onto a packet during transmission. The receive function is another state machine that synchronizes on the sync header, assembles a packet, and flags the protocol process if a packet is assembled correctly, is for the node, and has a valid CRC.
A receive and a transmit buffer (FIFOs) are filled and emptied at interrupt level. A common microprocessor such as the Motorola 6805, 6811, 6808, and the Intel 8051 series can easily drive its UART at 115 kbaud. RS-485 drivers have been used in harsh noise environments with this protocol at distances of a few hundered feet with negligible retransmissions of packets.
A full library of functions have been developed to facilitate application development. What follows is a brief overview of the library.
The library is build with the following modules:
netExecute.cThese modules implement the following and other data communication functions:
Host or slave node function to initialize the network functions, UART hardware, allocate memory and set up interrupts.
Host or slave node function to close network functions, stop the UART related interrupts and free memory.
This function is used on the host (master) and slave nodes. It allocates an internal data structure and assigns it to a network address. The address ucUnit is linked to the call back function fCallBack. Incoming messages from the network are waiting in an incoming message queue. The netExecute function uses this structure to pass a packet's information to this assigned function. The protocol uses the bEnable value to determine if a slave network node ever needs to be constantly polled for status information.
Host function to mark a remote node as inactive for polling of status information.
Host function to mark a remote slave node as active for the polling of status information.
Host function to re-activate all enabled nodes for polling.
Host function to return TRUE if a the unit address ucUnit is active for polling. The protocol removes slave units it can not communicate with after a predetermined number of retries from the active list.
Place the packet pointed to by pPacket into the queue pointed to by pQueue with a priority of ucPriority. The function returns with TRUE if the packet was successfully queued.
Returns a pointer to the next queued packet in the queue pointed to by pQueue. If no packets are queued it returns NULL. ucPriority is set to the priority value of the queued packet.
The host or slave node network process state machine. This must be called at regular intervals to process data in the receive character FIFO and to communicate to the host node or various slave nodes. All protocol transactions are within this process. In and out queues are updated here.
The parsing function to take the packet pointed to by pPacket and call the call back function assigned to the packet's unit address with the netUnitAllocate function.
The following code is called during system start up on a host processor.
#include "net.h"
extern QUEUE xdata QueueMessage; // outgoing queue
extern QUEUE xdata QueueResponse; // incoming queue
// 2 is the packet length
// U_DISP is the node address of a display and keyboard node
// CLR is the function code of the clear display function for this node
PACKET clr = {2, U_DISP, CLR};
void main()
{
unsigned char ucPriority;
timerOpen(); // start system timer
netOpen(); // open the UART and network
netUnitAllocate(U_DISP, execDisplay, ENABLE); // bind address U_DISP to function execDisplay
enqueue(&QueueMessage, (PACKET *)&clr, MIN_PRIORITY); // send the message "clr"
while (TRUE)
{
netProcess(); // network layers
// dequeue a message from the reponse queue
// pass the packet or NULL to the parsing function
// continue while messages are in the reponse queue
while (netExecute(dequeue(&QueueResponse, &ucPriority)));
machineProcess(); // other process state machines
watchdog();
}
}
The following example code processes parsed incoming messages asynchronously, (i.e. event driven). A packet pointer is passed to this routine via the netExecute function. All message codes that need to be processed are placed in a switch statement according to function codes for this node. Other packets are discarded.
void execDisplay(PACKET *pPacket)
{
switch (pPacket->ucCode)
{
case STA : // status response
if (Display.status.ucByte != pPacket->ucaData[0])
Display.status.ucByte = pPacket->ucaData[0];
break;
case VER : // version response
memcpy(Display.szVersion, pPacket->ucaData, pPacket->ucLength - 2);
break;
case KEY : // key hit
ucKey = pPacket->ucaData[0];
bHit = TRUE;
}
}
#include "net.h"
extern QUEUE xdata QueueMessage;
void main()
{
unsigned char ucPriority;
timerOpen();
netOpen();
netUnitAllocate(ADDRESS, localFunction, ENABLE);
while (TRUE)
{
netProcess(); // network layers
while (netExecute(dequeue(&QueueMessage, &ucPriority)));
localProcess(); // local process state machine
watchdog();
}
}
The following example code processes parsed incoming messages asynchronously, (i.e. event driven). A packet pointer is passed to this routine via the netExecute function. All message codes that need to be processed are placed in a switch statement according to function codes for this node. Other packets are discarded.
void localProcess(PACKET *pPacket) // messages to local process
{
switch (pPacket->ucCode)
{
case SYN : // sync command
ucColumn = pPacket->ucaData[0];
status.ucByte = pPacket->ucaData[1];
ucExtractState = EXTRACT_SYNC;
break;
case EXT : // cycle command
ucColumn = pPacket->ucaData[0];
status.ucByte = pPacket->ucaData[1];
ucExtractState = EXTRACT_CHECK;
}
}