Skip to content

15. Interface and Application Programming

This week, I experimented with creating web servers with the ESP8266, as well as explored interfacing with my TFT LCD. You can access all of my files here.

Assignment

group assignment:

  • compare as many tool options as possible

individual assignment:

  • write an application that interfaces a user with an input &/or output device that you made

ESP8266 WiFi Server

The first thing I wanted to try out was creating my own web server using one of the ESP boards. Although my final project involved the ESP32 wroom, I used the ESP8266 this week, which is the predecessor. I created a simple LED circuit with a 383 ohm resistor, connecting it to pin 5 on the MCU. I followed this website, which explained the basics of establishing a web server with a unique IP address.

In Arduino IDE, I ensured to include the ESP8266WiFi library (there is a general WiFi library for the ESP32 models), as well as the ESP8266 board in the Boards Manager.

I went through the example code provided and made some changes to address the pin I had assigned to the LED, as well as the WiFi. Here were some major takeaways I had:

  • ssid refers to Service Set Identifier and is your network’s name; to access the web server, ensure that your device and the ESP8266 acccess the same network

  • Serial.println(WiFi.localIP()); prints out the IP address assigned to this specific device

  • server.available() repeatedly checks if there is a “client” or browser that is currenrly accessing the IP address.

  • client.println is a function used to display the HTML web page and create the different objects (i.e. button) that appear on the screen.

// Load Wi-Fi library for ESP8266
#include <ESP8266WiFi.h>

// Replace with your network credentials
const char* ssid     = "INSERT SSID (wifi name)";
const char* password = "INSERT PASSWORD";

// Set web server port number to 80
WiFiServer server(80);

// Variable to store the HTTP request
String header;

// Auxiliar variables to store the current output state
String output5State = "off";


// Assign output variables to GPIO pins
const int output5 = 5;


// Current time
unsigned long currentTime = millis();
// Previous time
unsigned long previousTime = 0; 
// Define timeout time in milliseconds (example: 2000ms = 2s)
const long timeoutTime = 2000;

void setup() {
  Serial.begin(115200);
  // Initialize the output variables as outputs
  pinMode(output5, OUTPUT);

  // Set outputs to LOW
  digitalWrite(output5, LOW);


  // Connect to Wi-Fi network with SSID and password
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // Print local IP address and start web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();
}

