CE351 Fall 2020
Advanced IoT Devices - ESP32
Name: Cheyenne Tucson
Email: crtucson@fortlewis.edu

Home Work 3 - Advanced IoT Devices - ESP32

1. Introduction
    The Internet of Things (IoT) is a useful way to wirelessly communicate data. Sometimes it is more efficient to send data wirelessly between master and slave MCUs. Another important concept to learn is how to save data to a SD card so that the data may be processed later.
To demonstrate one way of accomplishing these tasks, a prototype circuit of a portable MPU6050 accelerometer is designed using two ESP32 modules and a SD holster. The prototype will then be turned into a wearable PCB, and any necessary device libraries will be created for EaglePCB.



2. Materials


3. Procedure
    For this lab, we started by learning how to use the ESP32 module's "boot button" to upload code. We did this by uploading a simple sketch to blink the blue LED on one of the ESP32 modules.
A demonstration video can be seen below.




Next, the MPU6050 was connected to the ESP32 and the data was displayed in the serial plotter. No conversions were made before plotting the data.








Once the data was successfully aquired and sent to the ESP32, the data type was changed from a fixed-length integer to a float, and the data itself needed to be converted to g's in the +/- 4g range.
This took a few steps to figure out. First the data was just redeclared as float type and converted to the +/- 4g range. As shown in the figure below, this did not work correctly.
The minimum value is 0; For acceleration, the sign indicates the direction of movement. Another method was needed to correctly display the data.



The next thing attempted was to remove the conversion factor but to leave the data type as float. The figure below shows that the changes made very little progress if any at all. The minimum value remains 0.
Still another method was needed to correctly display the acceleration data.




After that was done, to revert back to the fixed-length integer type int16_t while still leaving the conversion factor out of the sketch. The graph below shows that this removed the lower bound; however, the data is all integer
types, and the best way to accurately represent data, especially after being converted to differnt units, is to show a decimal value. The data still needed to be converted to g's also.



The conversion factor was represented as a float to attempt to prevent integer division, but that did not work. The figure below is a screen snip of the serial plotter when the conversion factor was added back in while leaving int16_t as the data type.



The next attempt was to declare a float type variable. These new variables were assigned the value of the int16_t data after being converted by the float conversion factor. As shown in the figure below, this worked! A joyous moment!
Thank you Dr. Li for suffering through that for us!




A demonstration video of the converted data being plotted in real time.


Once the data was situated to be recorded properly, the next step was to record the data to a SD card through Serial Peripheral Interface (SPI) communication. SPI was used because it is a synchronous data bus and is great for master-slave chains.
A microSD card adapter was connected to the ESP module, and a sketch was uploaded to test the communication between the ESP32 and the microSD adapter.



The data was stored on the microSD card in real time. It was stored in a text file titled "AccX.txt"


The code was modified to store the data from the X, Y, and Z axes respectively in separate files. File names kept the same format.


The data was then taken from the files and a Python script was written to plot the data using the matplotlib.pyplot library.
The data plot can be shown in the figure below.



The code was modified again to ensure that the data type being plotted was infact of type float and in the +/- 4g range. The new data was put into a similar Python script to be represented in a graph. The graph is shown in the figure below.



The next step was to create the master-slave circuit. First, the MAC address was checked for each ESP32 module.


The MAC address of the "master" ESP32 module is on the right. The MAC address of the "slave" ESP32 module is on the left.


Snippets of the scripts used for the master and slave ESP modules are shown in the figure below. The master script is shown on the right, and the slave script is shown on the left.
The completed OnDataRecv function is highlighted in yellow.




A video demonstration of the master-slave prototype circuit.


Finally, the microSD card adapter was connected to the receiving module, and the code for the microSD adapter was added into the sketches to allow the data to be saved onto the SD card.
This figure shows the separate files and the script.


As a finishing touch, the code was cleaned up; Any lines only for debugging purposes were removed, extra conversions were removed, and the sketches were left with the bare minimum code required to function properly.
The final data set saved to the microSD card was plotted using a Python script and the matplotlib.pyplot library. The graph can be seen in the following figure.



-------------------------------------------------------------------------------------
The code sketches before refactoring:.

// Task 7/8 master, the ESP/accelerometer board, works! :)
// Reference: https://randomnerdtutorials.com/esp-now-two-way-communication-esp32/
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include<esp_now.h>
#include<WiFi.h>
#include<Wire.h>
const int MPU=0x68;//Device address
float AccX, AccY, AccZ;
int16_t AccX_temp, AccY_temp, AccZ_temp;
String success;
uint8_t broadcastAddress[] = {0x24, 0x62, 0xAB, 0xF2, 0x8E, 0x1C}; // Receiver's mac address

typedef struct struct_message{
    float AccX;
    float AccY;
    float AccZ;
} struct_message;

struct_message MPU6050Readings;

