ESP 2G
Microcontroller Messages

General Message Format

All microcontrollers communicate on a multi-master I2C bus at 100 kbs/sec.  The I2C bus consists of only SCL (clock) and SDA (data) – no common \INT line is required.   In this document 'C' language structure definitions will be used to define the binary formats of I2C bus command packets.  We assume that the compiler will not be inserting pad bytes between structure members and standard network byte ordering (i.e. most significant byte first) for all multi-byte quantities.  The following primitive types are defined:

	typedef		unsigned char	byte;
	typedef		signed char	int8;
	typedef		unsigned short	uint16;
	typedef		short		int16;
	typedef		unsigned long	uint32;
	typedef		long		int32;
	typedef		byte		servoPosition[3];
	typedef		enum {false, true}  boolean;

 	typedef { 
		int16		P,I,D;		//integer PID gains
	} int16PID;

	typedef struct {		//One “Tic” = 0.25 seconds.
		uint16	dwell;		// in Tics
		int16		targetT;		// in hundredths degrees C
		uint16	errorHoldoff;	// Tics before starting to check temperature
		int16		minT, maxT;	// acceptable temperature band	
	} thermalProgramStep;

	typedef struct {
		int16PID heating,cooling;  //PID gains for heating & cooling
	} thermalCalibration;

I2C messages that do not elicit any replies are in the following form:

	struct noreplyI2Cmsg  { 	//message wrapper for msgs w/o replies
		byte	destination;		//7-bit I2C bus address, MSB is cleared
		payload			//variable, but first byte's MSB must be cleared
		byte	CRC;			//checksum
	};
 
Note that the first byte of  payload must have its MSB cleared to differenciate this message from those requiring replies.  In practice, the first byte of the payload denotes the message type.  So, this requirement is easily met ensuring that message types < 128 denote messages that do not have replies.

The destination is a 7-bit I2C network address with the MSB cleared.  This implies that all messages are write messages between peers.  The I2C read mode is not used because it requires that the bus hang until the addressed "slave" can reply.  Instead, messages that elicit replies from the addressee will cause the addressee to generate I2C write message(s) back to the I2C address from which the request came.  Any message that may require a reply from the addressee will have the following form:

	struct I2Crequest  { 	//message wrapper for request message
		byte	destination;		//7-bit I2C bus address, MSB is cleared
		byte	source;		//where to send replies, MSB is set
		byte	tag;			//MSB is clear, 7 LSBs are the request identifier
		payload			//this varies per request message type
		byte	CRC;			//checksum
	};

The message source address tells the addressee where to send all replies that the message may elicit.  The message tag returned unaltered to the original source.  This allows the requester to associate replies with the original message that caused them.  However, the receiver of such a reply still needs to differentiate between a reply and request from the addressee having the same tag as its original request did.  For this reason, reply messages always have the MSB of their tag byte set.  Thus, the format of a reply is:

	struct I2Creply  { 		//message wrapper reply messages
		byte	destination;		//original source of the request, MSB is cleared
		byte	source;		//responder's own address (with MSB set)
		byte	tag;			//the request id that elicited this response with MSB set
		payload			//this varies per reply message type
		byte	CRC;			//checksum
	};

In all the basic message wrappers defined above, the checksums include all bytes up to the CRC byte itself (i.e. they include the destination).   The CRC is always computed as follows:

byte nibbleTable[16] = {
0x00, 0x5e, 0xbc, 0xe2, 0x61, 0x3f, 0xdd, 0x83, 
0xc2, 0x9c, 0x7e, 0x20, 0xa3, 0xfd, 0x1f, 0x41, 
};

byte elbaTelbbin[16] = {  /* bit reversed elements of nibbleTable */
0x00, 0x9d, 0x23, 0xbe, 0x46, 0xdb, 0x65, 0xf8,
0x8c, 0x11, 0xaf, 0x32, 0xca, 0x57, 0xe9, 0x74
};

byte crc;  /* global CRC accumulator variable */

byte nextCRCbyte(byte data)
{
  unsigned char b = data ^ crc;
  return crc = nibbleTable[b&0xf] ^ elbaTelbbin[b>>4];
}

The global crc variable is initialized with 0xe5 after which each subsequent byte is passed to nextCRCbyte(). 

The I2C message payload field varies in content and length.  In spite of this, because each I2C message is framed in a Start/Stop condition pair and the rest of the message fields are fixed in length, the length of the payload need not be encoded explicitly anywhere in the message.

I2C Bus Event Handling

Incoming I2C messages addressed to the node are queued on a ring buffer of I2C “events”.  There are different event types corresponding to each I2C message type.  The application may sleep waiting on an incoming event or it may poll the event queue periodically to service them.  The I2C slave message event types are as follows:

typedef enum {
  noI2CnodeEvent,		//No events pending
  I2CnodeCRCmsg,		//CRC checked message received
  I2CnodeNonCRCmsg,	//non-CRC checked message received
  I2CnodeGCCRCmsg,	//general call CRC checked message received
  I2CnodeGCmsg, 		//general call non-CRC checked message received
  I2CnodeReadRequest,	//read notification – not used by default
  
  I2CnodeEventTypes
} I2CnodeEventType;

Each event consists of one of the above type identifiers followed by a variable number of bytes representing the message body.  CRC message events do not include the CRC byte in their bodies.  If an invalid CRC is received, the event is simply not queued and thus ignored.  The current implementation also discards messages longer than 255 bytes.  

Applications inspect incoming events via one of two hooks called directly from the I2C interrupt service handlers.  One allows the application to selectively “accept” the incoming message onto the event queue – or to ignore it.  The other is called when a master reads the node so that it can pass back a byte string to the master immediately.  Note that is this “read request handler” may also decide to defer service by adding an I2CnodeReadRequest to the event queue, however, this is generally not recommended because the bus is locked between the incoming read request from the master and when the slave services that event.  Also note that interrupts are disabled in both of these event handling hooks, so they must complete within 30 microseconds or so.  Any involved computation must be deferred to the background event handlers.  See the attached examples of message event hook functions for implementation details.

