Close

Ordering a double Latte in C++

A project log for Cardware

An educational system designed to bring AI and complex robotics into the home and school on a budget.

morningstarMorning.Star 10/12/2017 at 08:340 Comments

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