Networking and Communications

The goal this week was to (individually) make a network of two or nodes that can communicate with bus addresses, and (as a group) send a message between two projects. As I have absolutely no prior experience in this stuff, I am in over my head. My personal goal for the week was to make a NeoPixel LED change color and brighness based on the capacitive inputs of my Theremin. This might have been a lofty goal. But nevertheless, let's jump in.

The recurring theme of this class has been "spriral development," make something small work then incrementally improve it in ways you know how to complete until you achieve the desired outcome. In this spirit, I started by taking two Xiao ESP32C6's and seeing if I could send data between the two of them over WiFi. This didn't exactly work so I took a step back and worked with just a single ESP32. Can I get this one microcontroller to talk to my computer via a local WiFi network. I preloaded an example sketch from the Arduino IDE Library for ESP32s and did manage to get both microcontrollers to independently communicate over WiFi with my computer. The example code is a simple webpage with buttons to turn on and off the built-in LED on the Xiao, shown below.


Coming off the high of this major accomplishment (flashing a premade example code to my prebuilt microcontroller and having it work), I needed to find a way to have the two Xiao's talk to eachother. In this taks I first turned to a meshing library. The painless mesh library provides a useful framework for infrequent but mass data transfer over wireless using the ESP32 network communication abilities. After figuring out how to send text between the two boards, Quentin helped me spin up a code to change the color of a NeoPixel. I milled a simple board with a NeoPixel and flashed the code and, voila, I have a network of two nodes communicating with dedicated addresses. See the video of this below.

An issue with the mesh is that it doesn't support the freequency of commands that I would like for a constantly changing light as given by the Theremin. Trying to change the color in my hardcoded loop at intervals below a second resulted in stagnation and backlogs in updates.Because of this, I turned to another wireless protocol that promised a more responsive comminication framework: ESP-NOW. This protocol is well documented and can support communication in as little as 150ms. This is great since I can update the color, say, every 250ms and interpolate a color gradient between the two reads to appear slightly delayed but continuous to a viewer. Learning the library for ESP-NOW was certainly challenging because I am still getting used to C/C++, but we got there eventually and did another spiral development of starting with text data then worked to numerical data. One thing that I changed in terms of my code structure was that the original mesh communication code had human readable string data being sent and decoded to change the color. I figured this might be causing a delay on both sides due to the high computational cost of strings. For this baby color changing LED code, Quentin and I devised a simple color lookup table and a counter to indicate which color in the cycle to display. Instead of having the transmitting ESP32 do the iteration count and color lookup and string conversion followed by the receiver decoding a string, converting it to an integer, then sending it to the NeoPixel, I could just have the transmitter keep track of the count, send a single integer value as the count interation and then have the receiver use that count to pull the corresponsing color for the NeoPixel to use. This simplifies the data processing on both sides a bit and should save computational space. With this new protocol I found that 500ms intervals of updates were rather regular, but below that was somewhat taxxing. Perhaps the theremin LED will be a bit less granular than originally intended. Below is a video of the changing NeoPixel colors (and I added an origami box to diffuse the light and make it less annoying to work next to).


This is largely considered to be the hard part of the networking week. I have wireless comms between two microcontrollers that can send numerical data types and decode that into color. Great. The rest should be straightforward: wire the step response data from my Xiao SAMD21 to the transmitting ESP32 and make a little color gradient lookup algorithm on the receiver ESP32 to interpolate colors based on the theremin data (narrator: the rest was, in fact, not straightforward). Perhaps I am not meant to do anything that begins with I2, because I am 0 for 2 with I2x communication protocols. First it was I2S (since which Neil has assured me it can be done and has demonstrated it on an RP2040 - I remain skeptical that this is yet a continuation of the cruel joke that Big I2S is trying to play on us plebeians), and now it is I2C, for which I KNOW that it can be done because I have seen it done so many times but even with the best spiral development and following, verbatim, the arduino example and Xiao examples for baby I2C demos I cannot, for the life of me, get a single bite of data between two microcontrollers ON THE SAME BREADBOARD. What makes this more defeating is that I CAN SEE THE DAMN DATA ON THE OSCILLOSCOPE. I KNOW IT'S THERE. IT CANNOT HIDE FROM ME. Alas, the reciever does not want to pick up on it. I must be missing something either on the software side or hardware side, but this is where my time ends for the weekly assignment. And while I did meet the goals in the language of the assignment, I feel as if I have once again been bested by wired data communication protocols. Is it just not meant to be? Have the controllers in the simulation forgotten to install the expansion pack that enables me to use this cool feature of our false world? Who knows, but all I do know is that I will be trying again in the future of this class and I WILL get it to work. Neither I2S nor I2C can evade me forever.