If a node's event queue should become full, that node must hold the I2C bus clock low to avoid losing messages.  Thus, the application should work continuously process events without interruption as a full event queue  holds up all bus traffic.  Worse yet, a bus deadlock can arise if, while the event queue is full and the node has held up the bus, the application attempts to initiate a master mode message of its own.  In this case, the bus must first be unlocked, so the application can procede.  However, since there is no space in the incoming event queue, the message that was being received by the node is dropped.  If that message was not CRC checked, there is no mechanism inform the sender about this failure and the message is simply lost.  However, if that incoming message was CRC checked, the node NACKs it, even if the received CRC is valid, so that the sender will resend it after a small delay.  This delay is critical as it gives the receiving node a chance to initiate its bus traffic and (hopefully) remove some message(s) from its event queue so that there will be space to receive the original incoming message when it is resent.  

Message Parsing

The application's background loop removes events from the message event queue and interprets them.  As mentioned previously, these events consist of a type identifier and a variable length body.  Functions are provided to poll for the next event or sleep until it occurs.  They all return the event type and the length of its body.  To keep memory requirements to a minimum, the event body is never copied out of the queue into a linear buffer.  Instead, it is left at the head of the queue to be consumed by the parsing process.  Any “extra” message bytes not consumed during its parsing are silently discarded when the application polls for the next message event.

The following code skeleton suffices if the only events that the applications background loop must handle are from the I2C bus and all non-CRC checked messages are to be ignored:

//assuming initialization done and I2C node address already set
for (;;) {
	unsigned evBodyLen;
	I2CnodeEventType ev = awaitNextI2CnodeEvent (&node, &evBodyLen);
	switch (ev) {
 		case I2CnodeCRCmsg:   		//CRC checked message received (to node 14)
		case I2CnodeGCCRCmsg:	//general call CRC checked message received
			if (evBodyLen)  { //ignore null messages
				byte command = FIFOreadByte (&node.events);  //consume 1st byte of body
				switch (command) {  //we assume the only command types are 'A' and 'B'
					case 'A':  //process command that begins with the byte 'A'
						if (evBodyLen > 3)  { //'A' must be followed by a 3 byte signed value
						  int32 parameter = FIFOread24signed (&node.events);
							...process A command's parameter...
						}
						break;
					case 'B':  //process command that begins with the byte 'B'
						...
						break;
					default:
						{process unknown command}
				}
			}
			break;

		case I2CnodeNonCRCmsg:  //ignore non-CRC checked messages
 		case I2CnodeGCmsg: 	   //ignore general call non-CRC checked message	   		case noI2CnodeEvent:    //No events pending – shouldn't happen
			break;

		default:	//ignore unknown events, or display some diagnostic
			{complain about bogus event}
	}
}

Mastering the I2C Bus

There is no corresponding queue for master mode I2C bus access.  The functions that initiate master mode transfers do not return until the access is completed.  This implies that applications cannot call them from an interrupt context.

Microcontroller applications master the bus by calling one of the two functions below: 

//Read length bytes from node I2Csrc address into buffer
I2CnodeErr I2CnodeRead(I2CnodeInstance *instance, uint16 I2Csrc, 
                            			byte *buffer, unsigned length);

//Write length byte message in buffer to node I2Cdst
//if I2CnodeNoCRC mask in I2Cdst is set, the message is written without CRC checking
//  (in case the destination node does not support CRCs)
I2CnodeErr I2CnodeWrite(I2CnodeInstance *instance, uint16 I2Cdst, 
                              			byte *buffer, unsigned length);

Note that errors are returned by these functions only after the prescribed number of retries have already been attempted.

I2CnodeErr is defined as follows:

typedef enum {  //errors from I2C bus master operations
  I2CnodeOK,		//success
  I2CnodeNACK,		//negative acknowledge -- see I2CnodeBytesXferred()
  I2CnodeCRCNACK,	//negative CRC acknowledge after all bytes transferred
  I2CnodeBusErr,		//unrecoverable bus error
  I2CnodeTimeOut,		//operation timed out
  I2CnodeArgErr,		//invalid arguments
  I2CnodeRetryErr,		//used internally – not returned to caller
  
  I2CnodeErrs
} I2CnodeErr;

//return the number of bytes successfully transferred before last I2CnodeNACK
unsigned I2CnodeBytesXferred(I2CnodeInstance *instance);

Note that this function's return value is undefined unless the last error returned from I2CnodeWrite or I2CnodeRead was I2CnodeNACK.

Setting the Node's I2C  Address

After initialization, a node's I2C address is zero.  It will receive only  “general call” messages.  After software initialization, the application should call the following to set the address to a non-zero value:

//set own address and probe it to insure address not already in use
//returns true if successful, otherwise, the address reverts to zero.
boolean 
  setI2CnodeAdr (I2CnodeInstance *instance, byte newAdr);

Whenever the address is set, the node first tries sending a null message to the newAdr.  If the address is acknowledged, it is assumed that it is already in use.  In this case, the function returns false and the node's address reverts to zero.

Note that addresses are 7-bit quantities. If the MSB of newAdr is set, the node will ignore general call messages and only respond to the node address specified in the lower 7 bits of the address.

Managing Transaction Tags

Any microcontroller that sends multiple requests to the same destination microcontroller without waiting for all the responses that might have been elicited by previous ones, faces an ambiguity when attempting to parse that microcontroller's responses.  To resolve this ambiguity, the requester assigns the tag field of the each request an arbitrary 8-bit value that uniquely identifies it.  The responding microcontroller merely copies that request identifier in all their responses to it.  Thus, any given microcontroller may have as many as 256 simultaneous outstanding requests from any other.  Host applications must ensure that the transaction denoted by a given request 8-bit tag is completed before the tag may be recycled.

