Introduction: EPS8266 WEB LED Control + I2C Scanner

About: esp8266 projects

Am Ende des LED Projekts werden 16 LED mit einem I2C Treiber
über eine html Infoseite per WLAN angesteuert.

Im ersten Teil werde ich die Hardware und eine kleine Startseite für die LEDs erstellen.

Step 1: Ausgangspunkt Ist Das CaptivePortalAdvanced Beispiel

In den Sketch wede 4 Dateien geladen:

CaptivePortalAdvanced.ino

credentials.ino

handleHttp.ino

tools.ino

Nun wird der Sketch erstmal getestet. Alles OK?

Step 2: Erwiterungen in CaptivePortalAdvanced.ino

#include <ESP8266WiFi.h>
#include <<b>WiFiClient</b>.h>
#include <<b>ESP8266WebServer</b>.h>
#include <<b>DNSServer</b>.h>
#include <<b>ESP8266mDNS</b>.h>
#include <<b>EEPROM</b>.h>
#include <Wire.h>

/*
   This example serves a "hello world" on a WLAN and a SoftAP at the same time.
   The SoftAP allow you to configure WLAN parameters at run time. 
   They are not setup in the sketch but saved on EEPROM.

   Connect your computer or cell phone to wifi network ESP_ap with password 12345678. 
   A popup may appear and it allow you to go to WLAN config. 
   If it does not then navigate to <u>http://192.168.4.1/wifi</u> and config it there.
   Then wait for the module to connect to your wifi and take note of the WLAN IP it got. 
   Then you can disconnect from ESP_ap and return to your regular WLAN.

   Now the ESP8266 is in your network. 
   You can reach it through <u>http://192.168.x.x/</u> (the IP you took note of) 
   or maybe at <u>http://esp8266.local</u> too.

   This is a captive portal because through the softAP it will 
   redirect any http request to <u>http://192.168.4.1/</u>
*/

//////////////////////////// PINs /////////////////////////////////

// LED
#define BLINKPIN D8       // Status LED indicates a wifi / user interaction 
#define BLINKPIN_TOGGLE() (digitalWrite(BLINKPIN, !digitalRead(BLINKPIN)))   
#define BLINKPIN_ON()     (digitalWrite(BLINKPIN, HIGH))
#define BLINKPIN_OFF()    (digitalWrite(BLINKPIN, LOW))

//I2C TLC59116
#define TLC59116_SCL D1
#define TLC59116_SDA D2 


//////////////////////////// TLC59116  /////////////////////////////////
// Demo für I2C-LED-Treiber mit TLC59116
// ELV-AG Leer
// letzte Änderung 21.4.2011
// I2C -Adresse = 0C hex (alle Jumper A0 bis A3 offen)
// Hinweis zur Slaveadresse:
// Da bei der "Wire"-Funktion entfällt das letzte Bit, das für Lesen oder Schreiben (R/W) steht.
// Die Adresse hat somit nur noch 7 Bit, und sieht am Beispiel von C0hex so aus : 1100000b (60hex)
// 8Bit : 11000000 = C0h
// 7Bit : 1100000  = 60h

#define BRIGHTNESS 32  // max. 255
    int BRIGHTNESS_Array[16]={64,64,64,64, 64,64,64,64, 64,64,64,64, 64,64,64,64}; 
    int BRIGHTNESS_var = 32;

/* Set these to your desired softAP credentials. They are not configurable at runtime */
#ifndef APSSID
#define APSSID "ESP_LED"
#define APPSK  "12345678"
#endif

const char *softAP_ssid = APSSID;
const char *softAP_password = APPSK;

/* hostname for mDNS. Should work at least on windows. Try <u>http://esp8266.local</u> */
const char *myHostname = "esp8266";

/* Don't set this wifi credentials. They are configurated at runtime and stored on EEPROM */
char ssid[32] = "";
char password[32] = "";

// DNS server
const byte DNS_PORT = 53;
<b>DNSServer</b> dnsServer;

// Web server
<b>ESP8266WebServer</b> server(80);

/* Soft AP network parameters */
<b>IPAddress</b> apIP(172, 217, 28, 1);
<b>IPAddress</b> netMsk(255, 255, 255, 0);


/** Should I connect to WLAN asap? */
boolean connect;

/** Last time I tried to connect to WLAN */
unsigned long lastConnectTry = 0;

/** Current WLAN status */
unsigned int status = WL_IDLE_STATUS;

