NETWORKING AND COMMUNICATIONS

Individual assignments

  • Design, build, and connect wired or wireless node(s) with network or bus addresses and local input &/or output device(s)

For this week, work was focused on communications and information exchange between devices. Knowing this, for this week it was decided to work with WiFi networks. Due to my major, I never had the opportunity to program or use microcontrollers that could be capable of using WiFi. Likewise, I decided to use this type of communication because I want to create a page where data collection and visualization can be observed. In the same way, I would love to integrate a system where I could control my robot remotely. Therefore, I consider that working with WiFi is my ideal option.

Sensor

How Networking and Communications work

The connection between devices has different applications or functions, all depending on what is intended to be done. Among them, the following can be found:

  1. Location: it is known where a device is within the network.
  2. Parallelism: allows many devices to work at the same time.
  3. Modularity: allows separating data. For example, one sensor measures, another processes, and another sends data.
  4. Interference: allows avoiding signals from mixing or colliding.

For these to work, they need a device that generates the data, then this is converted into signals (electrical, radio, light), they travel through a medium, and another device receives and decodes the information. This is a general idea of how network communications work.

Types of communication

There are several types of communication (also that can be chosen for this week).

Sensor

This type of communication uses physical media to share information. It can be through wiring, using voltage or optical fiber. But it can also be wireless, such as radio frequency (in the case of Wi-Fi), light, sound.

What did I do in my week 11?

As I had already explained, during this week I worked with Wi-Fi HTTP. The way it works is by connecting the microcontroller being used to Wi-Fi and converting it into an HTTP web server. I relied on this page, which has very valuable information about networking.

Sensor

First, I designed a board in Altium using the XIAO ESP32C6 microcontroller. In this way, I relied on what was seen in week 6 and week 8. Afterwards, I did the following process.

1.Connection of the board to the computer

Sensor

2. Open ARDUINO IDE

3.Modify, change, or select the type of board that is being used. In my case, a XIAO ESP32C6.

Sensor

4. Test that the code can be uploaded to the board to confirm that everything is correctly connected.

Sensor

5. Perform programming code


#include <WiFi.h>

const char* ssid = "Piso 03";
const char* password = "123456789";

WiFiServer server(80);

void setup() {
  Serial.begin(115200);

  WiFi.begin(ssid, password);

  Serial.println("Conectando al WiFi...");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\nConectado!");
  Serial.print("IP: ");
  Serial.println(WiFi.localIP());

  server.begin();
}

void loop() {
  WiFiClient client = server.available();

  if (client) {
    Serial.println("Nuevo cliente");

    String request = client.readStringUntil('\r');
    Serial.println(request);
    client.flush();
    //Creación de página
    client.println("HTTP/1.1 200 OK");
    client.println("Content-type:text/html");
    client.println();

    client.println("<!DOCTYPE html>");
    client.println("<html>");
    client.println("<h1>Hola desde mi XIAO ESP32C6</h1>");
    client.println("</html>");

    client.println();
    client.stop();
  }
}

6. Once the program is uploaded to the microcontroller and it is tested that it works correctly, the IP given by the Serial Monitor was copied and pasted into a web browser. In this way, you can observe the created page.

Sensor Sensor

7. Afterwards, you will be able to observe that there is a client or that this page has been visited.

Sensor

8. Finally, I modified some settings so that the page had a better presentation.


#include <WiFi.h> //Library that allows connecting my board to WiFi

//WiFi credentials are set here:
const char* ssid = "Piso 03"; //network name
const char* password = "123456789"; //network password

WiFiServer server(80); //Creates a web server on the standard port (HTTP), hence 80

//Sensor pin definitions
const int trigPin = D2; //sends pulse
const int echoPin = D1; //receives the bounce

bool sensorActive = false; //Allows knowing if the sensor is active or not

void setup() {
  Serial.begin(115200); //Allows viewing messages in the serial monitor
  
  //Configures the pins
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  //WiFi connection - starts connection
  WiFi.begin(ssid, password);

  //Waits until connected and prints dots while waiting
  Serial.println("Connecting...");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  //Prints or shows the IP in the Serial Monitor to access from browser
  Serial.println("\nConnected!");
  Serial.println(WiFi.localIP());
  //Starts web server
  server.begin();
}
//Function that allows measuring distance with the sensor
float measureDistance() {
  //Generates a pulse
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);

  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  //Measures the time it takes for the sound to return
  long duration = pulseIn(echoPin, HIGH);
  //Calculates the distance
  float distance = duration * 0.034 / 2;
  //Returns the distance in cm
  return distance;
}