Message Categories

The remainder of this document deals with the detailed formats of each I2C message.
Only the message payloads are documented.  These appear in the context of I2C noreply, request or reply messages as appropriate.  Each I2C message payload type begins with a unique message type byte (constant) that indicates its structure so the receiver can parse  it.
 
Main messages categories are:

	Solenoid	--	Two position latching and non-latching
	Shaft	  	--	N position rotary controls
	Thermal	--	Control of temperature
	Servo		--	Motor control
	
Note that there are many message types within each of these broad categories.  Message type constants are written in boldface type within the payload structures.  These unique message type identifiers facilitate parsing message payloads.

Solenoid Control Messages

Solenoids are often used to actuate on/off valves or move other mechanical plungers.  A solenoid may be latching or non-latching.  Latching solenoids always remain in their current state when power is removed.  Non-latching solenoids always return to a known "home" state when power is removed.  In practice, this means that non-latching solenoids must remain powered for long periods, while latching solenoids are pulsed from one position to the other.

	Configure Solenoid
	Used to globally configure solenoid pulse duration.

	struct SolenoidConfigureMsg  {	//one tic = 1/64 seconds
		byte	SolenoidConfigure;
		byte	pulseDuration;		//in tics
	};

All subsequent solenoid pulses will be of the specified duration on hundredths of seconds.

Get Solenoid Status
	Used to retrieve the current state of all solenoids.

	struct SolenoidStatusRequest  {
		byte	SolenoidStatus;
	};

The addressed microcontroller replies to the requesting one with:

	struct SolenoidStatusReply  { 	//one tic = 1/64 seconds
		byte	SolenoidStatus;
		byte	pulseWidthRemaining;	//tics remaining until end of current pulse
		byte	state[n];		//each solenoid's state (packed 4 per byte)
	};

All solenoids are pulsed synchronously and have the same duration, so the current pulse for all will end at the same time.  Within each state byte, solenoids' current status are represented by adjacent bit pairs encoded as follows:

Binary Mask	Meaning	
00	Not powered (relaxed)	
01	Pulsing Up	
10	Pulsing Down	
11	Reserved (unused)	

Pulse Solenoid
	Used to pulse a latching solenoid to the specified states.

	struct SolenoidPulseMsg  { 	//message payload for Solenoid Pulses
		byte	SolenoidPulse;
		byte	direction[n];		//pulse commands (packed 4 per byte)
	};

Within each direction byte, solenoids' pulse types are represented by
adjacent bit pairs encoded as follows:

Binary Mask	Command	
00	Do nothing	
01	Pulse Up	
10	Pulse Down	
11	Terminate Pulse	

The LSBs are associated with the lowest numbered solenoids

"Up" and "Down" are arbitrary names for the two solenoid states.  Each pulse's duration is determined by the previous Solenoid Configure message.  A pulse in progress may be aborted by the Terminate Pulse mask.

Hold Solenoid
	Used to hold non-latching solenoids in specified states.

	struct SolenoidHoldMsg  { 	//message payload for Solenoid States
		byte	SolenoidHold;
		byte	state[n];	//hold commands (packed 4 per byte)
	};

Within each state byte, solenoids' states are represented by
adjacent bit pairs encoded as follows:

Binary Mask	Command	
00	Do nothing	
01	Hold Up	
10	Hold Down	
11	Relax (force neither up nor down)	

The LSBs are associated with the lowest numbered solenoids

"Up" and "Down" are arbitrary names for the two solenoid states.  A solenoid commanded to Hold either Up or Down draws power until a Relax command mask or the end of a solenoid pulse.

Rotary Shaft Positioner Control Messages

Rotary Positioners may be moved in angular increments called counts.  There are 216 counts in a circle.  Velocity and acceleration cannot be prescribed.  However, direction (counterclockwise and clockwise) may be.

	Configure Shaft
	Configure the specified rotary controller

	struct ShaftConfigureMsg  { 
		byte		ShaftConfigure;
		byte		shaftNumber;	//zero is the 1st
		uint16	maxError;	//maximum acceptable position error
		byte		maxTorque;	//maximum shaft torque
	};

	Shaft Configuration Query
	Request the configuration of the specified shaft

	struct ShaftConfigureRequest  { 
		byte		ShaftConfigure;
		byte		shaftNumber;		//zero is the 1st
	};

	struct ShaftConfigureReply  { 	//one tic = 1/64 seconds
		byte		ShaftConfigure;
		uint16	maxTics;			//maximum acceptable cmd duration in Tics
		uint16	maxError;		//maximum acceptable counts of position error
		byte		maxTorque;		//maximum shaft torque
	};

A ShaftConfigureMsg will abort any movement in progress on the selected shaftNumber and an appropriate error will be sent to the node that initiated the aborted Rotary Move command.

	Move Shaft
	Move previously configured shaft to specified position

	struct ShaftMoveRequest  {  	//one tic = 1/64 seconds 
		byte		ShaftMove;
		byte		shaftNumber;		//zero is the 1st 
		uint16	goal;			//goal angle in counts
		uint16	maxTics;			//maximum acceptable move duration in Tics
		int8		direction;		//0=fastest, 1=CW, -1=CCW
	};

Torque is specified from 0 to 255 where 0 is none and 255 is maximum.  The direction determines whether the roatation will be clockwise, counterclockwise, or whichever direction is fastest.  

	struct ShaftMoveReply  {  		//one tic = 1/64 seconds
		byte		ShaftMove;
		byte		completionCode;	//error code
		uint16	angle;			//angle at end of move
		uint16	elapsedTics;		//cmd duration in hundredths of seconds
	};

A non-zero completionCode implies that the rotation did not complete within the specified maxTics and/or maxError.

	Abort Shaft Move
	Abort any shaft move command in progress

	struct ShaftAbortMsg  { 
		byte		ShaftAbort;
		byte		shaftNumber;		//zero is the 1st 
	};