Below is the code for this week:

Main Code

// 
#include esp_now.h
#include WiFi.h
#include Wire.h

uint8_t receiverAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
esp_now_peer_info_t peerInfo;

#define xiaoAddress 4
#define N_COLORS 6
int color_i = 0;

typedef struct message {
    char text[64];
    int intVal;
    float floatVal;
} message;

message myMessage; 

void messageSent(const uint8_t *macAddr, esp_now_send_status_t status) {
    Serial.print("Send status: ");
    if(status == ESP_NOW_SEND_SUCCESS){
        Serial.println("Success");
    }
    else{
        Serial.println("Error");
    }
}

void setup(){
    Serial.begin(115200);
    // delay(1000); // uncomment if your serial monitor is empty
    WiFi.mode(WIFI_STA);
    
    if (esp_now_init() == ESP_OK) {
        Serial.println("ESPNow Init success");
    }
    else {
        Serial.println("ESPNow Init fail");
        return;
    }
    
    esp_now_register_send_cb(messageSent);   

    memcpy(peerInfo.peer_addr, receiverAddress, 6);
    peerInfo.channel = 0;
    peerInfo.encrypt = false;

    if (esp_now_add_peer(&peerInfo) != ESP_OK) {
        Serial.println("Failed to add peer");
        return;
    }

  Wire.begin(xiaoAddress);                // join i2c bus with address #4
  Wire.onReceive(receiveEvent); // register event
}

void receiveEvent(int howMany)
{
  int32_t x = Wire.read();    // receive byte as an integer
  Serial.println(x);         // print the integer
}

void loop(){
    

    //char textMsg[] = "Hi Receiver, here's my data for you: ";
    //memcpy(&myMessage.text, textMsg, sizeof(textMsg));
    myMessage.intVal = color_i;
    //myMessage.floatVal = 42.42;
    esp_err_t result = esp_now_send(receiverAddress, (uint8_t *) &myMessage, sizeof(myMessage));
    //if (result != ESP_OK) {
    //    Serial.println("Sending error");
    //}

    color_i++;
    if (color_i == N_COLORS) {
      color_i = 0;
    }

    delay(1000);
}

Secondary Code


#include esp_now.h
#include WiFi.h
#include Adafruit_NeoPixel.h

#define PIN_NEO_PIXEL D0  // The ESP32 pin GPIO16 connected to NeoPixel
#define NUM_PIXELS 1      // The number of LEDs (pixels) on NeoPixel

Adafruit_NeoPixel NeoPixel(NUM_PIXELS, PIN_NEO_PIXEL, NEO_GRB + NEO_KHZ800);

typedef struct message {
  char text[64];
  int intVal;
  float floatVal;
} message;

#define N_COLORS 6

uint8_t color_lookup[N_COLORS][3] = {
  { 255, 0, 0 },
  { 255, 255, 0 },
  { 0, 255, 0 },
  { 0, 255, 255 },
  { 0, 0, 255 },
  { 255, 0, 255 }
};

message myMessage;

void messageReceived(const esp_now_recv_info* info, const uint8_t* incomingData, int len) {
  uint8_t red, green, blue;
  memcpy(&myMessage, incomingData, sizeof(myMessage));
  //Serial.printf("Transmitter MAC Address: %02X:%02X:%02X:%02X:%02X:%02X \n\r",
  //        info->src_addr[0], info->src_addr[1], info->src_addr[2], info->src_addr[3], info->src_addr[4], info->src_addr[5]);
  //Serial.print("Message: ");
  //Serial.println(myMessage.text);
  Serial.print("Integer Value: ");
  Serial.println(myMessage.intVal);
  //Serial.print("Float Value: ");
  //Serial.println(myMessage.floatVal);
  //Serial.println();

  red = color_lookup[myMessage.intVal][0];
  green = color_lookup[myMessage.intVal][1];
  blue = color_lookup[myMessage.intVal][2];
  NeoPixel.setPixelColor(0, NeoPixel.Color(red, green, blue));
  NeoPixel.show();
}

void setup() {
  Serial.begin(115200);
  // delay(1000); // uncomment if your serial monitor is empty
  WiFi.mode(WIFI_STA);

  if (esp_now_init() == ESP_OK) {
    Serial.println("ESPNow Init success");
  } else {
    Serial.println("ESPNow Init fail");
    return;
  }

  esp_now_register_recv_cb(messageReceived);

  NeoPixel.begin();
}

void loop() {}