1. Introduction

EDGE101 is an IoT Programmable Controller Based on the ESP32 Core with Industrial Communication Interfaces and an Industrial-Grade Design.

ESP32 and Arduino are among the most popular hardware and development platforms for developers due to their excellent performance and extensive open-source libraries. Developers have created tens of thousands of innovative IoT applications using ESP32. However, most ESP32 development boards currently available only provide basic I/O functions and lack essential communication features such as RS485, CAN bus, and Ethernet, which are crucial for real-world deployment. This limitation creates significant functional gaps in common ESP32 development boards, affecting both learning and practical applications.

EDGE101 integrates RS485, CAN bus, Ethernet, Wi-Fi, Bluetooth, and RTC functionalities within a robust metal casing. It also supports PCIe slot expansion for 4G communication, offering a comprehensive and flexible IoT control solution. Additionally, it features 11 native GPIOs of the ESP32 and 3 I2C interfaces, allowing users to complete both IoT learning and deployment within a single device.

Most ESP32 development boards on the market lack necessary protection mechanisms, making them suitable only for desktop testing rather than direct IoT application deployment. EDGE101 is designed according to industrial standards, featuring a high-quality metal casing with comprehensive isolation protection, anti-static protection, lightning protection, overvoltage protection, and reverse polarity protection circuits. With a wide power supply range of 9V-26V and multiple mounting options, including rail mounting and ear-hook mounting, EDGE101 can operate stably and flexibly in various environments such as gardens, rooftops, garages, and power distribution rooms.

EDGE101 provides a detailed, free online development manual covering everything from basic I/O control and data acquisition to RS485 and CAN bus communication, as well as Wi-Fi, Bluetooth, Ethernet, 4G communication, and cloud service examples. Below is an overview of the tutorial sections:

  • GPIO Control
  • PWM Output
  • ADC Data Acquisition
  • Serial Communication
  • I2C Communication
  • SPI Communication
  • TF Card Storage and Examples
  • Timer Functions
  • Watchdog Timer
  • CAN Bus Communication and Examples
  • RS485 Communication and Examples
  • Wi-Fi Communication and Examples
  • Bluetooth Communication and Examples
  • Ethernet Communication and Examples
  • 4G Communication Examples
  • MQTT Examples

For more details, please visit the product’s WIKI tutorials.

2. Features

  • ESP32 core controller with 240MHz clock speed, supporting Wi-Fi and Bluetooth wireless communication.
  • Industrial-grade design with comprehensive circuit protections: isolation, ESD protection, lightning protection, overvoltage protection, and reverse polarity protection.
  • Metal enclosure to prevent accidental damage from human contact, insects, or animals.
  • Isolated RS485 interface.
  • Isolated CANBUS interface.
  • 11 native ESP32 GPIOs and 4 I2C interfaces.
  • TF card slot for local data storage.
  • Built-in PCIe expansion slot and nano SIM card slot, supporting 4G communication module installation.
  • Multiple mounting options: DIN rail mounting, wall-mounted screw installation, flat mounting, or vertical installation.

3. Applications

  • Outdoor environmental data acquisition
  • Greenhouse data acquisition and automated control
  • Water ingress monitoring for warehouses, base stations, excavation sites, etc.
  • Compact automation equipment controller
  • Robotics controller

4. Function indication

Front interface diagram

No. Item Description
1 Ethernet Port (RJ45) 10/100Mbps
2 Reset Button Press to reset
3 Onboard Button User-defined button
4 LED Indicators Custom LED ×1, Wireless Communication LED ×1, Power LED ×1
5 USB Port Type-C
6 External Power Input DC 9-26V
7 RS485 Port Recommended baud rate: 9600bps | 115200bps
8 CAN-Bus Port Maximum baud rate: 1Mbps
9 Terminal Resistor 120Ω (RS485 ×1, CAN-Bus ×1)
10 Wi-Fi Antenna 2.4G Wi-Fi & Bluetooth antenna
11 External Antenna Supports installation of additional wireless modules (not included). Functions as 4G antenna if a 4G module is added.

Back interface diagram

No. Item Description
1 Grounding Terminal Grounding connection
2 SD Card Slot Supports up to 32 GB
3 SIM Card Slot Nano SIM compatible
4 Gravity GPIO Interface P5 P12 P14 P15 P18 P23 P33 P34 P37 P38 P39 (11 channels total)
5 Gravity I2C Interface 3 groups (provides 3V3 | 5V VCC)

5. Specification

Hardware Specifications

Category Parameter
CPU Model Dual-core ESP32 32-bit processor
CPU Architecture Xtensa 32-bit LX6
CPU Frequency 240MHz, up to 600MIPS performance
On-chip Flash 16MB
Memory 520KB SRAM, 16KB RTC SRAM

Hardware Interfaces

Category Quantity Specifications
Wi-Fi 1 2.4GHz, 802.11 b/g/n, up to 150Mbps, supports STA/AP/STA+AP modes
Bluetooth 1 BT4.2/BLE5.0/BLE Mesh networking
Ethernet (RJ45) 1 10/100Mbps
Buttons 2 Reset button, onboard programmable button
LED Indicators 3 1x Power, 1x Customizable onboard, 1x Wireless status
USB Port 1 Type-C
External Power Input 1 DC 9-26V
RS485 1 Recommended baud rates: 9600bps | 115200bps
CAN-Bus 1 Supports up to 1Mbps baud rate
Terminal Resistors 2 120Ω (1 for RS485, 1 for CAN-Bus)
SD Card Slot 1 Supports up to 32GB
SIM Card Slot 1 Nano SIM compatible
PCIe Slot 1 Expandable for 4G, NB-IoT communication modules
Gravity I2C Interfaces 3 Supports 100Kbps (Standard) | 400Kbps (Fast mode)
Gravity GPIO Interfaces 11 Expandable for relays, digital/analog sensors; configurable as input/output/ADC

Electrical Parameters

Category Parameter
Operating Voltage DC 9V–26V or USB Type-C 5V 2A input
Power Protection Surge protection, reverse polarity protection
Protection 2kV surge protection, 6kV ESD contact protection
Operating Temperature -20°C to +75°C
Humidity 5–90% RH (non-condensing)
Operating Capability Supports 24/7 continuous operation
Standby Power Consumption 50mA @12V (power terminal) | 100mA @5V (USB)
Dimensions 136.7mm × 76mm × 31mm
Weight (bare board) 63g
Weight (assembled) 335g

6. Installation diagram

The product supports two installation methods: mounting ears and DIN rail mounting, as shown in the diagrams below.

Mounting Ears:

DIN Rail Installation:

DIN Rail Mounting Effect

7. Basic tutorial

7.1 Software Preparation

  • Download Arduino IDE: Click to Download Arduino IDE

  • Configure Arduino IDE for Edge101

    Step 1: Open Arduino IDE and go to File -> Preferences. In the pop-up window, click the button that looks like a document.

    Step 2: Copy and paste the following URL into the "Additional Board Manager URLs" field:

    https://downloadcd.dfrobot.com.cn/DFRobot_Edge101/package_Edge101_index.json

    Then click OK.

    Step 3: Go to Tools -> Board -> Boards Manager... as shown below:

    Step 4: In the search box, type "Edge101", locate the package shown below, and click Install. Wait for the download to complete, then click Close.

    Step 5: Finally, set the board to "Edge101 IOT Controller" by selecting Tools -> DFRobot Edge101 -> Edge101 IOT Controller.

    Now you have completed the installation of the Edge101 SDK and can start writing and uploading code to the Edge101!

7.2 GPIO

The 40-pin expansion interface on the Edge101 mainboard provides 11 GPIOs and is equipped with Gravity 3-pin and 4-pin I2C interfaces, allowing direct connection to DFRobot's Gravity devices.

The GPIOs support internal pull-up, pull-down, or high-impedance configurations. When used as inputs, the values can be read through registers, and they support edge or level-triggered CPU interrupts.

All IO pins are bidirectional, non-inverting, and tri-state designed, supporting input, output, and tri-state control functions. Additionally, they can be multiplexed for other functions such as SDIO, UART, and SPI.

40PIN Expansion Interface Diagram

40PIN Expansion Interface Diagram