A ShaftMoveReply with an appropriate error will be send to the node that initiated the aborted Rotary Move command.

	Shaft Status Query
	Info about the move in progress

	struct ShaftStatusRequest  { 
		byte		ShaftStatus;
		byte		shaftNumber;		//zero is the 1st 
		uint16	repeatRate;		//optional repeat rate 
	};

RepeatRateis the number of hundredths of seconds between replies.  These repeated replies continue until the next Shaft Status Query message.
If repeatRate is zero, any repeated replies are halted.
If it is missing (truncated message), only one reply is generated (immediately) -- and any repeated replies continue.

	struct ShaftStatusReply  { 
		byte		ShaftStatus;
		uint16	angle;			//current angle
	/* the remaining fields are sent only if a move is in progress */
		uint16	goal;			//goal position [0..modulus-1]
		byte		torque;			//current shaft torque [0..255]
		int8		direction;		//0=STOPPED, 1=CW, -1=CCW
	};

Note that torque == 0 implies that the shaft is either stopped or is coasting to a stop.   The reply is truncated only after the move is completed.

Thermal Controller

Microcontrollers may have at most one thermal control station which may consist of either a heater, a cooler or both.  The thermal controller may be programmed to execute a precisely timed sequence of temperature changes on a sample.

	Configure Thermal Controller
	Configure the thermal controller

	struct ThermalConfigureMsg  { 
		byte		ThermalConfigure;
		struct	thermalCalibration;	//PID gains for heating & cooling
	};

	Thermal Controller Configuration Query
	Request the configuration of the thermal controller

	struct ThermalConfigureRequest  { 
		byte		ThermalConfigure;
	};

	struct ThermalConfigureReply  { 
		byte		ThermalConfigure;
		struct	thermalCalibration;	//PID gains for heating & cooling
	};

A ThermalConfigureMsg will abort any thermal program in progress and an appropriate error will be sent to the node that initiated the aborted program.  Note:  zeroing all PID gains for heating and/or cooling will disable active heating and/or cooling.  PID units are TBD.  

	Status Query
	Request the status of the thermal controller

	struct ThermalStatusRequest  { 
		byte		ThermalStatus;
		byte		repeatRate;		//optional repeat rate 
	};

RepeatRateis the number of tenths of seconds between replies, continuing until the next Thermal Status Query message.  If repeatRate is zero, any repeated replies are halted.
If it is missing (truncated message), only one reply is generated (immediately).

	struct ThermalStatusReply  { 
		byte		ThermalStatus;
		int16		currentTemperature;
			/* subsequent fields only appear if a thermal program is running */
		uint16	elapsedTics;		/* tics since program started */
		byte		heaterPwr, coolerPwr;
	};

	Execute Thermal Program 
	Run specified thermal program with a variable number of setpoints.
		Program setpoint units are hundredths of degrees Celsius.
		Time units are in tenths of seconds.

	struct ThermalRunRequest  { 
		byte		ThermalRun;
		thermalProgramStep step[steps];	//variable number of program steps
	};

	struct ThermalRunReply  { 
		byte		ThermalRun;
		byte		completionCode;	//completion/error code
		uint16	elapsedTics;		//cmd duration in tics
	};

If the command fails, elapsedTics will indicate where in the thermal program the failure occurred.  The thermal program consists of an array of "step" structures.  Each specifies a new target temperature, duration (dwell) and error checking parameters. 

	Thermal Program Query
	Request the currently running program of the thermal controller

	struct ThermalProgramRequest  { 
		byte		ThermalProgram;
	};

	struct ThermalProgramReply  { 
		byte		ThermalProgram;
		thermalProgramStep step[steps];	//program steps
	};

If there is no currently running program, steps is zero.

Servo Control

Each microcontroller has some number of (usually 2) motor servo control channels.
Each servo channel has a PWM driver, relative encoder, home flag, forward & reverse limit switches and a pressure sensor associated with it. 

	Configure Servo
	Configure a channel of servo control

	struct ServoConfigureMsg  { 
		byte			ServoConfigure;
		byte			channel;	//1st channel is number zero
		byte			optionMask;	//bit mask (see below)
		int16PID 	gain;		//PID gains
		uint16		maxSpeed;	//max allowable speed  (counts/tics)
		unit16		minSpeed;	//min permissible progress (counts/tics) at maxCurrent
		servoPosition	maxPositionErr;	//max acceptable position error
		uint8			maxTorque;		//max normalized % torque allowable 
		int8			maxDeltaPress;	//max difference between inlet and outlet pressure
		uint8     	maxInPress;     	//max inlet pressure (units?)
 		uint8			maxOutPress;	//max outlet pressure
 		uint8			minInPress;		//min inlet pressure
		uint8			minOutPress;    	//min outlet pressure
	};

	optionMask:
		Bit 0 Encoder Power ON
		Bit 1 Stop on Reverse Limit Switch
		Bit 2 Stop on Forward Limit Switch
		Bit 3 reserved
		Bit 4 (Jog) toward the HOME position, reset the encoder, then do command
		Bit 5 Home sensor power ON
		Bit 6 Home flag polarity – Position Reset on rising (1) or falling (0) edge
		Bit 7	Encoder Polarity  (increasing clockwise or counterclockwise)

The motor is powered off if all PID gains are zero.
Turning off encoder or motor power immediately aborts any move in progress and turns the servo off.
Otherwise, the servo command in progress continues with the updated configuration.

Servo configuration commands may be truncated.  In which case, all the unspecified fields remain unchanged.

If bit 4 (Jog) is set, the servo first jogs toward the home flag before seeking the specified goal.
The direction jogged is determined by the state of the home flag sensor XOR'd with the specified home flag polarity.  After passing the home flag, the encoder counter is cleared and the servo completes the specified move.