void loop() {
  //Detect if someone connects to the browser
  WiFiClient client = server.available();
  //If there is a connection, read what the browser requested
  if (client) {
    String request = client.readStringUntil('\r');
    //Clears extra data
    client.flush();

    // If the button is pressed
    if (request.indexOf("/toggle") != -1) {
      //Change from OFF to ON or vice versa
      sensorActive = !sensorActive;

      client.println("HTTP/1.1 200 OK");
      client.println("Content-type:text/plain");
      client.println();
      client.println(sensorActive ? "ON" : "OFF");
      client.stop();
      return;
    }

    // Browser requests sensor data
    if (request.indexOf("/data") != -1) {
      client.println("HTTP/1.1 200 OK");
      client.println("Content-type:text/plain");
      client.println();
      //If active, measure distance or send
      if (sensorActive) {
        float distance = measureDistance();
        client.println(distance);
      } else {
        client.println("OFF");
      }

      client.stop();
      return;
    }

    // Web page
    client.println("HTTP/1.1 200 OK");
    client.println("Content-type:text/html");
    client.println();

    //Write HTML within the code without errors
    client.println(R"rawliteral(
<!DOCTYPE html> 
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style> 
body {
  font-family: Arial;
  text-align: center;
  background: #0f172a;
  color: white;
}
h1 {
  margin-top: 50px;
}
button {
  padding: 15px 30px;
  font-size: 18px;
  border: none;
  border-radius: 10px;
  background-color: #38bdf8;
  color: black;
  cursor: pointer;
}
button:hover {
  background-color: #0ea5e9;
}
#distance {
  font-size: 40px;
  margin-top: 30px;
}
</style>
</head>

<body>

<h1>WELCOME TO SENSOR TESTING</h1>

<button onclick="toggleSensor()">Activate/Deactivate</button>

<div id="distance">Sensor off</div>

<script>
function toggleSensor() {
  fetch('/toggle')
  .then(r => r.text())
  .then(d => {
    console.log("Status:", d);
  });
}

function update() {
  fetch('/data')
  .then(r => r.text())
  .then(d => {
    if(d === "OFF"){
      document.getElementById('distance').innerHTML = "Sensor off";
    } else {
      document.getElementById('distance').innerHTML = d + " cm";
    }
  });
}

setInterval(update, 1000);
</script>

</body>
</html>
)rawliteral");

    client.stop();
  }
}

9. This ended up being my final result.

Sensor

Results

This code allows me to display in real time the data collected by my sensor. In this way, you can visualize the measured distances, as well as have control over turning the sensor on and off.

How does the programming work?

Based on the first code that was made, the logic it follows is:

Regarding the libraries, the following are used:

UART Communication Type

Likewise, due to my project, it was important for me to implement another type of communication. This is UART, that is, Universal Asynchronous Receiver-Transmitter. It is a type of communication protocol that allows the exchange of information between two devices using two signal lines, TX and RX.

This type of communication can be programmed in:

  • Arduino IDE: Serial, Serial1, Serial2, etc.
  • MicroPython: using machine.UART()
  • RaspberryPi Pico SDK: in C/C++
  • PlatformIO

Among many others. This type of communication is one of the oldest; however, it still has many applications. It is very simple to use, since it only requires two data wires and one GND. It can be used with any microcontroller and does not require a master device.

Sensor

For my project, this type of communication will be widely used, since I will use two microcontrollers: one to receive data from the sensors I use and the other to move the motors according to that data. Based on this, I carried out a practice that demonstrates the use of this type of connection.

Practice

It consists of connecting two PCBs with XIAO ESP32C6 and XIAO RP2350 microcontrollers, each on its respective board. The connection was made using female-to-female wires, and code was developed in which, when my ESP32C6 received the button pulse, the motor connected to the RP2350 would move forward. To achieve this, two separate codes were used, corresponding to each board. These codes were uploaded to both boards, and specifically the XIAO ESP32C6 displayed real-time data about what was being performed.

In this way, I managed to obtain the following result:

This type of practice is very important, since it allowed me to learn about another type of communication that I will definitely use in my final project.This are the codes used:

Code for ESP32C6


#include 

HardwareSerial SerialUART(1);

#define BOTON  D0

void setup() {
  Serial.begin(115200);
  SerialUART.begin(9600, SERIAL_8N1, D7, D6);
  pinMode(BOTON, INPUT_PULLUP);
  Serial.println("ESP32C6 listo");
}