void setup() {
  
  pinMode(BLINKPIN, OUTPUT);
  BLINKPIN_ON();
  
  delay(1000);
  <b>Serial</b>.begin(9600);
  <b>Serial</b>.println();
  <b>Serial</b>.println("Configuring access point...");
  /* You can remove the password parameter if you want the AP to be open. */
  <b>WiFi</b>.softAPConfig(apIP, apIP, netMsk);
  <b>WiFi</b>.softAP(softAP_ssid, softAP_password);
  delay(500); // Without delay I've seen the IP address blank
  <b>Serial</b>.print("AP IP address: ");
  <b>Serial</b>.println(<b>WiFi</b>.softAPIP());

  /* Setup the DNS server redirecting all the domains to the apIP */
  dnsServer.setErrorReplyCode(<b>DNSReplyCode</b>::NoError);
  dnsServer.start(DNS_PORT, "*", apIP);

  /* Setup web pages: LED on/off */ 
  server.on("/LEDswitch", handleLED_switch);

  /* Setup web pages: I2C info on COM port */
  server.on("/i2c", handleStatusI2C);
    
  /* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */
  server.on("/", handleRoot);
  server.on("/wifi", handleWifi);
  server.on("/wifisave", handleWifiSave);
  server.on("/generate_204", handleRoot);  //Android captive portal. Maybe not needed. Might be handled by notFound handler.
  server.on("/fwlink", handleRoot);  //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.
  server.onNotFound(handleNotFound);
  server.begin(); // Web server start
  <b>Serial</b>.println("HTTP server started");
  loadCredentials(); // Load WLAN credentials from network
  connect = strlen(ssid) > 0; // Request WLAN connect if there is a SSID

  init_TLC59116();
  Set_LED_ALL(BRIGHTNESS_var);
  
  BLINKPIN_OFF();  
}

void connectWifi() {
  <b>Serial</b>.println("Connecting as wifi client...");
  <b>WiFi</b>.disconnect();
  <b>WiFi</b>.begin(ssid, password);
  int connRes = <b>WiFi</b>.waitForConnectResult();
  <b>Serial</b>.print("connRes: ");
  <b>Serial</b>.println(connRes);
}

void loop() {
  if (connect) {
    <b>Serial</b>.println("Connect requested");
    connect = false;
    connectWifi();
    lastConnectTry = millis();
  }
  {
    unsigned int s = <b>WiFi</b>.status();
    if (s == 0 && millis() > (lastConnectTry + 60000)) {
      /* If WLAN disconnected and idle try to connect */
      /* Don't set retry time too low as retry interfere the softAP operation */
      connect = true;
    }
    if (status != s) { // WLAN status change
      <b>Serial</b>.print("Status: ");
      <b>Serial</b>.println(s);
      status = s;
      if (s == WL_CONNECTED) {
        /* Just connected to WLAN */
        <b>Serial</b>.println("");
        <b>Serial</b>.print("Connected to ");
        <b>Serial</b>.println(ssid);
        <b>Serial</b>.print("IP address: ");
        <b>Serial</b>.println(<b>WiFi</b>.localIP());

        // Setup MDNS responder
        if (!<b>MDNS</b>.begin(myHostname)) {
          <b>Serial</b>.println("Error setting up MDNS responder!");
        } else {
          <b>Serial</b>.println("mDNS responder started");
          // Add service to MDNS-SD
          <b>MDNS</b>.addService("http", "tcp", 80);
        }
      } else if (s == WL_NO_SSID_AVAIL) {
        <b>WiFi</b>.disconnect();
      }
    }
    if (s == WL_CONNECTED) {
      <b>MDNS</b>.update();
    }
  }
  // Do work:
  //DNS
  dnsServer.processNextRequest();
  //HTTP
  server.handleClient();
}

Step 3: Erwiterungen in Credentials.ino

keine

/** Load WLAN credentials from EEPROM */
void loadCredentials() {
  EEPROM.begin(512);
  EEPROM.get(0, ssid);
  EEPROM.get(0 + sizeof(ssid), password);
  char ok[2 + 1];
  EEPROM.get(0 + sizeof(ssid) + sizeof(password), ok);
  EEPROM.end();
  if (String(ok) != String("OK")) {
    ssid[0] = 0;
    password[0] = 0;
  }
  Serial.println("Recovered credentials:");
  Serial.println(ssid);
  Serial.println(strlen(password) > 0 ? "********" : "");
}