MaxInPress, maxOutPress and maxDeltaPress checking are disabled if they are set to zero.

	Servo Configuration Query
	Request the configuration of a servo channel

	struct ServoConfigureRequest  {
		byte		ServoConfigure;
		byte			channel;	//1st channel is zero
	};

	struct ServoConfigureReply  { 
		byte		ServoConfigure;
		byte			optionMask;	//bit mask (see previous page)
		int16PID 	gain;		//PID gains
		uint16		maxSpeed;	//max speed  (counts/tenth seconds?)
		unit16		minSpeed;	//min premissable speed at maxForce
		servoPosition	maxPositionErr;	//max acceptable position error
		uint8			maxTorque;		//max normalized % torque allowable [0..0xff]
		uint8     	maxInPress;     	//max inlet pressure
 		uint8			maxOutPress;	//max outlet pressure
 		uint8			minInPress;		//min inlet pressure
		uint8			minOutPress;    	//min outlet pressure
	};

	Servo Status Query
	Request the dynamic status of a servo channel

	struct ServoStatusRequest  { 
		byte		ServoStatus;
		byte			channel;	//1st channel is zero
		uint16		repeatRate;	//optional repeat rate 
	};

RepeatRate is the number of 1/64 second tics between replies, continuing  until the next Servo Status Query message.  If repeatRate is zero, any repeated replies are halted.
If it is missing (truncated message), only one reply is generated (immediately).

	struct ServoStatusReply  { 
		byte		ServoStatus;
		byte		servoStatusMask;	//bit mask – see below.
		servoPosition position;		//current absolute position
		int16		velocity;			//  (counts/tic)
		int8		torque;			// current torque and direction
 		uint8     inPress;			//inlet pressure
 		uint8     outPress;		//outlet pressure
	};

	ServoStatusMask:
		Bit 0 Servo ON (actively holding or seeking)
		Bit 1 RLS active
		Bit 2 FLS active
		Bit 3 Exceeding Max. or Min. Velocity Limits
		Bit 4 over current
		Bit 5 over pressure
		Bit 6 Lost Home Flag (set if absolute encoder position lost)
		Bit 7 Current Home Flag state

If velocity or pressures are out of the range that can be represented in a int16 or int8, respectively, the values returned must be the maximum or minimum possible in their respective signed data types as appropriate.

	Servo (absolute or relative) Move 

This command will cause the motor move to a defined absolute position or a relative distance.  This command may be issued while a motor is moving.

	struct ServoMoveRequest  { 
		byte		ServoAbsMove or ServoRelMove;
		byte		channel;		//1st channel is zero
		servoPosition goal;	//relative or absolute goal position 
	};

Upon arrival at the goal (or serious error), the controller responds with:

	struct ServoMoveReply  { 
		byte		ServoAbsMove or ServoRelMove;
		uint16	elapsedTics;		//in tics
		/* subsequent fields are present only if an error occurred */
		byte		servoReason;		//bit mask – see below.
		servoPosition position;		//current absolute position
		int16		velocity;			//  (counts/tenth seconds?)
		int8		torque;
 		uint8     inPress;			//inlet pressure
 		uint8     outPress;		//outlet pressure
	};

Normally, the servo enters the “Hold” state and responds with the truncated ServoMoveReply.  It subsequently behaves as though a Servo Hold command were issued at the Goal Position.  However, if an error occurs, the servo enters the “Off” state and responds with the full servo Move Reply.  Failure to hold position may cause delayed error replies with the original Servo Move Command's Tag byte value.

	ServoReason:  -- why the previous servo command ended
		Bit 0 Servo ON (actively holding or seeking)
		Bit 1 reached RLS
		Bit 2 reached FLS
		Bit 3 Exceeded Max. or Min. Velocity Limits
		Bit 4 over current
		Bit 5 over pressure
		Bit 6 Lost Home Flag (lost if absolute encoder position lost)
		Bit 7 Aborted move due to receipt of a new servo command

Bits 0 through 6 of the ServoReason mask are copied from the ServoStatus mask at the instant the move was terminated.  The Aborted bit is set only if a new Servo Move command was received before this one completed.  Note that Bit 0 should reflect the state of the servo after the command abort.  Thus, it will be cleared unless aborted by a new command other than ServoOff. 

	Servo Hold 

This command is used hold a motor in position.  It uses the current position as the goal position to start control.  Any servo move command in progress will be aborted with the appropriate Abort response and the motor held at the current position.

	struct ServoHoldRequest  { 
		byte	ServoHold;
		byte	channel;
	};

If Servo Hold is terminated by another Servo command, no reply is produced.
If, however, a configured Error limit is exceeded while holding, the controller responds with:

	struct ServoHoldReply  { 
		byte		ServoHold;
		byte		servoStatusMask;	//bit mask – see ServoStatus.
		servoPosition position;		//current absolute position
		int16		velocity;			//  (counts/tics)
		int8		torque;
 		uint8     inPress;			//inlet pressure
 		uint8     outPress;		//outlet pressure
	};

	Query Servo Goal

This command returns information about the current servo command in progress.

	struct ServoGoalRequest  { 
		byte	ServoGoal;
		byte	channel;
	};

	struct ServoGoalReply  { 
		byte	ServoGoal;
		byte	ServoHold | ServoOff | 
				| ServoAbsMove | ServoRelMove;
		servoPosition goal;  /*not present if previous byte == ServoOff */
	};

I2C Gateway Protocol

A microcontroller may function as a gateway to the I2C network for an external host by implementing this protocol on one of its other serial ports.  I2C messages are encapsulated on the serial link such that the body of the message may be passed through from the host to the I2C bus as it is being received by the gateway without the requiring that the gateway buffer the entire message.  This minimizes latency for the host and memory requirements for the microcontroller.  This gateway protocol does not support 10-bit I2C extended addressing.