Pin Name GPIO Function ADC Function Communication Function Multiplexing Function
P5 GPIO5, supports input/output SPICS0 Gravity SPICS0
P12 GPIO12, supports input/output ADC2_CH5 SPI-SDO Gravity SPI-SDO
P14 GPIO14, supports input/output ADC2_CH6 SPI-CLK Gravity SPI-CLK
P15 GPIO15, supports input/output ADC2_CH3 Onboard LED
P18 GPIO18, supports input/output I2C-SDA Gravity I2C-SDA
P23 GPIO23, supports input/output I2C-SCL Gravity I2C-SCL
P33 GPIO33, supports input/output U1TXD PCIe Slot U1TXD
P34 GPIO34, input only U1RXD PCIe Slot U1RXD
P37 GPIO37, input only ADC1_CH1
P38 GPIO38, input only ADC1_CH2 Onboard Button
P39 GPIO39, input only ADC1_CH3 SPI-SDI Gravity SPI-SDI

Note:

  • GPIO34 to GPIO38 can only be used as inputs and do not support PULLUP or PULLDOWN modes. When using analog input, you must use the GPIOs connected to the ADC. However, if you use wireless communication such as Wi-Fi, ADC 2 will be unavailable.
  • The following GPIOs have built-in pull-up or pull-down resistors:
    • GPIO0: Internal pull-up
    • GPIO5: Internal pull-up
    • GPIO12: Internal pull-down (Default setting ensures FLASH operates at 3.3V. Forcing an external pull-up may cause the board to malfunction.)

Example: Controlling the LED

Set P38 (connected to the onboard button) as an input and P15 (connected to the onboard LED) as an output. When the button is pressed, the LED lights up.

Hardware Preparation:

Sample Code:

const int buttonPin = 38;   // GPIO38 for the onboard button
const int ledPin =  15;     // GPIO15 for the onboard LED

// Variable will change
int buttonState = 0;        // Use a variable to store the button state

void setup() {
   // If the external circuit does not have a pull-up or pull-down resistor,
   // enable the internal pull-up or pull-down resistor when using GPIO as an input
   // to accurately detect the signal level.

   // Initialize button pin as input without enabling pull-up or pull-down
   pinMode(buttonPin, INPUT);

   // Initialize button pin as input with pull-up enabled
   // pinMode(buttonPin, INPUT_PULLUP);

   // Initialize button pin as input with pull-down enabled
   // pinMode(buttonPin, INPUT_PULLDOWN);
      
  pinMode(ledPin, OUTPUT);     // Initialize LED pin as output
  pinMode(buttonPin, INPUT);   // Initialize button pin as input
}

void loop() {
  // Read the button state
  buttonState = digitalRead(buttonPin);

  // If the button input is LOW, the button is pressed, and the onboard green user LED will turn on
  if (buttonState == LOW) {
    // Turn on the LED
    digitalWrite(ledPin, LOW);
  } else {
    // Turn off the LED
    digitalWrite(ledPin, HIGH);
  }
}

Result:

When the button KEY (P38) is pressed, the LED (P15) lights up. When the button is released, the LED turns off.

7.3 PWM

Example: Adjusting LED Brightness with PWM

Create a breathing light effect on the user LED of the Edge101 board by adjusting its brightness using PWM with a frequency of 5000Hz.

Hardware Preparation:

Sample Code:

// PWM channel configuration
#define PWM_CH        0    // Use PWM channel 0
#define PWM_FREQ      5000 // Set PWM frequency to 5kHz
#define PWM_RES       13   // 13-bit resolution (range 0-8191)
#define LED_PIN       15   // LED connection pin

// Breathing light control parameters
int brightness = 0;  // Current brightness value (0-255)
int fadeAmount = 5;  // Brightness change step

/**
 * Custom PWM output function
 * @param ch     PWM channel
 * @param val    Brightness value (0-255)
 * @param maxVal Maximum input value (default is 8-bit)
 */
void ledcAnalogWrite(uint8_t ch, uint32_t val, uint32_t maxVal = 255) {
  // Convert 8-bit brightness value to 13-bit duty cycle
  ledcWrite(ch, (8191 / maxVal) * min(val, maxVal));
}

void setup() {
  // Initialize PWM configuration
  ledcSetup(PWM_CH, PWM_FREQ, PWM_RES);  // Set PWM parameters
  ledcAttachPin(LED_PIN, PWM_CH);        // Bind pin to PWM channel
}

void loop() {
  // Output the current brightness value
  ledcAnalogWrite(PWM_CH, brightness);
  
  // Update brightness value (linear gradient)
  brightness += fadeAmount;
  
  // Reverse gradient direction when reaching brightness limits
  if (brightness <= 0 || brightness >= 255) {
    fadeAmount = -fadeAmount;
  }
  
  delay(30); // Control breathing light speed
}

7.4 ADC

The Edge101 motherboard features a 12-bit SAR ADC, supporting 6 analog input channels with a sampling speed of 2 Msps. At any given time, each ADC can only sample one channel. Note: ADC2 cannot be used when WiFi is enabled.

Example: Single-Channel ADC Sampling

Hardware Required:

Example Code:

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

void loop()
{
  int vtmp = analogRead(37); // GPIO37 ADC1_CH1 to read voltage

  Serial.printf("sample value: %d\n", vtmp);
  Serial.printf("voltage: %.3fV\n", vtmp * 3.26 / 4095);
  delay(500);
}

Result:

After uploading the program to the board, connect a potentiometer to pin P37 and adjust the input voltage. The serial monitor will display the measured raw digital value and the corresponding voltage.

7.5 Serial Ports

The Edge101 board has three serial ports: Serial, Serial1, and Serial2.

  • Serial is connected to the USB interface for program downloading and code debugging. It will output basic information about the board upon power-up.
  • Serial1 is used to extend the onboard wireless module or external serial devices.
  • Serial2 is used for RS485 interface to connect industrial sensors, actuators, and other devices.

Example: Serial Read of User Button State

The following code demonstrates the functionality of reading the digital input signal from pin 38 and outputting the result to the serial monitor.

Hardware Preparation:

Sample Code:

/* Digital Input Read Example */
int pushButton = 38;

// Initialization function (runs once on reset):
void setup() {
  // Initialize serial communication at a baud rate of 115200:
  Serial.begin(115200);
  // Set the button pin as input:
  pinMode(pushButton, INPUT);
}

// Main loop function (runs repeatedly):
void loop() {
  // Read the state of the input pin:
  int buttonState = digitalRead(pushButton);
  // Output the button state to the serial monitor:
  Serial.println(buttonState);
  delay(1);        // Delay to ensure stable reading
}

Result:
When the onboard user button is not pressed, the serial monitor will display 1. When the button is pressed, the serial monitor will display 0.

7.6 I2C

The Edge101 board has two I2C control ports that handle communication on the two I2C buses. Each controller can be set as a master or slave. The board expansion interface uses GPIO18 as I2C SDA and GPIO23 as I2C SCL, and it uses a library compatible with the Arduino Wire library.

Example: Scanning Devices on the I2C Bus

The following code scans for devices on the I2C bus every 5 seconds and prints the slave device addresses found to the serial monitor.

Hardware Preparation:

Sample Code:

#include "Wire.h"  // Include I2C communication library

void setup() {
  Serial.begin(115200);  // Initialize serial communication (baud rate 115200)
  Wire.begin();          // Initialize I2C bus (default uses GPIO18-SDA/GPIO23-SCL)
}

void loop() {
  byte error, address;   // error: communication status code, address: scanned address
  int nDevices = 0;      // Device found counter

  delay(5000);           // Scan the bus every 5 seconds

  Serial.println("Scanning for I2C devices ...");
  
  // Scan the standard I2C address range (0x01~0x7F)
  for(address = 0x01; address < 0x7f; address++) {
    Wire.beginTransmission(address);  // Try to communicate with the target address
    error = Wire.endTransmission();   // Get communication status
    
    if (error == 0) {                 // Status code 0 means device responded
      Serial.printf("I2C device found at address 0x%02X\n", address);
      nDevices++;
    } 
    else if(error != 2) {             // Status code 2 is normal, indicating no device is connected, other errors need to be reported
      Serial.printf("Error %d at address 0x%02X\n", address, error);
    }
  }

  if (nDevices == 0) {                // No devices found
    Serial.println("No I2C devices detected");
  }
}

Result:

The serial monitor will print the scanned devices. The address 0x51 corresponds to the I2C address of the RTC chip on the board.

7.7 SPI

Example: Driving an SPI Display

Connect a DFR0664 2.0-inch TFT LCD screen to the SPI interface of the Edge101 board.

First, we need to install the DFRobot_GDL library (see TFT LCD wiki), then open the example from DFRobot_GDL->example->Basic->UI_bar.

Hardware Preparation:

Hardware Connections:

- Connect the LCD VCC to the +5V of the Edge101 board (Note: the LCD screen requires 5V power).  
- Connect the LCD GND to the GND of the Edge101 board.  
- Connect the LCD CK to the SPI interface's SCK (P14) on the Edge101 board.  
- Connect the LCD SI to the SPI interface's SDO (P12) on the Edge101 board.  
- Connect the LCD SO to the SPI interface's SDI (P39) on the Edge101 board.  
- Connect the LCD BL to the 3.3V of the Edge101 board.  
- Connect the LCD DC to P15 on the Edge101 board.  
- Connect the LCD CS to P5 on the Edge101 board.  
- Connect the LCD RT to P33 on the Edge101 board.

Sample Code:

This is an example displaying loading controls, showing three different types of loading controls.

/*!
 * @file UI_bar.ino
 * @brief Create a progress bar control on the screen.
 * @n Users can customize the parameters of the progress bar or use the default parameters.
 * @n Users can control the value of the progress bar through the callback function of the progress bar.
 * @n The example supports Arduino Uno, Leonardo, Mega2560, FireBeetle-ESP32, FireBeetle-ESP8266, FireBeetle-M0.
 * @copyright  Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com)
 * @licence     The MIT License (MIT)
 * @author [fengli](li.feng@dfrobot.com)
 * @version  V1.0
 * @date  2019-12-6
 * @get from https://www.dfrobot.com
 * @url https://github.com/DFRobot/DFRobot_GDL/src/DFRpbot_UI
*/

#include "DFRobot_UI.h"
#include "DFRobot_GDL.h"

/*M0*/
#if defined ARDUINO_SAM_ZERO
#define TFT_DC  7
#define TFT_CS  5
#define TFT_RST 6
/*ESP32 and ESP8266*/
// LCD VCC connected to Edge101WE board +5V (40PIN expansion interface PIN2)
// LCD GND connected to Edge101WE board GND
// LCD CK connected to Edge101WE board SPI interface SCK
// LCD SI connected to Edge101WE board SPI interface SDO
// LCD SO connected to Edge101WE board SPI interface SDI
// LCD BL connected to Edge101WE board 3.3V
// LCD DC connected to Edge101WE board GPIO 15
// LCD CS connected to Edge101WE board GPIO 5
// LCD RT connected to Edge101WE board GPIO 33
#define TFT_DC  15
#define TFT_CS  5
#define TFT_RST 33
/*AVR series mainboard*/
#else
#define TFT_DC  2
#define TFT_CS  3
#define TFT_RST 4
#endif

// Constructor for hardware SPI communication
DFRobot_ST7789_240x320_HW_SPI screen(TFT_DC, TFT_CS, TFT_RST);

// Initialize UI
DFRobot_UI ui(&screen, NULL);

uint8_t value1 = 0;
uint8_t value2 = 0;
uint8_t value3 = 0;

// Callback function for progress bar1
void barCallback1(DFRobot_UI::sBar_t &obj) {
    delay(50);
    obj.setValue(value1);
    if (value1 < 100) value1++;
}

// Callback function for progress bar2
void barCallback2(DFRobot_UI::sBar_t &obj) {
    delay(50);
    obj.setValue(value2);
    if (value2 < 100) value2++;
}

// Callback function for progress bar3
void barCallback3(DFRobot_UI::sBar_t &obj) {
    delay(50);
    obj.setValue(value3);
    if (value3 < 100) value3++;
}

void setup() {
  Serial.begin(9600);
  // Initialize UI
  ui.begin();
  ui.setTheme(DFRobot_UI::MODERN);

  // Display a string on the screen
  ui.drawString(33, screen.height() / 5 * 4, "Page of loading", COLOR_RGB565_WHITE, ui.bgColor, 2, 0);
  
  // Create a progress bar control
  DFRobot_UI::sBar_t &bar1 = ui.creatBar();
  bar1.setStyle(DFRobot_UI::COLUMN);
  bar1.fgColor = COLOR_RGB565_GREEN;
  bar1.setCallback(barCallback1);
  ui.draw(&bar1, 33, screen.height() / 5 * 3);

  DFRobot_UI::sBar_t &bar2 = ui.creatBar();
  bar2.setStyle(DFRobot_UI::CIRCULAR);
  bar2.setCallback(barCallback2);
  ui.draw(&bar2, 120, screen.height() / 5 * 2);

  DFRobot_UI::sBar_t &bar3 = ui.creatBar();
  bar3.fgColor = COLOR_RGB565_BLUE;
  bar3.setStyle(DFRobot_UI::BAR);
  bar3.setCallback(barCallback3);
  ui.draw(&bar3, (screen.width() - bar3.width) / 2, screen.height() / 10);
}

void loop() {
  // Refresh UI
  ui.refresh();
}

Result: The display result is shown below.

7.8 SD Storage

Note: When downloading the example, select the partition scheme with a FAT system partition.

Example: Read SD Card

First, mount the SD card. If mounting fails, an error will be printed, and then the SD card type will be checked. Next, a series of operations will be performed on the SD card.

Hardware Required:

Example Code:

/*
 * Connect the SD card to the following pins:
 *
 * SD Card | FireBeetle MESH 
 *    D2       - 
 *    D3       GPIO5
 *    CMD      MOSI
 *    VSS      GND
 *    VDD      3.3V
 *    CLK      SCK
 *    VSS      GND
 *    D0       MISO
 *    D1       - 
 */
#include "FS.h"
#include "SD.h"
#include "SPI.h"

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
    Serial.printf("Listing directory: %s\n", dirname);

    File root = fs.open(dirname);
    if(!root){
        Serial.println("Failed to open directory");
        return;
    }
    if(!root.isDirectory()){
        Serial.println("Not a directory");
        return;
    }

    File file = root.openNextFile();
    while(file){
        if(file.isDirectory()){
            Serial.print("  DIR : ");
            Serial.println(file.name());
            if(levels){
                listDir(fs, file.name(), levels -1);
            }
        } else {
            Serial.print("  FILE: ");
            Serial.print(file.name());
            Serial.print("  SIZE: ");
            Serial.println(file.size());
        }
        file = root.openNextFile();
    }
}

void createDir(fs::FS &fs, const char * path){
    Serial.printf("Creating Dir: %s\n", path);
    if(fs.mkdir(path)){
        Serial.println("Dir created");
    } else {
        Serial.println("mkdir failed");
    }
}

void removeDir(fs::FS &fs, const char * path){
    Serial.printf("Removing Dir: %s\n", path);
    if(fs.rmdir(path)){
        Serial.println("Dir removed");
    } else {
        Serial.println("rmdir failed");
    }
}

void readFile(fs::FS &fs, const char * path){
    Serial.printf("Reading file: %s\n", path);

    File file = fs.open(path);
    if(!file){
        Serial.println("Failed to open file for reading");
        return;
    }

    Serial.print("Read from file: ");
    while(file.available()){
        Serial.write(file.read());
    }
    file.close();
}

void writeFile(fs::FS &fs, const char * path, const char * message){
    Serial.printf("Writing file: %s\n", path);

    File file = fs.open(path, FILE_WRITE);
    if(!file){
        Serial.println("Failed to open file for writing");
        return;
    }
    if(file.print(message)){
        Serial.println("File written");
    } else {
        Serial.println("Write failed");
    }
    file.close();
}

void appendFile(fs::FS &fs, const char * path, const char * message){
    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();
}

void renameFile(fs::FS &fs, const char * path1, const char * path2){
    Serial.printf("Renaming file %s to %s\n", path1, path2);
    if (fs.rename(path1, path2)) {
        Serial.println("File renamed");
    } else {
        Serial.println("Rename failed");
    }
}

void deleteFile(fs::FS &fs, const char * path){
    Serial.printf("Deleting file: %s\n", path);
    if(fs.remove(path)){
        Serial.println("File deleted");
    } else {
        Serial.println("Delete failed");
    }
}

void testFileIO(fs::FS &fs, const char * path){
    File file = fs.open(path);
    static uint8_t buf[512];
    size_t len = 0;
    uint32_t start = millis();
    uint32_t end = start;
    if(file){
        len = file.size();
        size_t flen = len;
        start = millis();
        while(len){
            size_t toRead = len;
            if(toRead > 512){
                toRead = 512;
            }
            file.read(buf, toRead);
            len -= toRead;
        }
        end = millis() - start;
        Serial.printf("%u bytes read for %u ms\n", flen, end);
        file.close();
    } else {
        Serial.println("Failed to open file for reading");
    }


    file = fs.open(path, FILE_WRITE);
    if(!file){
        Serial.println("Failed to open file for writing");
        return;
    }

    size_t i;
    start = millis();
    for(i=0; i<2048; i++){
        file.write(buf, 512);
    }
    end = millis() - start;
    Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
    file.close();
}