/** Store WLAN credentials to EEPROM */
void saveCredentials() {
  EEPROM.begin(512);
  EEPROM.put(0, ssid);
  EEPROM.put(0 + sizeof(ssid), password);
  char ok[2 + 1] = "OK";
  EEPROM.put(0 + sizeof(ssid) + sizeof(password), ok);
  EEPROM.commit();
  EEPROM.end();
}

Step 4: Erwiterungen in HandleHttp.ino

/** Handle root or redirect to captive portal */
void handleRoot() {
  if (captivePortal()) { // If caprive portal redirect instead of displaying the page.
    return;
  }
  server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
  server.sendHeader("Pragma", "no-cache");
  server.sendHeader("Expires", "-1");

  String Page;
  Page += F(
            ""
            "HELLO WORLD!!");
  if (server.client().localIP() == apIP) {
    Page += String(F("<p>You are connected through the soft AP: ")) + softAP_ssid + F("</p>");
  } else {
    Page += String(F("<p>You are connected through the wifi network: ")) + ssid + F("</p>");
  }
/** Handle LED Control*/

  Page +=
    String(F(
             "\r\n<br>"
             "LED Control"
             ""));
  Page += String(F("\r\n<a href="/LEDswitch" rel="nofollow"> all LED </a>")); 
            
    for (int i = 1; i < 17; i++) {
      Page += String(F("\r\nLED")) + i + (F(""));
    }

    Page +=
    String(F(
      ""
      ""
      "\r\n<br>"));

  Page += F(
            "<p>You may want to test the <a href="/i2c" rel="nofollow">I2C status</a>. (COM port)</p>");
              
  Page += F(
            "<p>You may want to <a href="/wifi" rel="nofollow">config the wifi connection</a>.</p>"
            "");

  server.send(200, "text/html", Page);
}

/** Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */
boolean captivePortal() {
  if (!isIp(server.hostHeader()) && server.hostHeader() != (String(myHostname) + ".local")) {
    Serial.println("Request redirected to captive portal");
    server.sendHeader("Location", String("http://") + toStringIp(server.client().localIP()), true);
    server.send(302, "text/plain", "");   // Empty content inhibits Content-length header so we have to close the socket ourselves.
    server.client().stop(); // Stop is needed because we sent no content length
    return true;
  }
  return false;
}

/** Wifi config page handler */
void handleWifi() {
  server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
  server.sendHeader("Pragma", "no-cache");
  server.sendHeader("Expires", "-1");

  String Page;
  Page += F(
            ""
            "Wifi config");
  if (server.client().localIP() == apIP) {
    Page += String(F("<p>You are connected through the soft AP: ")) + softAP_ssid + F("</p>");
  } else {
    Page += String(F("<p>You are connected through the wifi network: ")) + ssid + F("</p>");
  }
  Page +=
    String(F(
             "\r\n<br>"
             "SoftAP config"
             "SSID ")) +
    String(softAP_ssid) +
    F(""
      "IP ") +
    toStringIp(WiFi.softAPIP()) +
    F(""
      ""
      "\r\n<br>"
      "WLAN config"
      "SSID ") +
    String(ssid) +
    F(""
      "IP ") +
    toStringIp(WiFi.localIP()) +
    F(""
      ""
      "\r\n<br>"
      "WLAN list (refresh if any missing)");
  Serial.println("scan start");
  int n = WiFi.scanNetworks();
  Serial.println("scan done");
  if (n > 0) {
    for (int i = 0; i < n; i++) {
      Page += String(F("\r\nSSID ")) + WiFi.SSID(i) + ((WiFi.encryptionType(i) == ENC_TYPE_NONE) ? F(" ") : F(" *")) + F(" (") + WiFi.RSSI(i) + F(")");
    }
  } else {
    Page += F("No WLAN found");
  }
  Page += F(
            ""
            "\r\n<br>Connect to network:"
            ""
            "<br>"
            "<br>"
            "<p>You may want to <a href="/" rel="nofollow">return to the home page</a>.</p>"
            "");
  server.send(200, "text/html", Page);
  server.client().stop(); // Stop is needed because we sent no content length
}

/** Handle the WLAN save form and redirect to WLAN config page again */
void handleWifiSave() {
  Serial.println("wifi save");
  server.arg("n").toCharArray(ssid, sizeof(ssid) - 1);
  server.arg("p").toCharArray(password, sizeof(password) - 1);
  server.sendHeader("Location", "wifi", true);
  server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
  server.sendHeader("Pragma", "no-cache");
  server.sendHeader("Expires", "-1");
  server.send(302, "text/plain", "");    // Empty content inhibits Content-length header so we have to close the socket ourselves.
  server.client().stop(); // Stop is needed because we sent no content length
  saveCredentials();
  connect = strlen(ssid) > 0; // Request WLAN connect with new credentials if there is a SSID
}