I2C bus messages in general contain 8-bit binary data and are delimited by I2C specific, out-of-band start and stop bits.  Most other types of serial channels do not provide any analogous mechanism to delimit messages.  More importantly, since the slave receiver of an I2C write does not know how many bytes to expect, prefixing the binary data with a length to delimit them would require that each I2C message be fully received and buffered the gateway before it could be forwarded.

This protocol expands the 8-bit binary data into a sequence of 7-bit characters when necessary to avoid buffering whole messages.  The number of bytes needed to represent the original data increases slightly, so that the resulting “unused”  most significant bit (MSB) in each 8-bit byte can delimit messages.  An empty message is encoded as the a single byte value 0x80.   

An I2C write message is encoded as follows: 

	struct I2CgateWrite  { 	//wrapper for I2C write messages via gateway
		byte	destination;		//destination node (MSB cleared)
		content			//7-bit recoded message with only the last byte's MSB set
	}; 

The first byte of  content consists of the first original byte's 7 least significant bits.   The 2nd contains the MSB from the original first byte in its LSB and six LSB's of the original 2nd byte.  The 3rd contains the two MSB's of the original 2nd byte in its LSB's and the five LSBs of the original 3rd.  Etc...
Finally, the last byte of  content contains the remaining (1..7) bits of the original last byte and has its MSB set to designate it as the last of the message.

These encoded I2C write messages may be either sent to the gateway or may be received from the gateway.  Note that the content may be any type of I2C message.   It is passed directly on to the destination unchanged with a minimum of buffering.  Messages should be written to the gateway without inter-character delays, as such delays will hold up all other activity on the I2C bus.

The gateway can be configured to send any single byte value < 128 to confirm to the host that the I2Cwrite succeeded.  By default, however, no confirmation is sent unless an error occurs.  

Although the ESP protocol does not utilize reads from I2C bus, to support other (traditional) I2C peripherals, it is possible to read from the bus via the gateway with the following command:

	struct I2CgateRead  { 	//wrapper for I2C read messages via gateway
		byte	source;		//source node (the MSB must be set)
		byte	length;		//maximum # of bytes to read from source (0..127)
	};

When the I2C bus is read, all bus traffic ceases until the source node responds.  Messages written to the host (via the gateway) by other I2C bus nodes will always have one of the gateway's receiving addresses as their destination.  Receipt of a “general call” I2C write is indicated via the gateway by a destination address of zero.

It is also possible for another I2C node to read from a host via a gateway.  When an I2C bus master reads the gateway, the gateway optionally forwards the event to the host as an I2CgateHostRead command.  It optionally waits at most hostDela tics for an I2CgateSetResponse command from the host.   [see I2CgateConfig below.  Note that hostDelay is not supported by version 1 of the protocol.]The gateway holds the  I2C bus clock while it waits.

Therefore, reading the host indirectly (via the gateway) with hostReadDelay > 0 can be very inefficient and should be avoided whenever possible.  Further, if the gateway receives a request to access the I2C bus from the host before receiving the expected I2CgateSetResponse from the host, it most satisfy the other master's read request with the previously cached response before it can satisfy the host's request to access the bus.  

Whenever practical, a better approach to servicing read requests from other  I2C masters is to set hostReadDelay = 0  and have the host update the I2CgateSetResponse, in anticipation of other bus masters' requesting reads.  With this scheme, the gateway will still (optionally) emit I2CgateHostReads to the host whenever gateway reads occur, but it will deliver the cached I2CgateSetResponse immediately to the other master without waiting to receive an updated one from the host.  Thus, when the gateway receives an I2CgateGetResponse command, it immediately outputs the cached read response back to the host in the form of an I2CgateSetResponse command.

If, while selecting a node or writing to another node, the gateway does not receive an expected I2C acknowledge pulse, it outputs a I2CgateSlaveNACK command to the host containing the number of bytes transferred successfully before the missing acknowledge.  The gateway outputs an I2CgateMasterNACK if another master reads the gateway, but does not transfer the expected number of bytes.  The response string is padded with zeros as necessary if the master attempts to read more bytes than its length.

Commands

Messages where the first byte's MSB is clear are  I2CgateWrites while messages whose the first byte's MSB is set can be either I2CgateReads or extended commands depending upon the the 2nd byte's MSB.  If the 2nd byte's MSB is cleared, the message is a read request, else the message is a command.  Thus, the general form of an command is:

 	struct I2Ccommand  { 	//wrapper for gateway commands embedded in a stream
		byte	length;		//command length + 128  (0x80 = the null command)
		byte	opcode;		//command opcode with MSB set
		parameters		//command parameters (length-1 bytes)
	};

The opcode and parameters are omitted if length is 0x80.  Only the parameters field is omitted if length is 0x81.  The most significant bytes of multibyte quantities in commands are transmitted first – i.e. Network byte ordering is used for the uint16 length, etc.  Extra parameter bytes are silently ignored.  
Syntax errors, such as truncated commands, may cause a restart..   See Resynchronization below.

Commands requiring more than 126 bytes of parameters must have the following form:

 	struct I2ClongCommand  { //wrapper for long commands embedded in a stream
		byte	0xff;
		uint16	length		//command length (0..65535)
		byte	opcode;		//command opcode with MSB set
		parameters		//command parameters (length bytes)
	};

Note that all commands, except I2CgateHostRead, have both long and short forms.  However, in the interests of brevity, only the short forms of each are documented.

I2C gateways must implement all the following commands:

	struct I2CgateHostRead  { //marks receipt of master read request from another node
		byte	0x80;			//null command (no parameters or even an op code)
	}; 

The I2CgateHostRead is sent to the host as soon as the gateway recognizes its own address.  Sending it to the gateway will cause it to restart.  See Resynchronization below.

	struct I2CgateVersionQuery  { //request version of the I2C gateway
		byte	0x81;			      //no parameters
		byte  0x80;		      //get version information
	}; 