void setup(){
    Serial.begin(115200);
    // GPIO5 connects to SD card CS 
    if(!SD.begin(5)){
        Serial.println("Card Mount Failed");
        return;
    }
    uint8_t cardType = SD.cardType();

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

    Serial.print("SD Card Type: ");
    if(cardType == CARD_MMC){
        Serial.println("MMC");
    } else if(cardType == CARD_SD){
        Serial.println("SDSC");
    } else if(cardType == CARD_SDHC){
        Serial.println("SDHC");
    } else {
        Serial.println("UNKNOWN");
    }

    uint64_t cardSize = SD.cardSize() / (1024 * 1024);
    Serial.printf("SD Card Size: %lluMB\n", cardSize);

    listDir(SD, "/", 0);
    createDir(SD, "/mydir");
    listDir(SD, "/", 0);
    removeDir(SD, "/mydir");
    listDir(SD, "/", 2);
    writeFile(SD, "/hello.txt", "Hello ");
    appendFile(SD, "/hello.txt", "World!\n");
    readFile(SD, "/hello.txt");
    deleteFile(SD, "/foo.txt");
    renameFile(SD, "/hello.txt", "/foo.txt");
    readFile(SD, "/foo.txt");
    testFileIO(SD, "/test.txt");
    Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
    Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
}

void loop(){

}

Result: The serial print output is as follows.

8. Advanced Tutorial

8.1 Timer

Example: RepeatTimer

The program uses Timer 0 for timing and prints the timing results to the serial monitor. The timing stops when the user button on P38 is pressed.

Hardware Required:

Example Code:

/*
 * Repeat Timer Example
 * This example demonstrates how to use a hardware timer on the ESP32.
 * The timer calls the onTimer function once per second.
 * The timer can be stopped using a button connected to PIN 38 (IO38).
 * This example code is in the public domain.
 */

// Stop button connected to PIN 38 (IO38)
#define BTN_STOP_ALARM    38

hw_timer_t * timer = NULL;
volatile SemaphoreHandle_t timerSemaphore;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

volatile uint32_t isrCounter = 0;
volatile uint32_t lastIsrAt = 0;

void ARDUINO_ISR_ATTR onTimer(){
  // Increment counter and record interrupt occurrence time
  portENTER_CRITICAL_ISR(&timerMux);
  isrCounter++;
  lastIsrAt = millis();
  portEXIT_CRITICAL_ISR(&timerMux);
  // Release semaphore to notify loop of the trigger
  xSemaphoreGiveFromISR(timerSemaphore, NULL);
  // It is safe to use digitalRead/Write here if an output state switch is needed
}

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

  // Set BTN_STOP_ALARM as input mode
  pinMode(BTN_STOP_ALARM, INPUT);

  // Create a semaphore to notify when the timer triggers
  timerSemaphore = xSemaphoreCreateBinary();

  // Use the first of the four available timers (index starts at 0)
  // Set prescaler value to 80
  // The board's clock frequency is currently 80MHz, with 80 prescaler, the count unit is microseconds
  timer = timerBegin(0, 80, true);

  // Attach the onTimer function to the timer
  timerAttachInterrupt(timer, &onTimer, true);

  // Set timer alarm to trigger onTimer function every second (value in microseconds)
  // Enable repeated alarm (third parameter)
  timerAlarmWrite(timer, 1000000, true);

  // Start the timer alarm
  timerAlarmEnable(timer);
}

void loop() {
  // If the timer has triggered
  if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE){
    uint32_t isrCount = 0, isrTime = 0;
    // Read interrupt count and time
    portENTER_CRITICAL(&timerMux);
    isrCount = isrCounter;
    isrTime = lastIsrAt;
    portEXIT_CRITICAL(&timerMux);
    // Print output
    Serial.print("onTimer Trigger Count: ");
    Serial.print(isrCount);
    Serial.print(" Time: ");
    Serial.print(isrTime);
    Serial.println(" ms");
  }
  // If the button is pressed
  if (digitalRead(BTN_STOP_ALARM) == LOW) {
    // If the timer is still running
    if (timer) {
      // Stop and release the timer
      timerEnd(timer);
      timer = NULL;
    }
  }
}

Result: The serial monitor prints the timer data. When the user button on the board is pressed, the timer stops counting.!

8.2 Watchdog Timer

Example: Task Watchdog Timer (TWDT)

In this example, the watchdog timer is set with an overflow time of 3 seconds. For the first 10 seconds, the watchdog is reset every 2 seconds. After 10 seconds, the watchdog is no longer reset, causing the watchdog timer to overflow and the board to reset.

Hardware Required:

Example Code:

#include <esp_task_wdt.h>

// 3-second Watchdog Timer (WDT)
#define WDT_TIMEOUT 3

void setup() {
  Serial.begin(115200);
  Serial.println("Configuring WDT...");
  esp_task_wdt_init(WDT_TIMEOUT, true); // Enable panic, ESP32 auto-restarts on WDT timeout
  esp_task_wdt_add(NULL); // Add the current thread to WDT monitoring
}

int i = 0;         // WDT reset counter
int last = millis(); // Record last reset time

void loop() {
  // Reset WDT every 2 seconds, stop after 5 resets
  if (millis() - last >= 2000 && i < 5) {
      Serial.println("Resetting WDT...");
      esp_task_wdt_reset(); // Reset watchdog timer
      last = millis();      // Update reset time
      i++;                  // Increment counter

      // Stop resetting after the 5th reset, wait for WDT timeout and reboot
      if (i == 5) {
        Serial.println("Stopping WDT reset, CPU will reboot in 3 seconds");
      }
  }
}

8.3 RS485 Communication

Example: RS485 Serial Data Transmission

In this example, we use AccessPort software as the debugging tool for sending and receiving serial data.

After uploading the code to a development board, use any USB/RS485/TTL protocol converter to send data to the board.

Hardware Requirements:

Example Code:
The program transmits the data received from the USB serial port via the RS485 interface and simultaneously receives RS485 data and sends it out via the USB serial port.

#include <ArduinoRS485.h>

RS485Class RS485;

void setup() {
  Serial.begin(9600);
  RS485.begin(9600); // Initialize RS485 with a baud rate of 9600
}

bool flag = false;

void loop() {
  // If there is data available on the serial port
  while (Serial.available()) {
    if (!flag) flag = true;              // Set the flag to true, indicating the start of transmission
    if (flag) RS485.beginTransmission(); // Start RS485 transmission
    RS485.write((char)Serial.read());    // Read data from the serial port and send it via RS485
  }

  // If transmission has started, end RS485 transmission
  if (flag) RS485.endTransmission();
  flag = false; // Reset the flag

  // If there is data available on RS485
  while (RS485.available()) {
    Serial.write(RS485.read()); // Read data from RS485 and send it to the serial port
  }
}

After uploading the program, assume the USB serial port of the development board is COM7.

Connect a USB/RS485/TTL protocol converter to the RS485 interface on the development board, and the converter's USB is connected to the computer. This will trigger the appearance of a USB serial port, COM8.

When the string "Data sent by motherboard" is entered into the development board's USB serial port (COM7), the RS485 converter at COM8 receives the transmitted string.

Similarly, when the string "Data sent by RS485 device" is entered into the RS485 converter's COM8, the development board's USB serial port (COM7) will receive the transmitted string.

8.4 CAN Communication

8.4.1 Example: CAN Bus Reception

This example demonstrates how the Edge101 development board uses the CAN Bus to receive frame data and determine the type of the frame. The user can also turn off the CAN Bus functionality using a user button.

Hardware Requirements:

Hardware Connection:

Example Code:

#include "DFRobot_ESP32CAN.h"

DFRobot_ESP32CAN ESP32Can;
uint8_t userKey = 38;
can_message_t message;

// External interrupt function to stop CAN
void interEvent(void){
  ESP32Can.clearReceiveQueue(); // Clear the RX queue
  ESP32Can.stop();              // Stop the CAN driver to prevent further transmission and reception
  ESP32Can.release();           // Unload the CAN driver
  detachInterrupt(userKey);     // Detach the interrupt from the specified pin
}