void handleNotFound() {
  if (captivePortal()) { // If caprive portal redirect instead of displaying the error page.
    return;
  }
  String message = F("File Not Found\n\n");
  message += F("URI: ");
  message += server.uri();
  message += F("\nMethod: ");
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += F("\nArguments: ");
  message += server.args();
  message += F("\n");

  for (uint8_t i = 0; i < server.args(); i++) {
    message += String(F(" ")) + server.argName(i) + F(": ") + server.arg(i) + F("\n");
  }
  server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
  server.sendHeader("Pragma", "no-cache");
  server.sendHeader("Expires", "-1");
  server.send(404, "text/plain", message);
}


/** Handle the WLAN LED link and redirect to WLAN root page again */
void handleLED_switch() {
BLINKPIN_ON();
 //   server.argName(i) => server.arg(i)  
    int arg1 = 0;    

    if (server.args() > 0){ // Werte vorhanden?
    arg1 =  server.arg(0).toInt();
        
    if ((arg1 <= 16) && (arg1 > 0)) {
        handleLED_switch_fkt(arg1);
      }
                               
    } // server.argName = ""
    else  {
          if (BRIGHTNESS_var > 127)
            BRIGHTNESS_var = 0;    
          else if (BRIGHTNESS_var < 128)  
            BRIGHTNESS_var = 255; 
            
          Set_LED_ALL(BRIGHTNESS_var);         
   } 
      
  String message = F("URI: ");
  message += server.uri();
  message += F("\nMethod: ");
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += F("\nArguments: ");
  message += server.args();  
  message += F("\n");
  Serial.print(message);

  for (uint8_t i = 0; i < server.args(); i++) {
    message += String(F("> ")) + server.argName(i) + F(": ") + server.arg(i) + F(" <\n");
  }
    
   server.sendHeader("Location", "/",true);   //redirect to WLAN root page
   server.send(302, "text/plane","");     

BLINKPIN_OFF();
}



/** Handle the WLAN I2C link and redirect to WLAN root page again */
void handleStatusI2C() {
byte error, address;
  int nDevices;

  Serial.println("Scanning...");

  nDevices = 0;
  for(address = 1; address < 127; address++ ) 
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the 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.print(address,HEX);
      Serial.println("  !");

      nDevices++;
    }
    else if (error==4) 
    {
      Serial.print("Unknown 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");
    
   server.sendHeader("Location", "/",true);   //redirect to WLAN root page
   server.send(302, "text/plane","");     
}

Step 5: Erwiterungen in Tools.ino

/** Is this an IP? */
boolean isIp(String str) {
  for (size_t i = 0; i < str.length(); i++) {
    int c = str.charAt(i);
    if (c != '.' && (c < '0' || c > '9')) {
      return false;
    }
  }
  return true;
}

/** IP to String? */
String toStringIp(IPAddress ip) {
  String res = "";
  for (int i = 0; i < 3; i++) {
    res += String((ip >> (8 * i)) & 0xFF) + ".";
  }
  res += String(((ip >> 8 * 3)) & 0xFF);
  return res;
}


//////////////////////////// TLC59116 ELV /////////////////////////////////