The gateway sends back:

	struct I2CgateVersionResponse  { //version of the I2C gateway
		byte	0x84 + length;	
		byte  0x80;	//version information
		byte	version;	//either 1 or 2 for now
		uint16	cacheSize;	//max # of bytes in any cached I2CgateSetResponse
		byte	description[length];	//ASCII text description of gateway version
	}; 

Two versions are defined.  Version 1 of the protocol lacks support for hostReadDelay -- it is fixed at zero.  Version 2 includes it.

Generic responses where the gateway does not understand the structure of the data:

	struct I2CgateResponse  { 	//requested data from gateway or set read response
		byte	0x81 + length;
		byte  0x81;	//set response string
		byte	response[length];		//body of the response string
	}; 

When sent to the gateway, this message updates its cached response to another bus master's read request.  If the gateway is holding up the I2C clock awaiting this update from the host, it will pass the response through to the waiting master while caching it for the next read.

When sent to the host, this message contains whatever data was previously requested , which will typically be the forwarded response from another node to the gateway's previous I2CgateRead.  Note that the source node is absent – it is implied to be the source that was specified in that previous request.  If that source node does not respond, the gateway sends the host an I2CgateSlaveNACK in place of the expected I2CgateWrite.  Note that, if the most recent request was an I2CgateHostRead, the response is simply the most recently cached response sent to the gateway.

	struct I2ClongRead  { 	//wrapper for requesting long I2C responses in a stream
		byte	0x84;			//# of parameter bytes
		byte	0x84;		//read operation
		byte	source;		//source node
		uint16 length;		//# of bytes to read (0..65535)
	};

Used to read >126 bytes from a slave node.

	struct I2CgateConfig  { 	//configure the I2C gateway
		byte	0x81..0x8B		//configuration parameters may be truncated
		byte	0x82;			//configuration command
		/* remaining fields are optional – they remain unchanged if omitted */
 		byte  retries;	//# of times to retry write if CRC fails or NACK received
							//MSB should be cleared
 		byte  timeout;	//bus timeout in quarters of seconds, 0 if none
 		byte  gatewayAddress;  //gateway's own address on the I2C bus or zero if none
							//MSB set if gateway should ignore general calls
		byte  OK;		//if !=0, send (& 0x7f) to host to confirm nonCRC writes
							//MSB set to enable appending this to received nonCRC writes
		byte  busNACK;	//if !=0, byte to send if bus times out
							//MSB set to enable sending I2CgateHostRead notifications
		byte  adrNACK;	//if !=0, send if slave does not acknowledge its address
							//MSB set to suppress testing address if unchanged
 		byte  slaveNACK; //if !=0, send if slave NACKs a data byte written to it
							//MSB set to suppress reporting null writes
		byte  masterNACK; //if !=0, send if master NACKs a byte it requested 
							//MSB set for 1 bit message CRC ACK, clear for full byte ACK
		byte  CRCOK;	//if !=0, send to confirm slave received CRC checked message
							//MSB set to enable appending this to received CRC writes
		byte  CRCNACK; //if !=0, send if slave NACKs CRC checked message
							//MSB set to enable appending this to received CRC writes
	};

Notes:	Unless  MSBs of byte fields have a documented function, they should always be cleared.
 	For slaveNACK and masterNACK, the # of bytes successfully transferred is a 16-bit quantity. 
	SlaveNACK and masterNACK are always directly followed by two bytes representing the number of bytes successfully transferred before the NACK occurred.
Bus timeouts may also occur when no command is in progress if receiving a message takes longer than busTics.  In this case, the incoming message will be truncated before timeout is sent to the host.

If the 7 LSBs of  I2CgateConfig.OK are zero, the gateway sends no positive confirmation when I2CgateWrites succeed;.otherwise, each successful write will cause the “OK”  byte to be returned to the host.  Set all OK* and NACK* configuration parameters to unique values other than zero or the current gatewayAddress to ensure that transmit status is not confused with writes to the gateway form another node.   After receipt of the I2CgateConfig command, the gateway probes the requested gatewayAddress.  If no other node is using it, I2CgateConfig.OK is returned to the host.  Otherwise, the gateway responds with I2CgateConfig.adrNACK and the gatewayAddress is set to zero.  When the address is not being altered, the address probe can be suppressed by setting the MSB of  adrNACK.

 AnyI2C network traffic directed to the gatewayAddress will be forwarded to the host.  A gatewayAddress of zero disables all reception of  I2C writes via the gateway except general calls.  This is the default configuration.  If the underlying  hardware supports it, a gatewayAddress with its MSB set disables the receipt of general calls by the host via the gateway.  Thus, the address of 0x80 disables the receipt of all I2C writes from other nodes.

	struct I2CgateConfigQuery  { //request the configuration of the I2C gateway
		byte	0x81;			    //no parameters
		byte 0x83;
	}; 

The gateway sends an I2Cgateconfig message with all supported fields back to the host.

	struct I2CgateEcho  { 	//echo command back to host
		byte	0x81 + length;	//# of parameter bytes  (0x81..0xfe)
		byte	0x85;		//echo operation
		byte	string[length];	//any arbitrary string of bytes
	};

The gateway sends back the exact same command it received.  The echo command can be used to test the serial link or to delay until all pending commands have been completed. 

All message writes received by the gateway are passed through to the host regardless of whether any CRC at the end of the message is correct.  The host should verify the CRC and ignore messages where it is in error.  If CRC checking is enabled, the gateway will automatically NACK any message with an incorrect CRC.  This “message NACK” should cause the sender to repeat the transmission. The following two commands facilitate the host's verification of received messages:

 	struct I2CgateCRCtype  {  //Set the CRC algorithm type & parameters
		byte	0x83 + sizeof parameters;
		byte  0x86;	//set CRC type
		byte	type;		//only type 1 is supported
		byte	parameters[];	//optional type specific CRC calculation parameters
	}; 

	struct I2CgateCRCquery  {   //get CRC algorithm parameters
		byte	0x82 + length;
		byte  0x87;
	}; 