void setup() {

  pinMode(userKey, INPUT_PULLUP);
  attachInterrupt(userKey,interEvent,CHANGE);

  Serial.begin(115200);

  /* Please refer to the structure can_general_config_t in DFRobot_ESP32CAN.h and CAN_GENERAL_CONFIG_DEFAULT(op_mode) for further understanding and modification.
  
  Function: Change CAN mode

  Modes:
  CAN_MODE_NORMAL  Normal send/receive/acknowledge mode
  CAN_MODE_NO_ACK  Send without acknowledgment mode, used for self-testing
  CAN_MODE_LISTEN_ONLY  Listen-only mode (no sending or acknowledgment, but can receive messages)
  */
  can_general_config_t g_config = CAN_GENERAL_CONFIG(CAN_MODE_NORMAL);  // Set the general configuration for initialization

  /* Please refer to the structure can_timing_config_t in DFRobot_ESP32CAN.h and CAN_TIMING_CONFIG_500KBITS() for further understanding and modification.
  
  Function: Change baud rate

  Available baud rates:
  CAN_TIMING_CONFIG_25KBITS()
  CAN_TIMING_CONFIG_50KBITS()
  CAN_TIMING_CONFIG_100KBITS()
  CAN_TIMING_CONFIG_125KBITS()
  CAN_TIMING_CONFIG_250KBITS()
  CAN_TIMING_CONFIG_500KBITS()
  CAN_TIMING_CONFIG_800KBITS()
  CAN_TIMING_CONFIG_1MBITS()
  */
  can_timing_config_t t_config = CAN_TIMING_CONFIG_500KBITS();  // Set the timing configuration for initialization

  /* Please refer to the structure can_filter_config_t in DFRobot_ESP32CAN.h and CAN_FILTER_CONFIG_ACCEPT_ALL() for further understanding and modification.
  
  CAN_FILTER_CONFIG_ACCEPT_ALL()  {.acceptance_code = 0, .acceptance_mask = 0xFFFFFFFF, .single_filter = true}

  Function: Configure filter. The default mask is all 1s, meaning that identifiers must match exactly to be received.
  */
  can_filter_config_t f_config = CAN_FILTER_CONFIG_ACCEPT_ALL();

  // Initialize with the configured parameters
  while(!ESP32Can.init(&g_config,&t_config,&f_config)){
    Serial.println("CAN init err!!!");
    delay(1000);
  }

  // Install CAN driver, reset RX and TX queues, and reset RX message count
  while(!ESP32Can.start()){
    Serial.println("CAN start err!!!");
    delay(1000);
  }
}

void loop() {
  // Receive message function, message is the CAN message on the bus, pdMS_TO_TICKS() sets the blocking time, waiting for the desired data (in milliseconds)
  if(ESP32Can.receive(&message,pdMS_TO_TICKS(1000))){
    // Check the received ID, multiple IDs can be accepted using '||'
    if(message.identifier == 0x0006){
    /* Refer to Message flags in DFRobot_ESP32CAN.h
        flags:
        CAN_MSG_FLAG_NONE   Standard format
        CAN_MSG_FLAG_EXTD   Extended format
        CAN_MSG_FLAG_RTR    Remote frame (Remote Transmit Request)
        CAN_MSG_FLAG_SS     Single Shot Transmission
        CAN_MSG_FLAG_SELF   Self Reception Request
    */
      if (message.flags == CAN_MSG_FLAG_NONE) {
        Serial.println("Message is in Standard Format");
      } else if(message.flags == CAN_MSG_FLAG_EXTD){
        Serial.println("Message is in Extended Format");
      } else if(message.flags == CAN_MSG_FLAG_RTR){
        Serial.println("Message is a Remote Transmit Request");
      } else if(message.flags == CAN_MSG_FLAG_SS){
        Serial.println("Transmit as a Single Shot Transmission");
      } else if(message.flags == CAN_MSG_FLAG_SELF){
        Serial.println("Transmit as a Self Reception Request");
      } else if(message.flags == CAN_MSG_FLAG_DLC_NON_COMP){
        Serial.println("Message's Data length code is larger than 8. This breaks CAN2.0B compliance");
      }
      // message.data_length_code is by default set to 5 characters in g_config for data transmission
      for (int i = 0; i < message.data_length_code; i++) {
        Serial.printf("Data byte %d = %d\n", i, message.data[i]);
      }
    } else {
      printf("Error ID:%d\n", message.identifier);
    }
  } else {
    Serial.println("Failed to queue message for receive");
  }
  delay(10);
}

After uploading the code, use a USB CAN analyzer to send data to the development board.

Results:

The CAN bus on the development board A receives the data and outputs it via the USB serial port.

8.4.2 Example: CAN Bus Transmission

This example demonstrates how the Edge101 development board uses the CAN Bus to send standard frame data. The user can also turn off the CAN Bus functionality using a user button.

Hardware Requirements:

Hardware Connection:

Example Code:

/*!
 * @file can_send.ino
 * @brief This demo demonstrates the FireBeetle MESH - Industrial IoT Mainboard using CAN to send standard frame data. The CAN functionality can be turned off using a user button.
 * @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com)
 * @licence The MIT License (MIT)
 * @author [yangfeng]<feng.yang@dfrobot.com>
 * @version V1.0
 * @date 2021-04-08
 * @get from https://www.dfrobot.com
 */
#include "DFRobot_ESP32CAN.h"

DFRobot_ESP32CAN ESP32Can;
uint8_t userKey = 38;
can_message_t tx_message;

void interEvent(void){
  ESP32Can.stop();
  ESP32Can.release();
  detachInterrupt(userKey);
}

void setup() {
  pinMode(userKey, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(userKey), interEvent, CHANGE);  //Enable external interrupts

  Serial.begin(9600);

  /** Here we provide annotations for the can_general_config_t structure. For the MESH development board, the default CAN transmit and receive I/O pins are GPIO_35 and GPIO_32.
   *
   * typedef struct {
   *     can_mode_t mode;                < Mode of CAN controller
   *     gpio_num_t tx_io;               < Transmit GPIO number
   *     gpio_num_t rx_io;               < Receive GPIO number
   *     gpio_num_t clkout_io;           < CLKOUT GPIO number (optional, set to -1 if unused)
   *     gpio_num_t bus_off_io;          < Bus off indicator GPIO number (optional, set to -1 if unused)
   *     uint32_t tx_queue_len;          < Number of messages TX queue can hold (set to 0 to disable TX Queue)
   *     uint32_t rx_queue_len;          < Number of messages RX queue can hold
   *     uint32_t alerts_enabled;        < Bit field of alerts to enable
   *     uint32_t clkout_divider;        < CLKOUT divider. Can be 1 or any even number from 2 to 14 (optional, set to 0 if unused)
   *     int intr_flags;                 < Interrupt flags to set the priority of the driver's ISR. Note that to use the ESP_INTR_FLAG_IRAM, the CONFIG_CAN_ISR_IN_IRAM option should be enabled first.
   * } can_general_config_t;
   *
   * mode:
   *     CAN_MODE_NORMAL,                < Normal operating mode where CAN controller can send/receive/acknowledge messages
   *     CAN_MODE_NO_ACK,                < Transmission does not require acknowledgment. Use this mode for self-testing
   *     CAN_MODE_LISTEN_ONLY,           < The CAN controller will not influence the bus (No transmissions or acknowledgments) but can receive messages
   *
   * CAN_GENERAL_CONFIG_DEFAULT(op_mode) {.mode = op_mode, .tx_io = GPIO_NUM_32, .rx_io = GPIO_NUM_35,   \
   *                                      .clkout_io = CAN_IO_UNUSED, .bus_off_io = CAN_IO_UNUSED,       \
   *                                      .tx_queue_len = 5, .rx_queue_len = 5,                          \
   *                                      .alerts_enabled = CAN_ALERT_NONE,  .clkout_divider = 0,        \
   *                                      .intr_flags = ESP_INTR_FLAG_LEVEL1}
   */
  can_general_config_t g_config = CAN_GENERAL_CONFIG(CAN_MODE_NORMAL);

  /** Here we provide annotations for the can_timing_config_t structure. Users can directly use initialization macros to configure the CAN communication speed.
   * typedef struct {
   *  uint32_t brp;                   < Baudrate prescaler (i.e., APB clock divider) can be any even number from 2 to 128.
   *                                    For ESP32 Rev 2 or later, multiples of 4 from 132 to 256 are also supported
   *  uint8_t tseg_1;                 < Timing segment 1 (Number of time quanta, between 1 to 16)
   *  uint8_t tseg_2;                 < Timing segment 2 (Number of time quanta, 1 to 8)
   *  uint8_t sjw;                    < Synchronization Jump Width (Max time quanta jump for synchronization from 1 to 4)
   *  bool triple_sampling;           < Enables triple sampling when the CAN controller samples a bit
   * } can_timing_config_t;
   *
   * CAN_TIMING_CONFIG_25KBITS()     {.brp = 128, .tseg_1 = 16, .tseg_2 = 8, .sjw = 3, .triple_sampling = false}
   * CAN_TIMING_CONFIG_50KBITS()     {.brp = 80, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false}
   * CAN_TIMING_CONFIG_100KBITS()    {.brp = 40, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false}
   * CAN_TIMING_CONFIG_125KBITS()    {.brp = 32, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false}
   * CAN_TIMING_CONFIG_250KBITS()    {.brp = 16, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false}
   * CAN_TIMING_CONFIG_500KBITS()    {.brp = 8, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false}
   * CAN_TIMING_CONFIG_800KBITS()    {.brp = 4, .tseg_1 = 16, .tseg_2 = 8, .sjw = 3, .triple_sampling = false}
   * CAN_TIMING_CONFIG_1MBITS()      {.brp = 4, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false}
   */
  can_timing_config_t t_config = CAN_TIMING_CONFIG_500KBITS();

  /** Here we provide annotations for the can_filter_config_t structure. Users can directly use initialization macros to configure the reception filter.
   *
   * typedef struct {
   *  uint32_t acceptance_code;       < 32-bit acceptance code
   *  uint32_t acceptance_mask;       < 32-bit acceptance mask
   *  bool single_filter;             < Use Single Filter Mode
   * } can_filter_config_t;
   *
   * CAN_FILTER_CONFIG_ACCEPT_ALL()  {.acceptance_code = 0, .acceptance_mask = 0xFFFFFFFF, .single_filter = true}
   *
   */
  can_filter_config_t f_config = CAN_FILTER_CONFIG_ACCEPT_ALL();

  while(!ESP32Can.init(&g_config,&t_config,&f_config)){
    Serial.println("CAN init err!!!");
    delay(1000);
  }

  while(!ESP32Can.start()){
    Serial.println("CAN start err!!!");
    delay(1000);
  }
  ESP32Can.clearTransmitQueue();
  tx_message.identifier = 0x0006;
  tx_message.data_length_code = 4;
  /** flags:
   *   CAN_MSG_FLAG_NONE                       < No message flags (Standard Frame Format)
   *   CAN_MSG_FLAG_EXTD                       < Extended Frame Format (29bit ID)
   *   CAN_MSG_FLAG_RTR                        < Message is a Remote Transmit Request
   *   CAN_MSG_FLAG_SS                         < Transmit as a Single Shot Transmission
   *   CAN_MSG_FLAG_SELF                       < Transmit as a Self Reception Request
   */
  tx_message.flags = CAN_MSG_FLAG_NONE;
  tx_message.data[0] = 0;
  tx_message.data[1] = 1;
  tx_message.data[2] = 2;
  tx_message.data[3] = 3;
}

