ESP8266 Example Applications with PlatformIO

July 6, 2024

This documents an attempt to build a test application on a laptop with Ubuntu 22.04 installed. The target is an open hardware NodeMCU (Lua) ESP8266 board with an ESP-12E Wi-Fi module and CH340G USB-to-serial driver chip.

Setup the Edit/Build Environment

  • Install Microsoft’s VS Code. PlatformIO sits on top of this code editor.
  • Open VS Code
    • Install “PlatformIO IDE” extension.
    • Select the PlatformIO Home icon to kick off any required initialization.
    • Close VS Code.
  • Restart VS Code
    • Open Terminal Window and type “git --version
    • If git is not installed, do so now.
  • At this time, the library reference for the Arduino code is Welcome to ESP8266 Arduino Core’s documentation!

Test Project #1 – LED Blinker

  • Open VS Code, select PlatformIO Home icon and create a new project.
    • Name: Test1
    • Board: Espressif ESP8266 ESP-12E
    • Framework: Arduino
    • Select “Finish” and let the environment will be initialized.
    • (Once complete, libraries will be located at c:\Users\name\.platformio\~)
  • Open file src/main.cpp and edit code to match below.
#include "Arduino.h"

// Set LED_BUILTIN if it is not defined by Arduino framework
// #define LED_BUILTIN 13

void setup()
{
  // initialize LED digital pin as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
  // turn the LED on (HIGH is the voltage level)
  digitalWrite(LED_BUILTIN, HIGH);

  // wait for a second
  delay(1000);

  // turn the LED off by making the voltage LOW
  digitalWrite(LED_BUILTIN, LOW);

   // wait for a second
  delay(1000);
}

Plug in a NodeMCU, ESP-12E, or other ESP8266 device, build then upload the code. If there are any problems with the USB /dev/ttyUSB0 driver, see this topic to help clear it up.

If all goes well, the onboard LED should be blinking at a rate of one second off, one second on.

Test Project #2 – USB Serial Communication

  • Open VS Code, select PlatformIO Home icon and create a new project (same as above) or edit the an existing project.
  • Open file src/main.cpp and edit code to match below.
#include "Arduino.h"

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

void loop()
{
  Serial.println("Hello");
  delay(1000);
}

You will also need to change the platformio.ini file to match the application UART speed.

[env:esp12e]
...
monitor_speed = 115200

Build and upload the code.

Open the Serial Monitor by clicking the icon at the bottom of VS Code. If all goes well, you should see the word “Hello” being printed in the window. You can right click in the teminal window and clear the screen to see the window redraw itself.

Test Project #3 – Connect to Local Wi-Fi as Client

This time go the Arduino 8266 Core documents and load the source file for Wi-Fi client (here). It connects to the local network and prints the IP address returned by DHCP. Make sure you edit the WiFi.begin() command to enter the SID and password for your network.

#include <ESP8266WiFi.h>

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

  WiFi.begin("ssid", "password");

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

  Serial.print("Connected, IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {}

Build and upload the code then open the PlatformIO Serial Monitor and should respond quickly.

Connecting..............................
........................................
Connected, IP address: 192.168.1.123

Test Project #4 – Get UTC from Time Server over Wi-Fi

This example is in the Arduino libraries downloaded to the local system by PlatformIO at C:\Users\user\.platformio\packages\framework-arduinoespressif8266\libraries\ESP8266WiFi\examples\NTPClient\NTPClient.ino. It gets the UTC time from “time.nist.gov” every ten seconds.

/*

  Udp NTP Client

  Get the time from a Network Time Protocol (NTP) time server
  Demonstrates use of UDP sendPacket and ReceivePacket
  For more on NTP time servers and the messages needed to communicate with them,
  see http://en.wikipedia.org/wiki/Network_Time_Protocol

  created 4 Sep 2010
  by Michael Margolis
  modified 9 Apr 2012
  by Tom Igoe
  updated for the ESP8266 12 Apr 2015
  by Ivan Grokhotkov

  This code is in the public domain.

*/

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>

#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif

const char* ssid = STASSID;  // your network SSID (name)
const char* pass = STAPSK;   // your network password

unsigned int localPort = 2390;  // local port to listen for UDP packets

/* Don't hardwire the IP address or we won't get the benefits of the pool.
    Lookup the IP address for the host name instead */
// IPAddress timeServer(129, 6, 15, 28); // time.nist.gov NTP server
IPAddress timeServerIP;  // time.nist.gov NTP server address
const char* ntpServerName = "time.nist.gov";

const int NTP_PACKET_SIZE = 48;  // NTP time stamp is in the first 48 bytes of the message

byte packetBuffer[NTP_PACKET_SIZE];  // buffer to hold incoming and outgoing packets

// A UDP instance to let us send and receive packets over UDP
WiFiUDP udp;

// Forward reference to locally defined methods.
void sendNTPpacket(IPAddress& address);

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

  // We start by connecting to a WiFi network
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pass);

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

  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  Serial.println("Starting UDP");
  udp.begin(localPort);
  Serial.print("Local port: ");
  Serial.println(udp.localPort());
}