void init_TLC59116() {

  Wire.begin(TLC59116_SDA, TLC59116_SCL); // (SDA), (SCL)  
  Wire.beginTransmission(B1100000); // TLC59116 Slave Adresse ->C0 hex
  Wire.write(0x80);  // autoincrement ab Register 0h

  Wire.write(0x00);  // Register 00 /  Mode1  
  Wire.write(0x00);  // Register 01 /  Mode2 

  Wire.write(0x00);  // Register 02 /  PWM LED 1    // Default alle PWM auf 0
  Wire.write(0x00);  // Register 03 /  PWM LED 2    
  Wire.write(0x00);  // Register 04 /  PWM LED 3
  Wire.write(0x00);  // Register 05 /  PWM LED 4
  Wire.write(0x00);  // Register 06 /  PWM LED 5
  Wire.write(0x00);  // Register 07 /  PWM LED 6
  Wire.write(0x00);  // Register 08 /  PWM LED 7
  Wire.write(0x00);  // Register 09 /  PWM LED 8
  Wire.write(0x00);  // Register 0A /  PWM LED 9
  Wire.write(0x00);  // Register 0B /  PWM LED 10
  Wire.write(0x00);  // Register 0C /  PWM LED 11
  Wire.write(0x00);  // Register 0D /  PWM LED 12
  Wire.write(0x00);  // Register 0E /  PWM LED 13
  Wire.write(0x00);  // Register 0F /  PWM LED 14
  Wire.write(0x00);  // Register 10 /  PWM LED 15
  Wire.write(0x00);  // Register 11 /  PWM LED 16  // Default alle PWM auf 0

  Wire.write(0xFF);  // Register 12 /  Group duty cycle control
  Wire.write(0x00);  // Register 13 /  Group frequency
  Wire.write(0xAA);  // Register 14 /  LED output state 0  // Default alle LEDs auf PWM
  Wire.write(0xAA);  // Register 15 /  LED output state 1  // Default alle LEDs auf PWM
  Wire.write(0xAA);  // Register 16 /  LED output state 2  // Default alle LEDs auf PWM
  Wire.write(0xAA);  // Register 17 /  LED output state 3  // Default alle LEDs auf PWM
  Wire.write(0x00);  // Register 18 /  I2C bus subaddress 1
  Wire.write(0x00);  // Register 19 /  I2C bus subaddress 2
  Wire.write(0x00);  // Register 1A /  I2C bus subaddress 3
  Wire.write(0x00);  // Register 1B /  All Call I2C bus address
  Wire.write(0xFF);  // Register 1C /  IREF configuration  
  Wire.endTransmission();  // I2C-Stop
}


// Diese Funktion setzt die Helligkeit für ein LED-Register 
// Voraussetzung ist, das im entsprechende Register 14 bis 17 die LED aktiviert ist
// Übergabeparameter: LED = Nummer der LED / PWM = Helligkeitswert 0 -255

void Set_LED_PWM(int LED, int PWM)
{
  Wire.begin();             //I2C-Start
  Wire.beginTransmission(B1100000); // TLC59116 Slave Adresse ->C0 hex
  Wire.write(0x01 + LED);    // Register LED-Nr
  Wire.write(PWM);
  Wire.endTransmission();   // I2C-Stop
}

// Diese Funktion setzt die Helligkeit für alle LED-Register gleichzeitig
// Voraussetzung ist, das im entsprechende Register 14 bis 17 die LED aktiviert ist
// Übergabeparameter: PWM = Helligkeitswert 0 -255

void Set_LED_ALL(int PWM)
{
  Wire.begin();                     // I2C-Start
  Wire.beginTransmission(B1100000); // TLC59116 Slave Adresse ->C0 hex
  Wire.write(0x82);                 // Startregister 02h 
  for (int i=1 ; i < 17; i++){      // 16Bytes (Register 02h bis 11h) schreiben
    Wire.write(PWM);
  }
  Wire.endTransmission();           // I2C-Stop
}

//////////////////////////// TLC59116 WLAN /////////////////////////////////
void handleLED_switch_fkt(int arg1) {

   if (BRIGHTNESS_Array[arg1] > 127)
      BRIGHTNESS_Array[arg1] = 0;    
   else if (BRIGHTNESS_Array[arg1] < 128)  
      BRIGHTNESS_Array[arg1] = 255; 
  
   Set_LED_PWM(arg1,BRIGHTNESS_Array[arg1]); 
   Serial.print(arg1);  
   Serial.println(" done\n");           
}

Step 6: Sketch in Den ESP Hochladen Und Mit Dem AP Verbinden

Wenn das Hochladen erfolgreich war, sollte bei der WLAN Suche ein neuer AP mit Namen ESP_LED gefunden werden.

Das Passwort ist:12345678.

Änderungen für den Namen und das PW können in der CaptivePortalAdvanced.ino unter

#define APSSID "ESP_LED"
#define APPSK "12345678"

vorgenommen werden.

Step 7: " LED's Play "

Let's Play =)

Es können nun alle 16 LEDs über einen Link ein- bzw. ausgeschaltet oder einseln gesteuert werden.

PS:

Sollte der I2C Treiber nicht reggieren, kann ein kleines Tool [ I2C Scanner ] mit ./i2c aufgerufen werden.

Der I2C Scanner ist von Nick Gammon [ http://www.gammon.com.au/i2c ] und die Ausgabe erfolgt über den eingestelten COM-Port.

.

Scanning...

I2C device found at address 0x60 !

I2C device found at address 0x6B !

Done

.

Der 16 Kanal LED-I2C-Steuertreiber ist von ELV und für die Ansteuerung wurde Teile aus der I2C Arduino Demo.ino verwendet.

.

und nun viel Spaß beim Nachbauen