void loop() {
  if(ESP32Can.transmit(&tx_message, pdMS_TO_TICKS(1000))){
    Serial.println("Message queued for transmission");
  } else {
    Serial.println("Failed to queue message for transmission");
  }

  delay(1000);
}

Download the code to another board. The board sends one data frame every second. Connect the board's CAN Bus to a USB CAN analyzer.

The USB CAN analyzer receives the data sent by the board as shown in the figure below.

8.5 Bluetooth

The Edge101 mainboard supports dual-mode Bluetooth, which means it can simultaneously support both classic Bluetooth and Bluetooth Low Energy (BLE). The host can run on the same device or be distributed across different devices. The Edge101 mainboard supports both configurations.

8.5.1 Example: Classic Bluetooth (BL)

The mainboard's Bluetooth operates in Bluetooth Classic mode, generating a Bluetooth serial port.

Hardware Required:

Example Code:

// This example code is in the public domain (or under CC0 license, optional).
// Author: Evandro Copercini - 2018
//
// This example creates a bridge between the serial port and classic Bluetooth (SPP),
// and demonstrates that SerialBT has the same functionality as a regular serial port.

#include "BluetoothSerial.h"

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

BluetoothSerial SerialBT;

void setup() {
  Serial.begin(115200);
  SerialBT.begin("Edge101_Device"); // Set Bluetooth device name
  Serial.println("The device started, now you can pair it with Bluetooth!");
}

void loop() {
  // If there's data available in the serial port, send it to Bluetooth
  if (Serial.available()) {
    SerialBT.write(Serial.read());
  }
  // If there's data available in Bluetooth, send it to the serial port
  if (SerialBT.available()) {
    Serial.write(SerialBT.read());
  }
  delay(20); // Slight delay to reduce CPU usage
}

Install the Serial Bluetooth Terminal app on an Android phone. After enabling Bluetooth on the phone, search for the Edge101_Device Bluetooth device and connect to it.

Open the Serial Bluetooth Terminal app and select the Edge101_Device.

Once connected, data can be sent from the phone to the mainboard, and the mainboard will print out the data sent from the phone via the serial port. The mainboard can also send data back to the phone through the serial port.

8.5.2 Example: Bluetooth Low Energy (BLE)

This program will create a BLE device. When the user button on the mainboard is pressed, the BLE device's name will be changed, and you can observe the change of the BLE device name on your phone.

Hardware Preparation:

Example Code:

// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Sketch shows how to use SimpleBLE to advertise the name of the device and change it on the press of a button
// Useful if you want to advertise some sort of message
// Button is attached between GPIO 0 and GND, and the device name changes each time the button is pressed

#include "SimpleBLE.h"  //Using the SimpleBLE library

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

SimpleBLE ble;

void onButton(){
    String out = "BLE32 name: ";
    out += String(millis() / 1000);
    Serial.println(out);
    ble.begin(out);  // Change the name of BLE
}

void setup() {
    Serial.begin(115200);
    Serial.setDebugOutput(true);
    pinMode(38, INPUT_PULLUP);	//The user button on the P38 motherboard is used to change the name of BLE devices
    Serial.print("Edge101WE SDK: ");
    Serial.println(ESP.getSdkVersion());
    ble.begin("Edge101WE SimpleBLE");//After enabling Bluetooth on the device, connect to FireBeetle MESH SimpleBLE. When the button is pressed, change the device name.
    Serial.println("Press the button to change the device's name");
}

void loop() {
    static uint8_t lastPinState = 1;
    uint8_t pinState = digitalRead(38); //After enabling Bluetooth on the device, connect to FireBeetle MESH SimpleBLE. When the button is pressed, change the device name.
    if(!pinState && lastPinState){
        onButton();  
    }
    lastPinState = pinState;
    while(Serial.available()) Serial.write(Serial.read());
}

8.6 WiFi

8.6.1 Example: WiFi Scan

WiFi Scan has both synchronous and asynchronous search modes. This example uses synchronous scanning. The drawback is that it runs in blocking mode by default, meaning the program will scan for WiFi and nothing else can be done during this time. You can switch to asynchronous mode by modifying the parameters.

In this example, the WiFi is first set to station mode. If the device is in AP mode and is connected to other devices, the connection will be disconnected.

Then, the WiFi.scanNetworks() function is called to scan for WiFi networks. If networks are found, their information will be printed out.

Hardware Requirements:

Example Code:

/*
 *  This example demonstrates how to scan for WiFi networks.
 *  The API is almost identical to the WiFi Shield library, 
 *  with the most obvious difference being the inclusion of different header files:
 */
#include "WiFi.h"

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

    // Set WiFi to station (client) mode. If the device is in AP (hotspot) mode and connected, it will disconnect.
    WiFi.mode(WIFI_STA);  // Set to Station mode
    WiFi.disconnect();    // Disconnect from the current WiFi connection
    delay(100);

    Serial.println("Setup done");
}

void loop()
{
    Serial.println("scan start");

    // The WiFi.scanNetworks() function will return the number of networks found
    int n = WiFi.scanNetworks();
    Serial.println("scan done");

    if (n == 0) {
        Serial.println("no networks found");
    } else {
        Serial.print(n);
        Serial.println("networks found");
        for (int i = 0; i < n; ++i) {
            // Print the SSID (Service Set Identifier) and RSSI (Received Signal Strength Indicator) of each network
            Serial.print(i + 1);
            Serial.print(": ");
            Serial.print(WiFi.SSID(i)); // Print network name
            Serial.print(" (");
            Serial.print(WiFi.RSSI(i)); // Print signal strength
            Serial.print(")");

            // Output " " (open network) or "*" (encrypted network) based on the encryption type
            // WIFI_AUTH_OPEN represents no encryption (no password required)
            Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " " : "*");            
            delay(10);
        }
    }
    Serial.println("");

    // Wait for a while before scanning again
    delay(5000);
}