void loop() {
  if (digitalRead(BOTON) == HIGH) { 
    SerialUART.println("motor:ON");
    Serial.println("motor:ON");
  } else {
    SerialUART.println("motor:OFF");
    Serial.println("motor:OFF");
  }
  delay(50); 
}

Code for RP2350


#define IN3  A2
#define IN4  A1

void setup() {
  Serial.begin(115200);
  Serial1.begin(9600);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, LOW);
  Serial.println("RP2350 listo");
}

void loop() {
  if (Serial1.available()) {
    String cmd = Serial1.readStringUntil('\n');
    cmd.trim();

    Serial.print("Comando recibido: ");
    Serial.println(cmd);

    if (cmd == "motor:ON") {
      digitalWrite(IN3, HIGH);
      digitalWrite(IN4, LOW);
      Serial.println("Motor girando...");
    } else if (cmd == "motor:OFF") {
      digitalWrite(IN3, LOW);
      digitalWrite(IN4, LOW);
      Serial.println("Motor detenido.");
    }
  }
}

Finally, these are some questions I had while working with this type of communication:

I2C Communication

This type of communication is called Inter-Integrated Circuit. It is a type of communication that allows two devices to communicate with each other using the smallest number of wires possible. In general, it can be observed that:

Sensor

Where all connected devices share the same two wires, which are called a bus.

Therefore, for this type of communication, there are two roles: Master (starts the communication) and Slave (waits to be addressed). It is necessary to consider that, for this type of communication to work, pull-up resistors must be used, since I2C devices use a logic called open-drain. This type of communication is ideal for multiple sensors and few pins.

For this practice, a XIAO ESP32C6 (using the board designed for the final project) will be connected with an OLED SH1106 display and a VL53L0X distance sensor to measure distances. Finally, the code used is the following:


#include 
#include 
#include 
#include 
#include 

#define i2c_Address 0x3C
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define XSHUT_PIN D0

Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_VL53L0X sensor = Adafruit_VL53L0X();

void setup() {
  Serial.begin(115200);
  delay(200);

  // Turn on sensor
  pinMode(XSHUT_PIN, OUTPUT);
  digitalWrite(XSHUT_PIN, HIGH);
  delay(10);

  Wire.begin(D4, D5);

  // Start display
  if (!display.begin(i2c_Address, true)) {
    Serial.println("OLED not found");
    for(;;);
  }

  // Start sensor
  if (!sensor.begin()) {
    Serial.println("VL53L0X not found");  // In case something does not work
    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(SH110X_WHITE);
    display.setCursor(0, 25);
    display.println("Error: I2C sensor");
    display.display();
    for(;;);
  }

  // Startup screen
  display.clearDisplay();
  display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SH110X_WHITE);
  display.setTextSize(1);
  display.setTextColor(SH110X_WHITE);
  display.setCursor(20, 10);
  display.println("XIAO ESP32C6");
  display.setCursor(20, 22);
  display.println("I2C bus ready");
  display.setCursor(10, 34);
  display.println("OLED  |  VL53L0X");
  display.setCursor(10, 46);
  display.println("Welcome");
  display.display();
  delay(2000);
}

void loop() {
  VL53L0X_RangingMeasurementData_t medicion;
  sensor.rangingTest(&medicion, false);

  display.clearDisplay();
  display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SH110X_WHITE);

  display.setTextSize(1);
  display.setTextColor(SH110X_WHITE);
  display.setCursor(25, 5);
  display.println("VL53L0X Sensor");
  display.drawLine(0, 16, 127, 16, SH110X_WHITE);

  if (medicion.RangeStatus != 4) {
    int distancia = medicion.RangeMilliMeter;

    Serial.print("Distance: ");
    Serial.print(distancia);
    Serial.println(" mm");

    display.setCursor(10, 22);
    display.setTextSize(1);
    display.println("Distance:");
    display.setTextSize(3);
    display.setCursor(10, 35);
    display.print(distancia);
    display.print(" mm");

  } else {
    Serial.println("Out of range");
    display.setTextSize(1);
    display.setCursor(15, 30);
    display.println("Out of range");
  }

  display.display();
  delay(200);
}