void loop(){
  WiFiClient client = server.available();   // Listen for incoming clients

  if (client) {                             // If a new client connects,
    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    currentTime = millis();
    previousTime = currentTime;
    while (client.connected() && currentTime - previousTime <= timeoutTime) { // loop while the client's connected
      currentTime = millis();         
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();

            // turns the GPIOs on and off
            if (header.indexOf("GET /5/on") >= 0) {
              Serial.println("GPIO 5 on");
              output5State = "on";
              digitalWrite(output5, HIGH);
            } else if (header.indexOf("GET /5/off") >= 0) {
              Serial.println("GPIO 5 off");
              output5State = "off";
              digitalWrite(output5, LOW);
            } 

            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // CSS to style the on/off buttons 
            // Feel free to change the background-color and font-size attributes to fit your preferences
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { background-color: #195B6A; border: none; color: white; padding: 16px 40px;");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
            client.println(".button2 {background-color: #77878A;}</style></head>");

            // Web Page Heading
            client.println("<body><h1>ESP8266 Web Server</h1>");

            // Display current state, and ON/OFF buttons for GPIO 5  
            client.println("<p>GPIO 5 - State " + output5State + "</p>");
            // If the output5State is off, it displays the ON button       
            if (output5State=="off") {
              client.println("<p><a href=\"/5/on\"><button class=\"button\">ON</button></a></p>");
            } else {
              client.println("<p><a href=\"/5/off\"><button class=\"button button2\">OFF</button></a></p>");
            } 

          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }
}

This code prints out the unique IP address in the serial monitor and verifies if there is a user connected online.

When I uploaded this code, I could successfully access the IP address and control the LED!

Interfacing the ILI9341 TFT LCD with the ESP32 Wroom

Now, onto greater (and harder) things. I initially wanted to interface the 2.8” ILI9341 TFT LCD with the ATmega328 since I got it working with the Arduino, but after stumbling upon Baptiste Lardais’ documentation, I decided I wanted to expand my horizons and try to interface with the esp32 wroom. Unfortunately, not many people online had documented the wroom with my model of the tft lcd (specifically the adafruit model), but I was able to find Maharshi Bhattacharya’s documentation, who had used a similar model. Some forums online suggested connecting both 3V3 and 3-5V. Here are the connections I ultimately found worked:

I also learned that the Adafruit_GFX library was compatible with the wroom chip, even though there was a separate e_spi library. The only difference was that I needed to individually define the SPI pins as they are different than the ones on the Arduino Uno. I created a simple program to test the screen out:

#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"

// define these pins since they are different on the ESP
#define TFT_DC 17
#define TFT_CS 5
#define TFT_MOSI 23
#define TFT_CLK 18
#define TFT_RST 4
#define TFT_MISO 19


Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST, TFT_MISO);

void setup() {


  tft.begin();


}


void loop(void) {
  int rotation = 1;
  tft.setRotation(rotation);

  delay(1000);
  tft.fillScreen(ILI9341_BLACK);


  tft.setCursor(45, 0);

  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(2);
  tft.println("ESP32 ILI9341 test!!");


}

In Arduino IDE, I downloaded the ESP32 boards from the Boards Manager:

Thankfully, the code worked when uploaded to the board:

Designing the ESP32 Wroom Board

Seeing as the display was working with the ESP32 breakout, I could begin creating my custom PCB. When designing my board, I followed alongside Neil’s example, adding two buttons and pullups for reset and boot. Because I was using an FTDI converter (RX / TX) to program this chip, I also needed to include a 3.3V voltage regulator. Here is how the board turned out:

Routing this board drove me absolute nuts, but after 45 minutes of struggle and squeezing the life out of the android plush in the lab, I came up with this:

For this board, I used the .005” engraving bit, 1/64” flat end mill bit, and 1/32” flat end mill bit. I kept all other settings as standard (refer to Electronics Production for more information).

I soldered the following components on the board, using a smaller iron tip and solder paste for the ESP32 chip.

Component Quantity
ESP32 Wroom 32 1
SMD blue LED 1
SMD white LED 1
6-pin SMT vertical male header 2
7-pin SMT vertical male header 1
2-pin SMT vertical male header 1
499 1206 SMD resistor 2
970 nF capacitor (~1uF) 2
Milled PCB 1
Tactile push button 2
3.3V voltage regulator 1
10k ohm resistor 2
.1 uF capacitor 1

Whew, hopefully there aren’t any bridges!

OpenWeatherMap API

As a major part of my final project, I wanted to incorporate a weather station using OpenWeatherMap API. I took inspiration from Baptiste Lardais’ final project, who similarly used the TFT LCD to display weather. I started by going to the the openweathermap website, creating a new account, and accessing a unique API key.

Ensuring that active is toggled, I copied the api key and typed the following into a web browser: https://api.openweathermap.org/data/2.5/weather?q=CITY,COUNTRY&APPID=APIKEY, inserting my city, country, and api key into the appropriate locations (i.e. Charlotte, US). I used this to confirm if it was displaying relatively accurate information:

To begin interfacing the LCD with the weather information, I used HTTPGET and the Arduino JSON library to fetch information from the API. Then, I used the Adafruit_ILI9341 library to display graphics on the screen. I found this tutorial to be super useful when it came to understanding how JSON worked!

Additionally, I wanted to add some custom bitmaps pertaining to the current weather. I ended up using a micro SD card, given by fellow student Ryan Zhou with the slot on the back of the screen. To begin formatting the sd card, I used a USB insert with the card inside. Adafruit created a project that was similar in nature and had also used openweathermap, so I simply downloaded their .bmp files (labeled accordingly to the icon codes on openweathermap) and moved them to the sd card. After right-clicking the USB location and clicking eject, I inserted the sd card into the screen’s slot. Additionally, I connected the Card CS pin to GPIO16 on the ESP32.

In Arduino IDE, under the Adafruit_Imagereader library, I modified parts of the ShieldILI9341 example to test if the screen could display the bitmaps alongside the weather data. I added these lines to ensure that the bitmap associated with the current weather would display:

String icon = myObject["weather"][0]["icon"]; 
String bmpFileName = "/" + icon + ".bmp"; // constructs the file location


reader.drawBMP(bmpFileName.c_str(), tft, -10, -50); // this function displays the bitmap according to the file, display, x pos. and y pos.

Programming the ESP32 Using RX / TX

To program the ESP32 wroom, I bought a FT232RL converter off amazon, which came with a cord and jumpers. Then, following Joaleong’s documentation, who had created a similar board, I learned that the computer needed to recognize two things in order to program the chip:

  1. The FTDI converter

  2. The ESP32 chip

Following her documentation, she suggested first checking if the computer (Macbook) recognizes the FTDI converter. I opened up terminal and typed the command ioreg -p IOUSB -l -w0 and confirmed the converter was picked up by the computer after seeing FT232RL pop up. Next, I installed an FTDI virtual COM port (VCP) driver, taken from here. I similarly downloaded the Max OS X10.15 and macOS11 version and it seemed to work fine, though you need to move the installer into Applications and give permission from System Preferences before running. After restarting my computer and opening up Arduino IDE, I saw the USB to UART port, called /dev/cu.usbserial-A50285BI.

I made the following connections between the converter and the chip. CTS (clear to send) and RTS (request to send) aren’t necessary in this case:

  • RXD to TX

  • TXD to RX

  • 5V to VCC

  • GND to GND

How to Upload a Program

  1. Hold down the boot button

  2. Upload the program as usual

  3. While it is uploading, press the reset button

Final Code

Here is the final code:

#include <Adafruit_GFX.h>         // Core graphics library
#include <Adafruit_ILI9341.h>     // Hardware-specific library
#include <SdFat.h>                // SD card & FAT filesystem library
#include <Adafruit_SPIFlash.h>    // SPI / QSPI flash library
#include <Adafruit_ImageReader.h> // Image-reading functions

#include <WiFi.h>
#include <HTTPClient.h>
#include <Arduino_JSON.h>

#include <NTPClient.h>
#include <WiFiUdp.h>

#include <TouchScreen.h>

String alternative;

String weekDays[7]={"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
String months[12]={"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
static const unsigned char PROGMEM image_weather_temperature_bits[] = {0x1c,0x00,0x22,0x02,0x2b,0x05,0x2a,0x02,0x2b,0x38,0x2a,0x60,0x2b,0x40,0x2a,0x40,0x2a,0x60,0x49,0x38,0x9c,0x80,0xae,0x80,0xbe,0x80,0x9c,0x80,0x41,0x00,0x3e,0x00};
static const unsigned char PROGMEM image_wifi_full_bits[] = {0x01,0xf0,0x00,0x07,0xfc,0x00,0x1e,0x0f,0x00,0x39,0xf3,0x80,0x77,0xfd,0xc0,0xef,0x1e,0xe0,0x5c,0xe7,0x40,0x3b,0xfb,0x80,0x17,0x1d,0x00,0x0e,0xee,0x00,0x05,0xf4,0x00,0x03,0xb8,0x00,0x01,0x50,0x00,0x00,0xe0,0x00,0x00,0x40,0x00,0x00,0x00,0x00};

#define USE_SD_CARD

#if defined(ESP8266)
  #define TFT_CS   5
  #define TFT_DC   17
  #define SD_CS    2
#elif defined(ESP32) && !defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2)
  #define TFT_CS   5
  #define TFT_DC   17
  #define SD_CS    16

#else // Anything else!
  #define TFT_CS   5
  #define TFT_DC   17
  #define SD_CS    16
#endif

#if defined(USE_SD_CARD)
  SdFat                SD;         // SD card filesystem
  Adafruit_ImageReader reader(SD); // Image-reader object, pass in SD filesys
#else
  // SPI or QSPI flash filesystem (i.e. CIRCUITPY drive)
  #if defined(__SAMD51__) || defined(NRF52840_XXAA)
    Adafruit_FlashTransport_QSPI flashTransport(PIN_QSPI_SCK, PIN_QSPI_CS,
      PIN_QSPI_IO0, PIN_QSPI_IO1, PIN_QSPI_IO2, PIN_QSPI_IO3);
  #else
    #if (SPI_INTERFACES_COUNT == 1)
      Adafruit_FlashTransport_SPI flashTransport(SS, &SPI);
    #else
      Adafruit_FlashTransport_SPI flashTransport(SS1, &SPI1);
    #endif
  #endif
  Adafruit_SPIFlash    flash(&flashTransport);
  FatVolume        filesys;
  Adafruit_ImageReader reader(filesys); // Image-reader, pass in flash filesys
#endif

Adafruit_ILI9341       tft    = Adafruit_ILI9341(TFT_CS, TFT_DC);
Adafruit_Image         img;        // An image loaded into RAM
int32_t                width  = 0, // BMP image dimensions
                       height = 0;


void timer();

const char* ssid = "CLSLabs";
const char* password = "clshawks";


String openWeatherMapApiKey = "e0f21e445c4ebf1aec3b6ebab38b7261";



String city = "Charlotte";
String countryCode = "US";


// For a final application, check the API call limits per hour/minute to avoid getting blocked/banned
unsigned long lastTime = 0;
// Timer set to 10 minutes (600000)
//unsigned long timerDelay = 600000;
// Set timer to 10 seconds (10000)
unsigned long timerDelay = 10000;

String jsonBuffer;


WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);


void setup(void) {

  ImageReturnCode stat; // Status from image-reading functions

  Serial.begin(9600);
  while(!Serial)  delay(100);       // Wait for Serial Monitor before continuing

  tft.begin();          // Initialize screen

  // The Adafruit_ImageReader constructor call (above, before setup())
  // accepts an uninitialized SdFat or FatVolume object. This MUST
  // BE INITIALIZED before using any of the image reader functions!
  Serial.print(F("Initializing filesystem..."));
#if defined(USE_SD_CARD)
  // SD card is pretty straightforward, a single call...
  if(!SD.begin(SD_CS, SD_SCK_MHZ(25))) { // ESP32 requires 25 MHz limit
    Serial.println(F("SD begin() failed"));
    for(;;); // Fatal error, do not continue
  }
#else
  // SPI or QSPI flash requires two steps, one to access the bare flash
  // memory itself, then the second to access the filesystem within...
  if(!flash.begin()) {
    Serial.println(F("flash begin() failed"));
    for(;;);
  }
  if(!filesys.begin(&flash)) {
    Serial.println(F("filesys begin() failed"));
    for(;;);
  }
#endif
  Serial.println(F("OK!"));

  // Fill screen blue. Not a required step, this just shows that we're
  // successfully communicating with the screen.


  // Load full-screen BMP file 'purple.bmp' at position (0,0) (top left).
  // Notice the 'reader' object performs this, with 'tft' as an argument.
  Serial.print(F("Loading weather.bmp to screen..."));
  tft.fillScreen(ILI9341_BLACK);
  //stat = reader.drawBMP("/01d.bmp", tft, 0, 0);
  // (Absolute path isn't necessary on most devices, but something
  // with the ESP32 SD library seems to require it.)
  //reader.printStatus(stat);   // How'd we do?


  delay(2000); // Pause 2 seconds before moving on to loop()
  WiFi.begin(ssid, password);
  timeClient.begin();
  timeClient.setTimeOffset(-14400);
  Serial.println("Connecting");
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to WiFi network with IP Address: ");
  Serial.println(WiFi.localIP());



}

void loop() {


    delay(1000); // Pause 1 sec.
    if ((millis() - lastTime) > timerDelay) {
    // Check WiFi connection status
    if(WiFi.status()== WL_CONNECTED){
      String serverPath = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "," + countryCode + "&APPID=" + openWeatherMapApiKey;

      jsonBuffer = httpGETRequest(serverPath.c_str());
      Serial.println(jsonBuffer);
      JSONVar myObject = JSON.parse(jsonBuffer);

      // JSON.typeof(jsonVar) can be used to get the type of the var
      if (JSON.typeof(myObject) == "undefined") {
        Serial.println("Parsing input failed!");
        return;
      }




      delay(1000);




      //tft.fillScreen(ILI9341_BLACK);

      tft.setRotation(1);

      String icon = myObject["weather"][0]["icon"];
      String bmpFileName = "/" + icon + ".bmp";


      reader.drawBMP(bmpFileName.c_str(), tft, -10, -50);


      tft.setRotation(1);
      tft.setTextSize(3);
      tft.setTextColor(ILI9341_WHITE);
      tft.setCursor(0, 0);
      timeClient.update();
      String hour = String(timeClient.getHours());
      String minute = String(timeClient.getMinutes());
      String weekDay = weekDays[timeClient.getDay()];
      tft.println(weekDay + ",");
      tft.setCursor(80, 0);
      tft.println(hour + ":" + minute);
      if(hour.toInt() >= 12){
    alternative = "PM";
  }else{
    alternative = "AM";
  }
      tft.setTextSize(2);
      tft.setCursor(170, 0);
      tft.println(alternative);
      tft.drawBitmap(301, 4, image_weather_temperature_bits, 16, 16, 0xFFFF);
      tft.drawBitmap(276, 4, image_wifi_full_bits, 19, 16, 0xFFFF);


      tft.setTextSize(2);
      Serial.print("JSON object = ");
      Serial.println(myObject);
      tft.setCursor(0, 175);
      tft.println("Temperature: ");
      tft.setCursor(150, 175);
      int temp = myObject["main"]["temp"]; 
       int new_temp = (temp - 273.15)*9/5 + 32;
       tft.println(new_temp);
       tft.setCursor (165, 175);  tft.println("  deg. F");
      Serial.print("Pressure: ");
      Serial.println(myObject["main"]["pressure"]);
      tft.setCursor(0, 195);
      tft.println("Humidity: ");
      tft.setCursor(110, 195);
      tft.println(myObject["main"]["humidity"]);
      tft.setCursor(0, 215);
      tft.println("Wind: ");
      tft.setCursor(65, 215);
      tft.println(myObject["wind"]["speed"]);
      tft.setCursor(155, 215);
      tft.println(city + ", " + countryCode);
   // taken from circuit python :D  weather = json.loads(weather)
     // weather_icon = weather['weather'][0]['icon']
      //self.set_icon("/sd/icons/"+weather_icon+".bmp")
      tft.drawRoundRect(5, 35, 90, 25, 4, ILI9341_WHITE);
      tft.fillRoundRect(5, 35, 90, 25, 4, ILI9341_WHITE);
      tft.setCursor(20, 45);
      tft.setTextSize(1);
      tft.setTextColor(ILI9341_BLACK);
      tft.println("POMO TIME!");



    }
    else {

      tft.setCursor(45, 20);
      tft.println("WiFi Disconnected");
    }
    lastTime = millis();
  }
}


String httpGETRequest(const char* serverName) {
  WiFiClient client;
  HTTPClient http;

  // Your Domain name with URL path or IP address with path
  http.begin(client, serverName);

  // Send HTTP POST request
  int httpResponseCode = http.GET();

  String payload = "{}"; 

  if (httpResponseCode>0) {
    Serial.print("HTTP Response code: ");
    Serial.println(httpResponseCode);
    payload = http.getString();
  }
  else {
    Serial.print("Error code: ");
    Serial.println(httpResponseCode);
  }
  // Free resources
  http.end();

  return payload;
}

and here is the result when I uploaded the code:

Group Assignment

This week, David Vaughn and I tested various examples of interfacing. You can find our group site here.

Individual Contribution

I mainly documented the introduction and comparison (benefits vs. drawbacks) for Processing and MIT App Inventor.

Reflection

As always, I liked the flexibility of this week: working with a custom web server and weather api for the first time was super interesting, and it ultimately will help me a lot in the programming portion of my final project. On a side note, I enjoy the repetition in electronics design/production over the last few weeks, especially as my boards get increasingly difficult to manufacture. It was nice to branch out and try the ESP32 Wroom, rather than always sticking to the seeed. In the future, I would love to try out other interfacing applications, such as MQTT, processing, etc.


Last update: June 19, 2024