OK so now I'm wandering around muttering code. As you may be aware I design out of my head and document the results after building, and code is no exception. Somehow, I've managed to build a multiprocessor semaphore that ties two identical pieces of code to a third very similar one, all three run transparently and interact with each other and its puddled my brain lol. I dont even drink coffee either. ;-)
The processor board has two Atmega 328 and one ESP8266, and they are networked together by joining the TX pin on each to the RX pin on the next to make a triangle with the processors on each corner.
Obviously they will need to pass data internally from RX to TX, and this would loop forever without some kind of semaphore so I have defined a protocol.
The data is sent using a 5-byte packet.
Byte 1 contains the header - two 4-bit numbers that specify where the packet came from and where its going to. They are bit-wise so that a processor may address both the other at once by combining their ID bits.
Byte 2 contains a command code and parameters - 3 bits tell the system that the packet is a servo instruction or a request for sensor data, or sensor data itself. 4 more bits optionally give the servo number if it is a servo instruction, and bit 8 is always spare for now.
Bytes 3 - 5 contain data in an arbitrary format. For a servo instruction byte 1 gives the angle and byte 2 gives the number of steps to take to move to it from the current angle, byte 3 is blank. For a data request all three are blank, and for a sensor data packet the three bytes contain the three angles from the sensors.
The Atmega 328 code
Each processor reads its RX and looks at the header. If the source ID is the same as its own, the packet has made a circuit and is deleted. If it is for another processor it is passed to TX, after checking to see if it is also for this processor. if it is, the ID is removed from the header and the packet passed to TX. Any data for the processor is processed and sent to servos, or responded to with a sensor packet.
//#include "EEPROM.h"
#include <Servo.h>
const int host=4; // this processor
const int servos=5;
// servo definition structure:
// articulation
// |____ servo (servo object)
// | |____ attach()
// | |____ write()
// |____ pin (physical pin number)
// |____ min (minimum range of movement 0-255)
// |____ max (maximum range of movement 0-255)
// |____ home (home position defaults to 128; 90 degrees)
// |____ position (positional information)
// |____ next (endpoint of movement)
// |____ pos (current position as float)
// |____ last (beginpoint of movement)
// |____ steps (resolution of movement)
// |____ step (pointer into movement range)
//
// packet configuration:
// byte 1: header - 2 nybbles containing source id and destination id
// byte 2: control - bits 1-3 packet type, bits 4-7 command parameters, bit 8 spare
// byte 3: data 1 - arbitrarily assigned
// byte 4: data 2 - arbitrarily assigned
// byte 5: data 3 - arbitrarily assigned
// eg
// byte 1: 33/1000,0100 packet from processor 1 to processor 2 (esp to atmel a)
// byte 2: 9/100,1000,0 position command for servo 1
// byte 3: data 1 - angle (0-255 at the moment, will be percentage of range)
// byte 4: data 2 - steps (divisions in angular displacement)
// byte 5: data 3 - spare
// eg
// byte 1: 33/1000,0100 packet from processor 1 to processor 2 (esp to atmel a)
// byte 2: 10/010,1000,0 request for sensor data
// byte 3: data 1 - spare
// byte 4: data 2 - spare
// byte 5: data 3 - spare
// eg
// byte 1: 20/0010,1000 packet from processor 3 to processor 1 (atmel b to esp)
// byte 2: 12/001,0000,0 sensor data
// byte 3: data 1 - sensor 1
// byte 4: data 2 - sensor 2
// byte 5: data 3 - sensor 3
struct servo_position { // servo status
int next;
float pos;
int last;
int steps;
int step;
} ;
typedef struct servo_position servo_pos; // atmel c++ curiosity, substructs need a hard reference
struct servo_definition { // servo information
Servo servo;
int pin;
int min;
int max;
int home;
servo_pos position;
} ;
typedef struct servo_definition servo_def; // servo structure containing all relevant servo information
servo_def articulation[servos]; // array of servo structures describing the limb attached to it
int mins[]={ 0,0,0,0,0,0,0,0,0,0,0,0 }; // defaults for the servo ranges and positions
int maxs[]={ 255,255,255,255,255,0,0,0,0,0,0,0 };
int homes[]={ 128,128,128,128,128,0,0,0,0,0,0,0 };
void setup() {
Serial.begin(115200);
while (!Serial) { ; } // wait for the port to be available
for (int s=0; s<servos; s++) { // iterate servos
articulation[s].servo.attach(s+2); // configure pin as servo
articulation[s].pin=s+2; // echo this in the structure
articulation[s].home=homes[s]; // configure the structure from defaults
articulation[s].min=mins[s];
articulation[s].max=maxs[s];
articulation[s].position.next=homes[s];
articulation[s].position.pos=homes[s];
articulation[s].position.last=homes[s];
articulation[s].position.steps=0;
}
}
void loop() {
if (Serial.available() > 0) { // if there is a packet
unsigned char ids=Serial.read(); // read 2 bytes in; host and destination ids
unsigned char ctl=Serial.read(); // control byte
unsigned char b1=Serial.read(); // data bytes
unsigned char b2=Serial.read(); // data bytes
unsigned char b3=Serial.read(); // data bytes
int hostid=ids & 15;
int destid=(ids & 240)/16;
if (hostid & host != host and hostid>0) { // byte 1; if packet source is the same as host ignore it
if (destid & host == host) { // if host is in packet destination
if (ctl & 7 == 1) { // if its a servo command in bits 1 to 3
int srv=(ctl & 120) / 8; // byte 2; mask out servo number from byte bits 4 to 7
int agl=(int)b1; // byte 3; acquire angle
int st=(int)b2; // byte 4; acquire steps
articulation[srv].position.last=articulation[srv].position.pos; // capture current position as last position
articulation[srv].position.next=agl; // write new angle and steps into destination position
articulation[srv].position.steps=st;
articulation[srv].position.step=st;
destid=destid ^ host; // xor out this host from packet destination
}
if (ctl & 7 == 2) { // if its a sensor data request send a sensor packet
Serial.write(host+32); // byte 1 : host id plus destination bit 1; ESP
Serial.write(4); // byte 2 : set bit 3; its a sensor packet
Serial.write((int)analogRead(A0)/4); // bytes 3 - 5 : sensor data
Serial.write((int)analogRead(A1)/4);
Serial.write((int)analogRead(A2)/4);
}
if (destid>0) { // if there is another packet destination
ids=hostid+(destid*16); // pack the new header
Serial.write(ids); // send the packet to another processor
Serial.write(ctl);
Serial.write(b1);
Serial.write(b2);
Serial.write(b3);
}
if (ctl & 7 == 4) { // if its a sensor packet pass to another processor
Serial.write(ids);
Serial.write(ctl);
Serial.write(b1);
Serial.write(b2);
Serial.write(b3);
}
}
}
}
for (int s=0; s<servos; s++) { // iterate servo structure
int st=articulation[s].position.last; // beginning of the movement
int nd=articulation[s].position.next; // end of the movement
int stp=articulation[s].position.steps; // how many steps to take to traverse the movement
int sn=articulation[s].position.step; // current step
if (sn>0) { // if pos hasnt reached its end of travel
float ps=(((float)nd-st)/(float)stp)*sn; // calculate new pos from travel and step
articulation[s].servo.write((int)ps); // set the servo to pos
sn--; // step counter
if (sn<0) { sn=0; }
articulation[s].position.pos=ps; // update the structure
}
}
}
The ESP8266 code
This is a bit simpler in one sense, because it doesnt have sensors or servos to deal with. Code for these is missing, and extra code to handle a WiFi bridge is inserted. This is an asynchronous bridge, so its complicated and relies on a callback.
The ESP is setup as a soft AP with its own hostname and password, and has a webserver which provides a basic page containing the sensor data when a browser connects to it or reloads. This is accomplished by the webcallback routine, which will service a link from a Python program on a computer, or a phone running a MIT Appinventor app both with webkit code to interact with the processors.
Both pieces of code are as yet untested, they compile and are complete but I still have work to do before I can attach servos to any of it.
#include <ESP8266WiFi.h> // wifi stack
#include <WiFiClient.h> // connection manager
#include <ESP8266WebServer.h> // web framework
const int host=1; // this processor
const char *netw="network"; // static access point; cardware is a server
const char *pasw="password";
int lsensor[]= { 0,0,0 }; // current sensor values
int rsensor[]= { 0,0,0 };
String tmp;
ESP8266WebServer server(80); // start webserver threads
void webcallback() { // this runs when a browser connects or reloads the page
tmp=String("<h1>Connected!\nLeft leg : "); // make a page with the sensor status information on it
for (int n=0; n<3; n++) { tmp=tmp+lsensor[n]+","; } // in a temporary string
tmp=tmp+"\nRight leg : ";
for (int n=0; n<3; n++) { tmp=tmp+rsensor[n]+","; } // (this will be replaced with a semaphore between web host and esp)
tmp=tmp+"\nOK\n</h1>";
server.send(200,"text/html",tmp); // and send it to the browser
}
void setup() {
delay(1000); // wait for wifi to finish
Serial.begin(115200); // open serial port
while (!Serial) { ; } // wait for the port to be available
WiFi.softAP(netw,pasw); // make a new access point using the provided ssid and password
server.on("/",webcallback); // connect the server to the sensor data via a web-page
server.begin(); // and launch it
}
void loop() {
if (Serial.available() > 0) { // if there is a packet
unsigned char ids=Serial.read(); // read 5 bytes in; host and destination ids
unsigned char ctl=Serial.read(); // control byte
unsigned char b1=Serial.read(); // data byte
unsigned char b2=Serial.read(); // data byte
unsigned char b3=Serial.read(); // data byte
int hostid=ids & 15; // mask out host and destination ids from id byte
int destid=(ids & 240)/16; // nybble 2, shift left 4 bits
if (hostid & host != host and hostid>0) { // byte 1; if packet source is the same as host ignore it
if (destid & host == host) { // if host is in packet destination
destid=destid ^ host; // xor out this host from packet destination
if (destid>0) { // if there is another packet destination
ids=hostid+(destid*16); // pack the new header
Serial.write(ids); // send the packet to another processor
Serial.write(ctl);
Serial.write(b1);
Serial.write(b2);
Serial.write(b3);
}
if (ctl & 7 == 4) { // if its a sensor packet pass to another processor
// wifi bridge to host for now, RPi will handle it via serial eventually
if (hostid & host == 2) {
lsensor[0]=(int)(b1); // store the sensor positions
lsensor[1]=(int)(b2);
lsensor[2]=(int)(b3);
}
if (hostid & host == 4) {
rsensor[0]=(int)b1;
rsensor[1]=(int)b2;
rsensor[2]=(int)b3;
}
}
}
}
}
server.handleClient(); // poll the webserver for a connection
}
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.