void setup(){
    Wire.begin(); //To redefine the I2C pins: Wire.begin(SDA,SCL) or Wire.begin(SDA, SCL, Bus_Speed).
  // Wake up the sensor (reset)
    Wire.beginTransmission(MPU);
    Wire.write(0x6B);//Wake up the MPU chip
    Wire.write(0x00);
    Wire.endTransmission(true);
    Wire.beginTransmission(MPU);
    Wire.write(0x1C);//Talk to the ACCEL_CONFIG register (1C hex)
    Wire.write(0x08);//Set the register bits as 00001000 (+/- 4g full scale range)
    Wire.endTransmission(true);     
    Serial.begin(115200); // It's fine to use a higher speed other than 9600 but remember to change the rate in your serial monitor
    WiFi.mode(WIFI_STA);
    // Init ESP-NOW
    if (esp_now_init() != ESP_OK) {
      Serial.println("Error initializing ESP-NOW");
      return;
    }
    // Once ESPNow is successfully Init, we will register for Send CB to get the status of Trasnmitted packet
//      esp_now_register_send_cb(OnDataSent);
    // Register peer
    esp_now_peer_info_t peerInfo;
    memcpy(peerInfo.peer_addr, broadcastAddress, 6);
    peerInfo.channel = 0;
    peerInfo.encrypt = false;
    // Add peer      
    if (esp_now_add_peer(&peerInfo) != ESP_OK){
      Serial.println("Failed to add peer");
      return;
  }
}
void loop(){
    accRead();
    MPU6050Readings.AccX=AccX;
    MPU6050Readings.AccY=AccY;
    MPU6050Readings.AccZ=AccZ;
    esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &MPU6050Readings, sizeof(MPU6050Readings)); //Send the data. &MPU6050Readings is just a 8-bit character pointer to store the data
//    if (result == ESP_OK) { // For debug purpose
//      Serial.println("Sent with success");
//    }
//    else {
//      Serial.println("Error sending the data");
//    }
}
void accRead(){ // read the MPU data to ESP32
    Wire.beginTransmission(MPU);
    Wire.write(0x3B);//Start with register 0x3B
    Wire.endTransmission(false);
    Wire.requestFrom(MPU,6,true);// Read 6 registers total
    AccX_temp=Wire.read()<<8 | Wire.read();
    AccX=AccX_temp/8192.0;
    AccY_temp=Wire.read()<<8 | Wire.read();
    AccY=AccY_temp/8192.0;
    AccZ_temp=Wire.read()<<8 | Wire.read();
    AccZ=AccZ_temp/8192.0;
    Wire.endTransmission(true);
}
// Callback when data is sent, for debug purpose
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  Serial.print("\r\nLast Packet Send Status:\t");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
  if (status ==0){
    success = "Delivery Success :)";
  }
  else{
    success = "Delivery Fail :(";
  }
}
-------------------------------------------------------------------------------------

// Task 8 Slave - Save data to SD, works! :)
//https://randomnerdtutorials.com/esp-now-two-way-communication-esp32/
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include<esp_now.h>
#include<WiFi.h>
float incomingAccX, incomingAccY, incomingAccZ;

typedef struct struct_message{
  float AccX;
  float AccY;
  float AccZ;
} struct_message;

struct_message incomingReadings;

// Callback when data is received
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
//  Serial.print("Bytes received: "); // For debug purpose
//  Serial.println(len);
  incomingAccX=incomingReadings.AccX;
  incomingAccY=incomingReadings.AccY;
  incomingAccZ=incomingReadings.AccZ;// Complete the function by filling out the blocked area
}

void setup(){
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);
   
    // Init SD adapter
    if(!SD.begin()){
        Serial.println("Card Mount Failed");
        return;
    }
   
    // Init ESP-NOW
    if (esp_now_init() != ESP_OK) {
      Serial.println("Error initializing ESP-NOW");
      return;
    }
// Register for a callback function that will be called when data is received
    esp_now_register_recv_cb(OnDataRecv);
}

void loop(){
  uint8_t cardType = SD.cardType();
    if(cardType == CARD_NONE){
        Serial.println("No SD card attached");
        return;
    }

  appendFile(SD, "/AccX.txt", incomingAccX);
  appendFile_char(SD, "/AccX.txt", ",");
  appendFile(SD, "/AccY.txt", incomingAccY);
  appendFile_char(SD, "/AccY.txt", ",");
  appendFile(SD, "/AccZ.txt", incomingAccZ);
  appendFile_char(SD, "/AccZ.txt", ",");

 
  // For debugging purposes 
  Serial.print(incomingAccX);
  Serial.print(" ");
  Serial.print(incomingAccY);
  Serial.print(" ");
  Serial.println(incomingAccZ);

}

void appendFile(fs::FS &fs, const char * path, float message){ // changed the argument type to int16_t

    File file = fs.open(path, FILE_APPEND);
    if(!file){
        Serial.println("Failed to open file for appending");
        return;
    }
    if(file.print(message)){
        Serial.println("Message appended");
    } else {
        Serial.println("Append failed");
    }
    file.close();
}
void appendFile_char(fs::FS &fs, const char * path, const char * message){ // added an 'appendFile_char' function to append ',' as data separators
    Serial.printf("Appending to file: %s\n", path);

    File file = fs.open(path, FILE_APPEND);
    if(!file){
        Serial.println("Failed to open file for appending");
        return;
    }
    if(file.print(message)){
        Serial.println("Message appended");
    } else {
        Serial.println("Append failed");
    }
    file.close();
}
-------------------------------------------------------------------------------------