The gateway responds with a I2CgateCRCtype message where the parameters field consists of 33 bytes.  The first byte is the CRC seed, the next 16 are the nibbleTable, and the last 16 are the nibbleTable with the bits in reversed order, as follows:

 	struct I2CgateCRCtype  {  //Get the CRC algorithm type & parameters
		byte	0xa3;
		byte  0x86;	//CRC type
		byte	1;			//CRC type 1
		byte	0xE5;	//CRC seed
		byte	nibbleTable[16];
		byte elbaTelbbin[16];	//see General Message Format section
	}; 

The next two commands are more efficient alternatives to I2CgateWrite for use when the length of the body is known a priori.  These commands may be sent by the Gateway in place of Write commands.  In practice, most applications should use these forms whenever possible.  The only situation where I2CgateWrite must be used is to forward other master's writes to the host via gateway, as, in this case, the message length is not known a priori.

	struct I2CgateWriteNoCRC  {  //Write I2C msg where length is known w/o CRC check
		byte	0x82 + length;
		byte  0x88;	//write operation
		byte	destination;	//destination node
		byte	body[length];	//body of the message
	}; 

	struct I2CgateWriteCRC  {   //Write I2C msg with given length + CRC byte appended
		byte	0x82 + length;
		byte  0x89;	//write operation
		byte	destination;	//destination node
		byte	body[length];	//body of the message
	}; 

When I2CgateWriteCRC is received by the gateway, it appends the calculated CRC byte to the end of the message body after it is written to the I2C bus.  Thus, the actual number of message bytes sent over the bus is length + 2. [Note that  I2CgateWriteNoCRC sends length + 1 bytes].  The gateway sends the host the configured CRCOK byte immediately after the CRC checked message is acknowledged by the slave.  The gateways sends CRCNACK just once if the slave does not acknowledge the CRC message after the configured number of retries.

The gateway checks CRCs on incoming messages.  Thus, every write message received will be followed immediately by a OK, CRCOK, or CRCNACK status byte.  The host should wait for the message status byte before accepting (or ignoring) the message.  In case of a CRC error, if retries > 0, the gateway will automatically send a message NACK back to the other master, which should cause the offending message to be resent (at most retries times).

Although not recommended, it is possible to suppress OK, CRCOK or CRCNACK by clearing their respective MSBs in the I2CgateConfig struct.  If both CRCOK and CRCNACK status bytes are  suppressed, received CRC'd messages will include the actual CRC byte received as the last byte of their body.  The host must then verify that the CRC value is valid using the information previously cached from the gateway's I2CgateCRCtype reply.  CRC retries still function as usual in this case.

Note that in some implementations, long (>250 byte) CRC checked messages cannot be verified.  In this case, if the CRC matches, it will be accepted by the slave, if it does not, the slave will silently ignore the message.  Such long CRC messages will return the OK (as opposed to CRCOK) status to indicate this limitation to the host.

Message Acknowledgement

The Philips  I2C specification provides an acknowledge bit for each byte transferred.  No explicit provision is made for acknowledging the acceptance of a message as a whole.  It is simply assumed that, if each byte was transferred successfully, the entire message must be OK.  However, if messages utilize a CRC code to improve data integrity, it is likely that a transient bus error will cause the CRC received at the end of the message not to match the expected value.

One might think that NACKing the CRC byte received would be an efficient way of informing the sender that the erroneous message should be resent.  Unfortunately, most message controllers do not allow firmware to NACK a byte that was successfully received.  The gateway provides two functionally equivalent methods to workaround this limitation in typical I2C controllers.

To confirm strictly with the I2C standard, byte oriented messages must always be transferred, each prefixed by a slave address.  Thus, the most straightforward way for a receiver to acknowledge a message is for the sender to explicitly read one byte from it after each message sent to it.  That byte is either an message ACK or NACK code.  Each message written incurs the overhead of this additional “message acknowledge” read.  A master writing a CRC checked message to a slave always follows the write with a repeated start rather than an I2C STOP condition so that the bus remains locked between the write and status read message transfers.  The normal slave response is prefixed by the message acknowledge byte only if the read begun with a I2C “repeated start”.  This standards compliant mode of CRC verification is selected by clearing the MSB of CRCNACK.

Setting the MSB of CRCNACK allows slaves to send back a more efficient (but non-bus compliant) single bit representing message acknowledge.  Each message sent by the master is ended with a repeated start as in the “standard” method described above, but the slave responds to the repeated start in a non-standard way.  First, the master releases SDA and the slave pulses the clock low.  The master extend this low pulse as needed. The slave optionally drives SDA low at this point to indicate an ACK rather than a NACK.  After the master has sampled SDA, it releases the SCL, which signals the slave to release SDA.  This CRC message ACK also signals an I2C STOP condition to other masters waiting on the bus and ends the transaction.  Otherwise, the master either resends the message or signals a STOP condition.  Note that, when this accelerated method for message acknowledgment is used, repeated start conditions must never be signaled by masters that do not support the single bit ACK.

Resynchronization
When embedding I2C network traffic in an RS-232 stream, the RS-232 BREAK signal is used reestablish message framing synchronization whenever either side of the RS-232 link resets or detects an error.  To resynchronize, one side signals BREAK and waits until the other side is also signaling BREAK.  Then, the host deasserts BREAK and waits for the gateway to deassert BREAK.  At this point, the gateway sends a one line text message to identify itself and is guaranteed to be awaiting the initial message after sending a linefeed (\n) character.  The first messages after resynchronization should be commands to (re-)configure the gateway.  The gateway immediately restarts on the trailing edge of the BREAK signal.  It acknowledges the hosts BREAK request after it has finished processing all pending messages.  To avoid interrupting a message in progress, the host should therefore wait for  the gateway's BREAK signal acknowledgement.