Result: The scanned WiFi networks are printed out from the serial port.

8.6.2 Example: Web Page Configuration

Initially, the WiFi device is in AP mode, and the default IP address is 192.168.4.1. By accessing this IP via a web page, users can connect to the AP. In the input fields, users enter the SSID and password of their router's WiFi. Once the WiFi device receives this information, it switches to STA mode and connects to the network using the provided details. The advantage of this method is a 100% success rate, but it requires a button to put the device into configuration mode.

Advantages of Web Page Configuration:

  • Direct input makes configuration simple, the process is clear, and the success rate is high.
  • The router or hotspot to be connected to is not restricted and does not require an internet connection.
  • No need to add additional interfaces to the system, making it suitable for closed or inconvenient scenarios where extra interfaces cannot be exposed.
  • The configuration can be done using any device that supports WiFi and a browser, making it very flexible and practical.

Hardware Requirements:

Example Code:

#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <esp_wifi.h>

const char* AP_SSID  = "Edge101_"; // Hotspot name
String wifi_ssid = "";
String wifi_pass = "";
String scanNetworksID = "";// Used to store scanned WiFi

#define ROOT_HTML  "<!DOCTYPE html><html><head><title>WIFI Config by DFRobot</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"></head><style type=\"text/css\">.input{display: block; margin-top: 10px;}.input span{width: 100px; float: left; float: left; height: 36px; line-height: 36px;}.input input{height: 30px;width: 200px;}.btn{width: 120px; height: 35px; background-color: #000000; border:0px; color:#ffffff; margin-top:15px; margin-left:100px;}</style><body><form method=\"GET\" action=\"connect\"><label class=\"input\"><span>WiFi SSID</span><input type=\"text\" name=\"ssid\"></label><label class=\"input\"><span>WiFi PASS</span><input type=\"text\"  name=\"pass\"></label><input class=\"btn\" type=\"submit\" name=\"submit\" value=\"Submit\"> <p><span> Nearby wifi:</P></form>"

WebServer server(80);

#define RESET_PIN   38  // GPIO 38 User Key for deleting WiFi information

void setup() {

  Serial.begin(115200);
  pinMode(RESET_PIN, INPUT_PULLUP);

  // Connect to WiFi
  if (!AutoConfig())
  {
    wifi_Config();
  }

  // Used to delete stored WiFi
  if (digitalRead(RESET_PIN) == LOW) {
    Serial.println("Delete WiFi and restart");
    delay(1000);
    esp_wifi_restore();
    delay(10);
    ESP.restart();  // Reset ESP32
  }
}

void loop() {
  server.handleClient();
  while (WiFi.status() == WL_CONNECTED) {
    // WIFI is connected
  }
}

// For configuring WiFi
void wifi_Config()
{
  Serial.println("scan start");
  // Scan for nearby WiFi networks
  int n = WiFi.scanNetworks();
  Serial.println("scan done");
  if (n == 0) {
    Serial.println("no networks found");
    scanNetworksID = "no networks found";
  } else {
    Serial.print(n);
    Serial.println(" networks found");
    for (int i = 0; i < n; ++i) {
      // Print SSID and RSSI for each network found
      Serial.print(i + 1);
      Serial.print(": ");
      Serial.print(WiFi.SSID(i));
      Serial.print(" (");
      Serial.print(WiFi.RSSI(i));
      Serial.print(")");
      Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " " : "*");
      scanNetworksID += "<P>" + WiFi.SSID(i) + "</P>";
      delay(10);
    }
  }
  Serial.println("");

  WiFi.mode(WIFI_AP); // Set to AP mode
  boolean result = WiFi.softAP(AP_SSID, ""); // Start WiFi hotspot
  if (result)
  {
    IPAddress myIP = WiFi.softAPIP();
    // Print related information
    Serial.println("");
    Serial.print("Soft-AP IP address = ");
    Serial.println(myIP);
    Serial.println(String("MAC address = ")  + WiFi.softAPmacAddress().c_str());
    Serial.println("waiting ...");
  } else {  // Failed to start hotspot
    Serial.println("WiFiAP Failed");
    delay(3000);
    ESP.restart();  // Reset ESP32
  }

  if (MDNS.begin("esp32")) {
    Serial.println("MDNS responder started");
  }

  // Homepage
  server.on("/", []() {
    server.send(200, "text/html", ROOT_HTML + scanNetworksID + "</body></html>");
  });

  // Connect
  server.on("/connect", []() {

    server.send(200, "text/html", "<html><body><font size=\"10\">Success, WiFi connecting...<br />Please close this page manually.</font></body></html>");

    WiFi.softAPdisconnect(true);
    // Get the entered WiFi SSID and password
    wifi_ssid = server.arg("ssid");
    wifi_pass = server.arg("pass");
    server.close();
    WiFi.softAPdisconnect();
    Serial.println("WiFi Connect SSID:" + wifi_ssid + "  PASS:" + wifi_pass);

    // Set to STA mode and connect to WiFi
    WiFi.mode(WIFI_STA);
    WiFi.begin(wifi_ssid.c_str(), wifi_pass.c_str());
    uint8_t Connect_time = 0; // Used for connection timeout, reset the device if it takes too long
    while (WiFi.status() != WL_CONNECTED) {  // Wait for WiFi connection
      delay(500);
      Serial.print(".");
      Connect_time++;
      if (Connect_time > 80) {  // Long connection time, reset the device
        Serial.println("Connection timeout, check if input is correct or try again later!");
        delay(3000);
        ESP.restart();
      }
    }
    Serial.println("");
    Serial.println("WIFI Config Success");
    Serial.printf("SSID:%s", WiFi.SSID().c_str());
    Serial.print("  LocalIP:");
    Serial.print(WiFi.localIP());
    Serial.println("");

  });
  server.begin();
}

// For automatic WiFi connection on power-up
bool AutoConfig()
{
  WiFi.begin();
  for (int i = 0; i < 20; i++)
  {
    int wstatus = WiFi.status();
    if (wstatus == WL_CONNECTED)
    {
      Serial.println("WIFI SmartConfig Success");
      Serial.printf("SSID:%s", WiFi.SSID().c_str());
      Serial.printf(", PSW:%s\r\n", WiFi.psk().c_str());
      Serial.print("LocalIP:");
      Serial.print(WiFi.localIP());
      Serial.print(" ,GateIP:");
      Serial.println(WiFi.gatewayIP());
      return true;
    }
    else
    {
      Serial.print("WIFI AutoConfig Waiting......");
      Serial.println(wstatus);
      delay(1000);
    }
  }
  Serial.println("WIFI AutoConfig Failed!" );
  return false;
}

8.7 Ethernet

Example: Switching Between Ethernet and WiFi

Hardware Requirements:

Example Code: You need to modify the WiFi SSID and password, then upload the program to the board.