void loop() {
  // get a random server from the pool
  WiFi.hostByName(ntpServerName, timeServerIP);

  sendNTPpacket(timeServerIP);  // send an NTP packet to a time server
  // wait to see if a reply is available
  delay(1000);

  int cb = udp.parsePacket();
  if (!cb) {
    Serial.println("no packet yet");
  } else {
    Serial.print("packet received, length=");
    Serial.println(cb);
    // We've received a packet, read the data from it
    udp.read(packetBuffer, NTP_PACKET_SIZE);  // read the packet into the buffer

    // the timestamp starts at byte 40 of the received packet and is four bytes,
    //  or two words, long. First, esxtract the two words:

    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    // combine the four bytes (two words) into a long integer
    // this is NTP time (seconds since Jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;
    Serial.print("Seconds since Jan 1 1900 = ");
    Serial.println(secsSince1900);

    // now convert NTP time into everyday time:
    Serial.print("Unix time = ");
    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;
    // subtract seventy years:
    unsigned long epoch = secsSince1900 - seventyYears;
    // print Unix time:
    Serial.println(epoch);


    // print the hour, minute and second:
    Serial.print("The UTC time is ");       // UTC is the time at Greenwich Meridian (GMT)
    Serial.print((epoch % 86400L) / 3600);  // print the hour (86400 equals secs per day)
    Serial.print(':');
    if (((epoch % 3600) / 60) < 10) {
      // In the first 10 minutes of each hour, we'll want a leading '0'
      Serial.print('0');
    }
    Serial.print((epoch % 3600) / 60);  // print the minute (3600 equals secs per minute)
    Serial.print(':');
    if ((epoch % 60) < 10) {
      // In the first 10 seconds of each minute, we'll want a leading '0'
      Serial.print('0');
    }
    Serial.println(epoch % 60);  // print the second
  }
  // wait ten seconds before asking for the time again
  delay(10000);
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress& address) {
  Serial.println("sending NTP packet...");
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;  // LI, Version, Mode
  packetBuffer[1] = 0;           // Stratum, or type of clock
  packetBuffer[2] = 6;           // Polling Interval
  packetBuffer[3] = 0xEC;        // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12] = 49;
  packetBuffer[13] = 0x4E;
  packetBuffer[14] = 49;
  packetBuffer[15] = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  udp.beginPacket(address, 123);  // NTP requests are to port 123
  udp.write(packetBuffer, NTP_PACKET_SIZE);
  udp.endPacket();
}

The output is displayed below.

Connecting to Snowflake
.........
WiFi connected
IP address:
192.168.1.198
Starting UDP
Local port: 2390
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900 = 3929195828
Unix time = 1720207028
The UTC time is 19:17:08

Test Project #5 – I2C Scanner

Walk through the I2C addresses and look for devices. This code from the Random Nerd Tutorials site.

#include <Wire.h>
// Needed to add to Random Nerd Tutorials code to resolve Serial.
#include <ESP8266WiFi.h>
 
void setup() {
  Wire.begin();
  Serial.begin(115200);
  Serial.println("\nI2C Scanner");
}
 
void loop() {
  byte error, address;
  int nDevices;
  Serial.println("Scanning...");
  nDevices = 0;
  for(address = 1; address < 127; address++ ) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address<16) {
        Serial.print("0");
      }
      Serial.println(address,HEX);
      nDevices++;
    }
    else if (error==4) {
      Serial.print("Unknow error at address 0x");
      if (address<16) {
        Serial.print("0");
      }
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0) {
    Serial.println("No I2C devices found\n");
  }
  else {
    Serial.println("done\n");
  }
  delay(5000);          
}

At this point, with no I2C peripherals, no devices will be found.

Add a RTC to the Circuit – DS3231 Module

Connect the ESP3266 to the RTC module as follows. This will use the default ESP3266 I2C pins.

  • Connect ESP8266 SDA (D2) to DS3231 SDA
  • Connect ESP8266 SCL (D1) to DS3231 SCL
  • Connect ESP8266 3.3V to DS3231 VCC
  • Connect ESP8266 GND to DS3231 GND

Rerun the code. The new output will resemble below.

Scanning...
I2C device found at address 0x57
I2C device found at address 0x68
done

Notice that the scanner found two addresses from the RTC:

  • The 7-bit I2C device address for the DS3231 RTC is 1101000 (0x68).
    • Read address: 11010001 (0xD1)
    • Write address: 11010000 (0xD0).
  • The 7-bit I2C device address for the AT24C32 EEPROM is 1010111 (0x57).
    • Read address: 10101111 (0xAF), and the
    • Write address: 10101110 (0xAE).

Test Project #6 – Read Light Dependent Resistor (LDR) Value

In this example, we will add an LDR to the ESP8266. An example usage is if you create a device that has a display, you can use the LDR to sense the current room light level and dim the backlight if necessary. Be aware that the Arduino library does have a warning with the usage of the analog input hardware. Reading between the lines, the ADC has multiple input channels and it is shared with the Wi-Fi functionality. Performing an analogRead(A0) instruction usings low-level software semaphore logic to acquire the ADC for the external pin read.

ADC sampling rate: can reach 100,000 times per second with Wi-Fi turned off, and 1,000 times per second with Wi-Fi turned on. Exceeding this capability can cause Wi-Fi instability.

The code below applies an exponential average to the instantaneous LDR analog reading to smooth the value then outputs the LDR reading and the smoothed value normalized by the maximum value (0 to 0.99).

 #include <Arduino.h>

// Averaged value of LDR.
double _ldrAvg = 0;

// Shared string buffer.
char t[100];

void setup() {
  Serial.begin(9600);
}

void loop() {

  // Update LDR value.
  double v = analogRead(A0);
  _ldrAvg = _ldrAvg * 0.5 + v * 0.5;
  double s = _ldrAvg / 1024.0;

  sprintf(t, "\nAnalog: %d, (%.2f)", (int)v, s);
  Serial.println(t);

  delay(1000);  // Delay for 1 second before the next iteration
}

Example outputs value are displayed below.

Analog: 172, (0.17)   - LDR covered by hand
Analog: 541, (0.55)   - Office lighting
Analog: 910, (0.89)   - Flashlight on LDR