This code uses the libraries to manage I2C communication, the basic graphics, the display controller, and the sensor (defined with #include). Likewise, the I2C addresses of the display, the screen width and height, the pin responsible for turning the sensor on or off, and the setting that the display does not have a reset pin are defined (using #define). From there, the objects used for communication are created and the programs are executed.

From this, it is possible for two different devices that share the same wires to communicate without confusion because each one has its own address. The final result is the following:

ESP-NOW

Now, in order to connect two ESP32C6 boards, I used ESP-NOW. This protocol works as a direct messaging system between boards, where the transmitter needs to know the destination device to which the data will be sent. This identifier is known as the MAC address. To obtain it, the following code is first uploaded:


#include 
#include 

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  Serial.print("MAC del receptor: ");
  Serial.println(WiFi.macAddress());
}

void loop() {}

Once this is done, the board outputs its MAC address through the serial monitor. This address is then copied into the transmitter board. These values correspond to the MAC address bytes.

The transmitter code defines the data structure that will be sent. It is important that the receiver uses the exact same data structure to correctly interpret the incoming information. Additionally, the transmitter initializes the VL53L0X sensor, starts ESP-NOW communication, and repeatedly sends the collected data. The code is shown below:


#include 
#include 
#include 
#include 
#include "VL53L0X.h"


#define XSHUT_PIN  D2
#define SDA_PIN    D4
#define SCL_PIN    D5


uint8_t MAC_Receptor[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // REEMPLAZA_CON_TU_MAC


typedef struct Mensaje {
  uint16_t distancia_mm;
} Mensaje;

Mensaje datos;

TwoWire I2C_sensor = TwoWire(0);
VL53L0X sensor;

void onDataSent(const wifi_tx_info_t* info, esp_now_send_status_t status) {

}

void setup() {
  Serial.begin(115200);

  // Inicializar VL53L0X
  pinMode(XSHUT_PIN, OUTPUT);
  digitalWrite(XSHUT_PIN, LOW);
  delay(10);
  digitalWrite(XSHUT_PIN, HIGH);
  delay(10);

  I2C_sensor.begin(SDA_PIN, SCL_PIN);
  sensor.setBus(&I2C_sensor);
  if (!sensor.init()) {
    Serial.println("ERROR: VL53L0X no encontrado");
    while (1);
  }
  sensor.setTimeout(500);
  sensor.startContinuous();
  Serial.println("VL53L0X listo");

  // Inicializar ESP-NOW
  WiFi.mode(WIFI_STA);
  if (esp_now_init() != ESP_OK) {
    Serial.println("ERROR: ESP-NOW no inició");
    while (1);
  }
  esp_now_register_send_cb(onDataSent);

  esp_now_peer_info_t peer = {};
  memcpy(peer.peer_addr, MAC_Receptor, 6);
  peer.channel = 0;
  peer.encrypt = false;
  if (esp_now_add_peer(&peer) != ESP_OK) {
    Serial.println("ERROR: No se pudo agregar el receptor");
    while (1);
  }

  Serial.println("Transmisor listo");
}

void loop() {
  uint16_t lectura = sensor.readRangeContinuousMillimeters();
  if (!sensor.timeoutOccurred()) {
    datos.distancia_mm = lectura;
    esp_now_send(MAC_Receptor, (uint8_t *)&datos, sizeof(datos));
    Serial.print("Enviado: ");
    Serial.print(datos.distancia_mm);
    Serial.println(" mm");
  }
  delay(50);
}

While the receiver code listens for and accepts messages from any address. The system is designed so that a callback function is automatically executed whenever new data is received. The received information is then extracted and converted into the previously defined data structure. The corresponding code is shown below:


#include 
#include 
#include 


typedef struct Mensaje {
  uint16_t distancia_mm;
} Mensaje;

Mensaje datos;

void onDataReceived(const esp_now_recv_info_t* info, const uint8_t* data, int len) {
  memcpy(&datos, data, sizeof(datos));
  Serial.print("Distancia recibida: ");
  Serial.print(datos.distancia_mm);
  Serial.println(" mm");
}

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);

  Serial.print("MAC de este receptor: ");
  Serial.println(WiFi.macAddress());

  if (esp_now_init() != ESP_OK) {
    Serial.println("ERROR: ESP-NOW no inició");
    while (1);
  }
  esp_now_register_recv_cb(onDataReceived);

  Serial.println("Receptor listo");
}

void loop() {
  delay(10);
}

Finally, the obtained results are:

Conclusions

It is important to understand connections and how they work so that communication between devices can be established to control systems remotely, view data in real time, exchange information, and automation. During this week, it was achieved that the XIAO ESP32C6 board acted as a server capable of interacting with a user through a web page.

If you want to access to my work from this week, please click here to download!

Finally, for the group assignment for this week, you can find the information here