```cpp
/*
   Ethernet and WiFi network switching control
   Default power-on starts in Ethernet mode:
   - Switches to WiFi mode after detecting Ethernet connection timeout
   - Automatically switches back to Ethernet mode when Ethernet is restored in WiFi mode
*/

#include <ETH.h>
#include <WiFi.h>
const char* ssid     = "yourssid";     // WiFi network name
const char* password = "yourpasswd";   // WiFi password

// Network status flags
static bool eth_connected = false;     // Ethernet connection status
bool wifi_mode = false;                // WiFi mode flag
bool eth_mode = true;                  // Ethernet mode flag
uint64_t time_start = 0 ;              // Ethernet startup timestamp
uint64_t time_connected = 0 ;          // Ethernet connection success timestamp

// WiFi event callback function
void WiFiEvent(WiFiEvent_t event)
{
  switch (event) {
    case ARDUINO_EVENT_ETH_START:       // Ethernet start event
      ETH.setHostname("esp32-ethernet"); // Set the device hostname
      time_start = millis();            // Record start time
      break;
    case ARDUINO_EVENT_ETH_CONNECTED:   // Physical layer connection established
      time_connected = millis();
      Serial.println("ETH Connected");
      if(wifi_mode){                    // If currently in WiFi mode, restart
        ESP.restart();
      }
      break;
    case ARDUINO_EVENT_ETH_GOT_IP:      // Successfully obtained IP address
      Serial.print("ETH MAC: ");
      Serial.print(ETH.macAddress());   // Print MAC address
      Serial.print(", IPv4: ");
      Serial.print(ETH.localIP());      // Print IP address
      if (ETH.fullDuplex()) {           // Full-duplex status check
        Serial.print(", FULL_DUPLEX");
      }
      Serial.print(", ");
      Serial.print(ETH.linkSpeed());    // Print connection speed
      Serial.println("Mbps");
      eth_connected = true;             // Update connection status
      break;
    case ARDUINO_EVENT_ETH_DISCONNECTED:// Physical connection disconnected
      Serial.println("ETH Disconnected");
      if(!wifi_mode){                   // If not in WiFi mode, attempt restart
        ESP.restart();
      }
      eth_connected = false;
      eth_mode = false;
      break;
    case ARDUINO_EVENT_ETH_STOP:        // Ethernet stop
      Serial.println("ETH Stopped");
      eth_connected = false;
      break;
    default:
      break;
  }
}

// Network connection test function
void testClient(const char * host, uint16_t port)
{
  Serial.print("\nconnecting to ");
  Serial.println(host);  // Output target host info

  WiFiClient client;
  // Attempt to establish a TCP connection
  if (!client.connect(host, port)) {
    Serial.println("connection failed");
    return;
  }
  // Send HTTP request
  client.printf("GET / HTTP/1.1\r\nHost: %s\r\n\r\n", host);
  // Wait for server response
  while (client.connected() && !client.available());
  // Read returned data
  while (client.available()) {
    Serial.write(client.read());
  }

  Serial.println("closing connection\n");
  client.stop();
}

void setup()
{
  Serial.begin(115200);          // Initialize serial
  WiFi.onEvent(WiFiEvent);       // Register network event callback
  ETH.begin();                   // Start Ethernet
  delay(5000);                   // Wait for 5 seconds to initialize
  
  // Ethernet connection timeout detection (4 seconds without connection)
  if((millis()-time_start>4000)&&(time_connected==0)){
    // Start WiFi connection
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
    wifi_mode = true;            // Switch to WiFi mode
  }
}

void loop()
{
  // Test network in valid connection mode
  if (eth_connected||wifi_mode) {
    testClient("baidu.com", 80);  // Test connection to Baidu server
  }
  delay(10000);                   // 10-second detection cycle
}

Next, connect the Ethernet cable to the Ethernet port on the Edge101 motherboard. When the green LED on the Ethernet port is lit, it indicates a successful connection. The orange LED blinking indicates communication.

At this point, the serial monitor will print information such as Ethernet connection success, the Ethernet MAC address, and IP address. The program will then access www.baidu.com every 10 seconds and return the retrieved data.

If the Ethernet cable is unplugged, the serial monitor will display "Ethernet Disconnected." The motherboard will then restart and attempt to connect to the network via WiFi. Once connected, it will access the website.

8.8 4G

The Edge101 motherboard does not come with a 4G module by default, but it supports an expansion board for installing a 4G module.

Example: Fetching Network Time

Hardware Requirements:

Example Code:

/*! 
 * @file setNTP.ino
 * @brief : Network time synchronization example
 * @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com)
 * @licence The MIT License (MIT)
 * @author [yangfeng]<feng.yang@dfrobot.com>
 * @version V1.0
 * @date 2021-08-18
 */
#include <DFRobot_AIR720UH.h>
DFRobot_AIR720UH      AIR720UH;

void setup(){
    int signalStrength,dataNum;
    Serial.begin(115200);
    Serial1.begin(115200);
    while(!Serial);
    AIR720UH.begin(Serial1);
    
    // SIM card status check
    Serial.println("Check SIM card......");
    if(AIR720UH.checkSIMStatus()){                               // Check SIM card status
        Serial.println("SIM card READY");
    }else{
        Serial.println("SIM card ERROR, Check if you have inserted SIM card and restart AIR720UH");
        while(1);
    }

    // Signal quality check
    Serial.println("Get signal quality......");
    delay(500);
    signalStrength = AIR720UH.checkSignalQuality();                // Get signal strength
    Serial.print("signalStrength =");
    Serial.println(signalStrength);

    // NTP network time synchronization configuration
    delay(500);
    Serial.println("set NTP ...");
    while(1){
      int data = AIR720UH.setNTP();
      if(data == 1){
        Serial.println("The network time is synchronized successfully!");  // Time synchronization successful
        break;
      }else if(data == 61){
        Serial.println("Network error");                         // Network error
        break;
      }else if(data == 62){
        Serial.println("DNS resolution error");                  // DNS resolution error
        break;
      }else if(data == 63){
        Serial.println("Connection error");                      // Connection error
        break;
      }else if(data == 64){
        Serial.println("Service response error");                // Service response error
        break;
      }else if(data == 65){
        Serial.println("Service response timeout");             // Service response timeout
        break;
      }else{
        Serial.println("set error");                             // Unknown error
      }
    }
}

void loop(){
  // Continuously fetch and display the network time
  char *buff = AIR720UH.getCLK();
  if(buff != NULL){
    Serial.println(buff);  // Output format example: 2021/08/18 14:30:45
  }
}

8.9 Application Layer Protocol

Example: MQTT — Publish and Subscribe

Connect to a public MQTT server, publish a "hello world" message to the "outTopic" every 2 seconds, and have the client listen to the "inTopic" to control a light based on the payload content.

Hardware Required:

Example Code: Modify the WiFi SSID and password in the code below to your own WiFi SSID and password, then upload the program to the mainboard.

```cpp
#include <WiFi.h>
#include <PubSubClient.h>

#define LED  15  // LED control pin (adjust according to actual hardware connections)

/* Network configuration parameters (need user modification) */
const char* ssid = "your_ssid";         // WiFi network name
const char* password = "your_password"; // WiFi password
const char* mqtt_server = "broker.mqtt-dashboard.com"; // MQTT server address

WiFiClient espClient;                   // WiFi client instance
PubSubClient client(espClient);         // MQTT client instance
long lastMsg = 0;                       // Last message timestamp
char msg[50];                           // Message buffer
int value = 0;                          // Test counter

/* WiFi connection initialization */
void setup_wifi() {
  delay(10);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);           // Start WiFi connection

  // Wait for successful connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());                 // Initialize random seed

  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());       // Display obtained IP address
}

/* MQTT message callback function */
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);                  // Display message topic
  Serial.print("] ");
  
  // Print message content
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  // Control LED based on the first character of the message
  if ((char)payload[0] == '0') {
    digitalWrite(LED, LOW);    // Turn off LED when receiving '0' (low level active)
  } else {
    digitalWrite(LED, HIGH);   // Turn on LED for non-'0' messages
  }
}

/* MQTT reconnection mechanism */
void reconnect() {
  while (!client.connected()) {         // Keep attempting to connect
    Serial.print("Attempting MQTT connection...");
    
    // Generate random client ID (to avoid duplication)
    String clientId = "FireBeetleClient-";
    clientId += String(random(0xffff), HEX);
    
    if (client.connect(clientId.c_str())) {  // Connection attempt
      Serial.println("connected");
      client.publish("outTopic", "hello world"); // Publish initial message
      client.subscribe("inTopic");              // Subscribe to input topic
    } else {
      Serial.print("failed, rc=");      // Display error status code
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

/* Initialization setup */
void setup() {
  pinMode(LED, OUTPUT);              // Initialize LED pin as output
  Serial.begin(115200);              // Start serial communication
  setup_wifi();                      // Connect to WiFi network
  client.setServer(mqtt_server, 1883); // Configure MQTT server
  client.setCallback(callback);      // Set message callback function
}

/* Main loop */
void loop() {
  if (!client.connected()) {         // Maintain MQTT connection
    reconnect();
  }
  client.loop();                     // Process MQTT messages

  // Publish test message every 2 seconds
  long now = millis();
  if (now - lastMsg > 2000) {
    lastMsg = now;
    ++value;
    snprintf(msg, 75, "hello world #%ld", value); // Generate message content
    Serial.print("Publish message: ");
    Serial.println(msg);
    client.publish("outTopic", msg);  // Publish to specified topic
  }
}

Results: The serial output is as follows.

9. Product Dimensions

Unit: mm

Front View Dimensions

Rear View Dimensions

Top View Dimensions

Side View Dimensions

10. Downloads

  • DFR0886_3D.STP

  • DFR0886_2D.pdf

For any questions, advice or cool ideas to share, please visit the DFRobot Forum.