The sketches after refactoring:

// Example 9, master, the ESP/accelerometer board, works! :)
// Reference: https://randomnerdtutorials.com/esp-now-two-way-communication-esp32/
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include<esp_now.h>
#include<WiFi.h>
#include<Wire.h>
const int MPU=0x68;//Device address

int16_t AccX_temp, AccY_temp, AccZ_temp;
String success;
uint8_t broadcastAddress[] = {0x24, 0x62, 0xAB, 0xF2, 0x8E, 0x1C}; // Receiver's mac address

typedef struct struct_message{
    int16_t AccX;
    int16_t AccY;
    int16_t AccZ;
} struct_message;

struct_message MPU6050Readings;

void setup(){
    Wire.begin(); //To redefine the I2C pins: Wire.begin(SDA,SCL) or Wire.begin(SDA, SCL, Bus_Speed).
    Wire.beginTransmission(MPU);
    Wire.write(0x6B);//Wake up the MPU chip
    Wire.write(0x00);
    Wire.endTransmission(true);
    Wire.beginTransmission(MPU);
    Wire.write(0x1C);//Talk to the ACCEL_CONFIG register (1C hex)
    Wire.write(0x08);//Set the register bits as 00001000 (+/- 4g full scale range)
    Wire.endTransmission(true);     
    WiFi.mode(WIFI_STA);
    // Init ESP-NOW
    if (esp_now_init() != ESP_OK) {
      return;
    }
   
    // Register peer
    esp_now_peer_info_t peerInfo;
    memcpy(peerInfo.peer_addr, broadcastAddress, 6);
    peerInfo.channel = 0;
    peerInfo.encrypt = false;
    // Add peer      
    if (esp_now_add_peer(&peerInfo) != ESP_OK){
      return;
  }
}

void loop(){
    accRead();
    MPU6050Readings.AccX=AccX_temp;
    MPU6050Readings.AccY=AccY_temp;
    MPU6050Readings.AccZ=AccZ_temp;
    esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &MPU6050Readings, sizeof(MPU6050Readings)); //Send the data. &MPU6050Readings is just a 8-bit character pointer to store the data
}
void accRead(){ // read the MPU data to ESP32
    Wire.beginTransmission(MPU);
    Wire.write(0x3B);//Start with register 0x3B
    Wire.endTransmission(false);
    Wire.requestFrom(MPU,6,true);// Read 6 registers total
    AccX_temp=Wire.read()<<8 | Wire.read();
    AccY_temp=Wire.read()<<8 | Wire.read();
    AccZ_temp=Wire.read()<<8 | Wire.read();
    Wire.endTransmission(true);
}
-------------------------------------------------------------------------------------

// Task 9 Slave - Remove conversions and debugging code, then save data to SD, works! :)
//https://randomnerdtutorials.com/esp-now-two-way-communication-esp32/
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include<esp_now.h>
#include<WiFi.h>
int16_t incomingAccX, incomingAccY, incomingAccZ;

typedef struct struct_message{
  int16_t AccX;
  int16_t AccY;
  int16_t AccZ;
} struct_message;

struct_message incomingReadings;

// Callback when data is received
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));

  incomingAccX=incomingReadings.AccX;
  incomingAccY=incomingReadings.AccY;
  incomingAccZ=incomingReadings.AccZ;
}

void setup(){
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);
   
    // Init SD adapter
    if(!SD.begin()){
        return;
    }
   
    // Init ESP-NOW
    if (esp_now_init() != ESP_OK) {
      return;
    }
// Register for a callback function that will be called when data is received
    esp_now_register_recv_cb(OnDataRecv);
}

void loop(){
  uint8_t cardType = SD.cardType();
    if(cardType == CARD_NONE){
        return;
    }

  appendFile(SD, "/AccX.txt", incomingAccX);
  appendFile_char(SD, "/AccX.txt", ",");
  appendFile(SD, "/AccY.txt", incomingAccY);
  appendFile_char(SD, "/AccY.txt", ",");
  appendFile(SD, "/AccZ.txt", incomingAccZ);
  appendFile_char(SD, "/AccZ.txt", ",");
}

void appendFile(fs::FS &fs, const char * path, int16_t message){ // changed the argument type to int16_t

    File file = fs.open(path, FILE_APPEND);
    if(!file){
        return;
    }
    file.close();
}
void appendFile_char(fs::FS &fs, const char * path, const char * message){ // added an 'appendFile_char' function to append ',' as data separators
    Serial.printf("Appending to file: %s\n", path);

    File file = fs.open(path, FILE_APPEND);
    if(!file){
        return;
    }
    file.close();
}