Pages

Friday, March 18, 2022

More buttons on fewer pins

For an index to all my stories click this text

I am working at at larger project in which I need an ESP8266 to send certain data over wifi depending on buttons being pressed. Nothing exiting you might think. However in one particular case I might need a lot of buttons.

The ESP8266 boards like a NodeMCU module or a Wemos D1 have about 11 digital I/O ports available. So I could attach a button to each I/O pin giving me 11 buttons. However that leaves no room for attaching other items like control-leds or a display.

It gets worse when you are working with really tiny controllers like the Attiny85. This micro-controller has, when normally used, just 5 I/O pins.

Let's have a look how the commercially available keyboards work.

 
 
As you can see they are build in a matrix layout. The 4 x 4 matrix has 4 rows and 4 columns. In the software you make sure that one of the rows is set to LOW, the rest to HIGH. This way you can test which column gets the LOW signal and that intersection is the key being pressed. If none of the columns gets a LOW signal the next row is set to LOW and the test starts over.

This works great however has some flaws for me. First the software makes the controller load high. You actively have to set each row in its turn LOW and the rest HIGH and then test which column gets the LOW signal. This will keep your controller pretty busy.

Next to that for 12 buttons you will need 7 I/O pins. For 16 buttons you will need 8 pins. This leaves little room for control leds although I could use neopixels for that which only take one I/O port. And this is no option for working with an Attiny85 with its mere 5 I/O pins.

I needed something better. So started thinking.

The binary system.

Let's look at the binary system. If I have one wire it can be HIGH or LOW. When using two wires there are already more possibilities:

LOW   LOW
LOW   HIGH
HIGH  LOW
HIGH  HIGH


So how do I put that to use. Lets attach switches to the 2 wires using this principle.




Look at the schematic. The two pull up resistors make sure that if no key is pressed both lines are HIGH. That is the HIGH-HIGH state.
There are three buttons.
The first button is connected to the first line, second button to the second and third button to both.
The idea is ok but it just does not work. If you press button one the ground will indeed be connected to the first line, but it will flow through the wire connected to button three. And as this button is connected to both wires, so ground will be connected to both wires.
We have to prevent that ground will be connected to two wires at the same time and that is easy.




Just put diodes on the wires connected to the buttons.
If you press button 1 ground will be connected to line 1. Ground wants to flow to button three but is prevented by the diode so can not flow back to line 2.

That is it. So with two lines we can use three switches.




Above you can see the breadboard setup. And best part: it really works.

The binary system with 3 wires

Using 3 wires we have the following posibillities:

LOW   LOW   LOW
LOW   LOW   HIGH
LOW   HIGH  LOW
LOW   HIGH  HIGH
HIGH  LOW   LOW
HIGH  LOW   HIGH
HIGH  HIGH  LOW
HIGH  HIGH  HIGH


Leaving the last one out because that is when no button is pressed, we have 7 posibillities. So 7 buttons with just 3 wires.

Lets look at the schematics again.


Not a lot different from our fist setup with only two wires.
Does it look familiar somewhere ???
Well if you are a faithfull reader of this blog you might recognise this from my stories on Charlieplexing. The hardware setup is not quite the same but it has some resemblance. You can re-read that story here:
https://lucstechblog.blogspot.com/2017/09/charlieplexing.html or search my index page for all the Charlieplexing stories: http://lucstechblog.blogspot.com/p/index-of-my-stories.html

Adding visual feedback

I wanted a visual feedback for testing purposes. For that I added a string of 7 neopixels. The reason is obvious: attaching a bunch of neopixels can be done with just 1 I/O pin. For those not familiar with the term: neopixels are adressable RGB leds officially called WS2812. For basic information on Neopixels re-read this story: http://lucstechblog.blogspot.com/2015/10/neopixels-ws2812-intro.html

The software.

Let's start with the software in Arduino IDE (C++).



#include <Adafruit_NeoPixel.h>
#define PIXEL_PIN    D8    
#define PIXEL_COUNT 7

Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);

void setup() 
{
  pinMode(D5, INPUT);
  pinMode(D6, INPUT);
  pinMode(D7, INPUT);
  attachInterrupt(digitalPinToInterrupt(D5), buttonpress, FALLING);
  attachInterrupt(digitalPinToInterrupt(D6), buttonpress, FALLING);
  attachInterrupt(digitalPinToInterrupt(D7), buttonpress, FALLING);
  strip.begin();
  strip.show();

}

void loop() 
{

}

void buttonpress()
{
  // button 1 ==> 011
  if (digitalRead(D5)== LOW && digitalRead(D6)== HIGH && digitalRead(D7)== HIGH)
  {
    strip.setPixelColor(0, 0,255,0); 
    strip.setPixelColor(1, 0,0,0);
    strip.setPixelColor(2, 0,0,0);
    strip.setPixelColor(3, 0,0,0);
    strip.setPixelColor(4, 0,0,0);
    strip.setPixelColor(5, 0,0,0);
    strip.setPixelColor(6, 0,0,0);
    strip.show(); 
    delay(500);
  }
  // button 2 ==> 101
  if (digitalRead(D5)== HIGH && digitalRead(D6)== LOW && digitalRead(D7)== HIGH)
  {
    strip.setPixelColor(0, 0,255,0); 
    strip.setPixelColor(1, 0,255,0);
    strip.setPixelColor(2, 0,0,0);
    strip.setPixelColor(3, 0,0,0);
    strip.setPixelColor(4, 0,0,0);
    strip.setPixelColor(5, 0,0,0);
    strip.setPixelColor(6, 0,0,0);
    strip.show(); 
    delay(500);
  }
  // button 3 ==> 110
  if (digitalRead(D5)== HIGH && digitalRead(D6)== HIGH && digitalRead(D7)== LOW)
  {
    strip.setPixelColor(0, 0,255,0); 
    strip.setPixelColor(1, 0,255,0);
    strip.setPixelColor(2, 0,255,0);
    strip.setPixelColor(3, 0,0,0);
    strip.setPixelColor(4, 0,0,0);
    strip.setPixelColor(5, 0,0,0);
    strip.setPixelColor(6, 0,0,0); 
    strip.show(); 
    delay(500);
  }
    // button 4 ==> 100
    if (digitalRead(D5)== HIGH && digitalRead(D6)== LOW && digitalRead(D7)== LOW) //
  {
    strip.setPixelColor(0, 0,255,0); 
    strip.setPixelColor(1, 0,255,0);
    strip.setPixelColor(2, 0,255,0);
    strip.setPixelColor(3, 0,255,0);
    strip.setPixelColor(4, 0,0,0);
    strip.setPixelColor(5, 0,0,0);
    strip.setPixelColor(6, 0,0,0);
    strip.show(); 
    delay(500);
  }
    // button 5 ==> 001
    if (digitalRead(D5)== LOW && digitalRead(D6)== LOW && digitalRead(D7)== HIGH) //
  {
    strip.setPixelColor(0, 0,255,0); 
    strip.setPixelColor(1, 0,255,0);
    strip.setPixelColor(2, 0,255,0);
    strip.setPixelColor(3, 0,255,0);
    strip.setPixelColor(4, 0,255,0);
    strip.setPixelColor(5, 0,0,0);
    strip.setPixelColor(6, 0,0,0);
    strip.show(); 
    delay(500);
  }
    // button 6 ==> 010
    if (digitalRead(D5)== LOW && digitalRead(D6)== HIGH && digitalRead(D7)== LOW) //
  {
    strip.setPixelColor(0, 0,255,0); 
    strip.setPixelColor(1, 0,255,0);
    strip.setPixelColor(2, 0,255,0);
    strip.setPixelColor(3, 0,255,0);
    strip.setPixelColor(4, 0,255,0);
    strip.setPixelColor(5, 0,255,0);
    strip.setPixelColor(6, 0,0,0);
    strip.show(); 
    delay(500);
  }
    // button 7 ==> 000
    if (digitalRead(D5)== LOW && digitalRead(D6)== LOW && digitalRead(D7)== LOW) //
  {
    strip.setPixelColor(0, 0,255,0); 
    strip.setPixelColor(1, 0,255,0);
    strip.setPixelColor(2, 0,255,0);
    strip.setPixelColor(3, 0,255,0);
    strip.setPixelColor(4, 0,255,0);
    strip.setPixelColor(5, 0,255,0);
    strip.setPixelColor(6, 0,255,0); 
    strip.show(); 
    delay(500);
  }
}


Before I get a load of comments in my mail on this code let me make it clear that this source code can be optimised a lot. That would make it a lot more complicated for beginning programmers to comprehend. So adjust it for your own purposes.

The program starts with initialising the Neopixel library and attaching the library to pin D8.

In the setup routine the input lines D5, D6 and D7 are defined, and we attach an interrupt to each of these lines. All the interrupts point to the same routine: buttonpress on a FALLING signal. This means that when a button is pressed (connected to ground) the program starts the interrupt routine.

This is a big advantage over the software for the commercial keyboards. Your software is not constant sending LOW signals to row-pins and polling the collumn-pins. We just wait till a key is pressed and in the mean time the micro-controller has loads of time for other tasks.

When a button is pressed the program jumps to the buttonpress() routine.

Here is a closer examination about what happens when button 4 is pressed:

    // button 4 ==> 100
    if (digitalRead(D5)== HIGH && digitalRead(D6)== LOW && digitalRead(D7)== LOW) //
  {
    strip.setPixelColor(0, 0,255,0);
    strip.setPixelColor(1, 0,255,0);
    strip.setPixelColor(2, 0,255,0);
    strip.setPixelColor(3, 0,255,0);
    strip.setPixelColor(4, 0,0,0);
    strip.setPixelColor(5, 0,0,0);
    strip.setPixelColor(6, 0,0,0);
    strip.show();
    delay(500);
  }

The diodes from button 4 are attached to D6 and D7. So when button 4 is pressed D5 will get no signal and the pull-up resistor makes sure the line is HIGH. Line D6 and D7 will be connected to ground. This is what the if statement analyses.

Next part is just setting 4 of the neopixels to green, so we know for sure button 4 is pressed. The delay makes sure that the neopixels are lit before we go on.

Simple and efficient !!!
We got lots of room and time for the processor to perform other tasks in stead of constant sending signals and polling lines and have the risk of missing a keypress.

You will notice that the only thing you have to do is to alter D5, D6, D7 and D8 and this will also work on the ESP32 or any of the Arduino family even on the humble Attiny85 !!!

Basic program

No I am not forgetting ESP-Basic, - my used to be - favorite rapid devellopment environment (that's a mouth full). In fact the hardware setup is so efficient that writing the software in Basic is very easy. It looks a lot like the C++ program.



interrupt d5, [CHANGE]
interrupt d6, [CHANGE]
interrupt d7, [CHANGE]

neo.setup(d8)
neo.cls()

wait

[CHANGE] 

interrupt d5
interrupt d7
interrupt d6

if ((io(pi,d5) = 0) and (io(pi,d6) = 1) and (io(pi,d7) = 1)) then
neo.cls()
neo(0,250,250,250)
endif
 
if ((io(pi,d5) = 1) and (io(pi,d6) = 0) and (io(pi,d7) = 1)) then
neo.cls()
neo(0,250,250,250)
neo(1,250,250,250)
endif

if ((io(pi,d5) = 1) and (io(pi,d6) = 1) and (io(pi,d7) = 0)) then
neo.cls()
neo(0,250,250,250)
neo(1,250,250,250)
neo(2,250,250,250)
endif

if ((io(pi,d5) = 1) and (io(pi,d6) = 0) and (io(pi,d7) = 0)) then
neo.cls()
neo(0,0,0,250)
neo(1,0,0,250)
neo(2,0,0,250)
neo(3,0,0,250)
endif

if ((io(pi,d5) = 0) and (io(pi,d6) = 0) and (io(pi,d7) = 1)) then
neo.cls()
neo(0,250,0,0)
neo(1,250,0,0)
neo(2,250,0,0)
neo(3,250,0,0)
neo(4,250,0,0)
endif


if ((io(pi,d5) = 0) and (io(pi,d6) = 1) and (io(pi,d7) = 0)) then
neo.cls()
neo(0,0,250,0)
neo(1,0,250,0)
neo(2,0,250,0)
neo(3,0,250,0)
neo(4,0,250,0)
neo(5,0,250,0)
endif


if ((io(pi,d5) = 0) and (io(pi,d6) = 0) and (io(pi,d7) = 0)) then
neo.cls()
neo(0,250,0,250)
neo(1,250,0,250)
neo(2,250,0,250)
neo(3,250,0,250)
neo(4,250,0,250)
neo(5,250,0,250)
neo(6,250,0,250)
endif

delay (500)

interrupt d6, [CHANGE]
interrupt d7, [CHANGE]
interrupt d5, [CHANGE]

delay (500)
cls
wait 

end


A quick examination of the program:

interrupt d5, [CHANGE]
interrupt d6, [CHANGE]
interrupt d7, [CHANGE]

neo.setup(d8)
neo.cls()

wait

This is the main part where the interrupts are defined and the neopixels are attached to IO pin D8

[CHANGE]

interrupt d5
interrupt d7
interrupt d6

This is a tricky part.

If the [CHANGE] routine is called (as a button is pressed) we do not want that the routine is accidentally (think bouncing buttons) called again. So these commands disable the interrupts.

if ((io(pi,d5) = 1) and (io(pi,d6) = 0) and (io(pi,d7) = 0)) then
neo.cls()
neo(0,0,0,250)
neo(1,0,0,250)
neo(2,0,0,250)
neo(3,0,0,250)
endif

This is almost the same as in the C++ routine and is the part where button 4 is processed.
As you can see D5 gets no signal so that will be pulled HIGH by the pull-up resistors. D6 and D7 should both get a LOW signal. If this is true the first 4 neopixels (0 to 3) will be set to blue.

interrupt d6, [CHANGE]
interrupt d7, [CHANGE]
interrupt d5, [CHANGE]

These lines will activate the interrupts anew when the routine has finished.

As there are two delay (500) statements the program will register a button-press once every second. That should be fast enough for most purposes.

Expanding

Using 4 I/O pins you can attach 15 buttons which is enough for a simple calculator keyboard. And with 5 I/O pins you can attach 31 buttons !!
I am sure you can work the schematics out for yourselves.

Let's elaborate on this.
With 5 I/O pins you can attach 31 buttons. Now suppose you use one of these 31 buttons as a Shift key and another one as a Second Function key you could build a simple ASCII keyboard using an Arduino Pro Micro which has a real USB interface.

Pros and cons.

So why isn't this used more often, and why are all the commercial keyboards build as a matrix ssytem and not in this way.
The answer is simple: costs.
Using the matrix layout you only need buttons. In this setup you need buttons and diodes which is more expensive and makes the PCB a bit more complicated.

The pros for me weigh much more as the cons.
For the 12 button commercial version you will need to use 7 I/O pins on your controller. I only need 4 and can use 15 buttons (3 more).
The additional costs are so small that they are neglectable. At the moment of this writing you can buy 100 diodes for 50 cents !!!
Next to that the software does not put a heavy load on my controller and is much easier to program.

And last but not least: on an Attiny85 I can attach 15 buttons and a string of neopixels at the same time !!!

Real world setup




Above you can see my real world setup and test. The only difference with the schematics in this story is that I attached a seperate power supply for the Neopixels.

That's it for now.

Till next time and have fun.

Luc Volders

Friday, March 11, 2022

ESP webserver tutorial part 5 - Neopixel control

For a complete index to all my articles click this text.

This is a series on building a webpage with the ESP8266 and the ESP32. When this series is completed all the information on building a webpage with buttons, textfields, sliders and color pickers witch have interaction with the ESP8266 or ESP32 will be discussed and demonstrated.

This fifth part leans heavy on the previous four parts in this series, so I urge you to read these stories first:

ESP webserver tutorial part 1 - textfields
ESP webserver tutorial part 2 - button
ESP webserver tutorial part 3 - button with save
ESP webserver tutorial part 4 - slider

The last part showed you how to build a webpage with the ESP8266 and ESP32 that incorporates a slider with which you can dim a led.



As promised in that story I am going to show you here how to build a webpage with 3 sliders with which you can control the colors of a ledstrip. The ledstrip I am going to use here is a strip of Neopixels.


Again I am writing the program in Arduino language (C++)

As a bonus there is a nice trick build into the software that makes the colors change automatically when you alter the slider settings. You do not have to press a confirmation button as was required in the previous story.

The ESP8266 breadboard setup.

The breadboard setup is off course different from the setup in the previous stories because we are goiung to attach a neopixels strip to the ESP8266. You can find a full blown explanation/tutorial on Neopixels in a previous story on my weblog which you can find here:
http://lucstechblog.blogspot.com/2015/10/neopixels-ws2812-intro.html



In this setup I used an ESP8266 (the3 Wemos D1 mini version). The Neopixelstrip is connected to D7 with a current delimiting resistor of 470 Ohm. And a large capacitor for stabilising the current is connected over the power lines (mind the polarity). I used the symbol of an existing neopixel strip from the fritzing library but you may use a ring of strip or even matrix of your own liking. Keep the amount of neopixels low (like 7 to 8) and you can get the current directly from the ESP8266. If you want to attach a larger amount of neopixels make sure you give them an external power supply.

The ESP32 breadboard setup.

The ESP32 breadboard setup is equally simple.



I am using the ESP32 Devkit board. These boards will not fit a breadboard. Therefore I use 2 breadboards. From one of these breadboards I stripped the power rail as discussed in the story you can read here:
https://lucstechblog.blogspot.com/2018/08/breadboard-hack-for-esp32-and-esp8266.html

The led is attached to GPIO22 through a 470 Ohm current delimiting resistor, and there is a large capacitor connected to the power rail to stabilising the current.
I attached an external USB power connector because the ESP32 consumes a lot of power in itself and attaching a ledstrip might impose too much to its power leads.
You can find a detailed explanation on powering the ESP32 in my book (which is in Dutch or English) called ESP32 uitgelegd and in English ESP32 Simplified on which you can find detailed info here:
http://lucstechblog.blogspot.com/2019/07/esp32-uitgelegd-nu-verkrijgbaar.html 

and here https://lucstechblog.blogspot.com/2020/10/esp32-simplified-world-wide-available.html

You can find a story on external powering the ESP microcontrollers here:
https://lucstechblog.blogspot.com/2017/10/powering-your-project.html

The program

Just like the previous entries in this series the program is written in Arduino language (C++) and is almost identical for the ESP8266 and the ESP32. The changes for the ESP32 are really minimal. I will give you the ESP8266 program as a whole and you can find the few lines which have to be modified for the ESP32 at the end of this story.


// ======================================================
// Libraries needed
// ======================================================

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <Adafruit_NeoPixel.h>

// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS      7

// Your credentials
ESP8266WebServer server(80);
const char* ssid = "XXXXXXXXXXXXXXX";
const char* password = "YYYYYYYYYYYYYY";

int neoPin = D7; 

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, neoPin, NEO_GRB + NEO_KHZ800);

String responseHTML = ""
  "<!DOCTYPE html><html><head><title>CaptivePortal</title></head><body>"
  "<h1>Hello World!</h1><p>This is a captive portal example. All requests will "
  "be redirected here.</p></body></html>";

// ==========================================
// initial variables
// ========================================== 

int sliderREDvalue = 0;
int sliderGREENvalue = 0;
int sliderBLUEvalue = 0;

// =========================================
// Build the webpage
// =========================================
String getPage()
  {
  String page = "<!DOCTYPE HTML>";
  page += "<html>";
  page += "<head>";
  page += "<meta name = \"viewport\" content = \"width = device-width, initial-scale = 1.0 maximum-scale = 2.5, user-scalable=1\">";
  page += "<title>Luc's Neopixels slider demo</title>";
  page += "<style>";
  page += "body { background-color: powderblue}";
  page += "</style>";
  page += "</head>";
  page += "<body>";
  page += "<h1 style='color:red'>Luc's Neopixels slider demo</h1>";

  page += "<form id=\"myForm\" action=\"/\" method=\"post\">";
  page += "Move the slider for choosing the color :<br><br>";
  page += "RED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
  page += "<input type=\"range\" min=\"0\" max=\"255\" onmouseleave=\"myFunction()\" id=\"sliderred\" name=\"sliderred\" value=\"";
  page += sliderREDvalue;
  page +="\" > <br>";
  
  page += "GREEN&nbsp;&nbsp;&nbsp;";
  page += "<input type=\"range\" min=\"0\" max=\"255\" onmouseleave=\"myFunction()\" id=\"slidergreen\" name=\"slidergreen\" value=\"";
  page += sliderGREENvalue;
  page +="\" > <br>";
  
  page += "BLUE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
  page += "<input type=\"range\" min=\"0\" max=\"255\" onmouseleave=\"myFunction()\" id=\"sliderblue\" name=\"sliderblue\" value=\"";
  page += sliderBLUEvalue;
  page +="\" > <br>";
  page += "</form>";
  page += "<br>";

  page += "<script>";
  page += "function myFunction() {";
  page += "document.getElementById(\"myForm\").submit()";
  page +="}";
  page +="</script>";

  page += "</body>";
  page += "</html>";
  return page;
  }




// ==========================================
// Handle if unknown command
// ==========================================
void handleNotFound()
{
  server.send(200, "text/html", getPage());
}


// ===========================================
// Submit page
// ===========================================
void handleSubmit()
  {
  if (server.hasArg("slidergreen"))
     {
      sliderGREENvalue = server.arg("slidergreen").toInt();
      Serial.print("The slider Green has value: ");
      Serial.println(sliderGREENvalue);
      for(int i=0;i<NUMPIXELS;i++)
         {
    // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
    pixels.setPixelColor(i, pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue)); // Moderately bright green color.

    pixels.show(); // This sends the updated pixel color to the hardware.
         }
      }

   if (server.hasArg("sliderred"))
     {
      sliderREDvalue = server.arg("sliderred").toInt();
      Serial.print("The slider Red has value: ");
      Serial.println(sliderREDvalue);
      for(int i=0;i<NUMPIXELS;i++)
         {
    // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
    pixels.setPixelColor(i, pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue)); // Moderately bright green color.
    pixels.show(); // This sends the updated pixel color to the hardware.
         }
      }     

   if (server.hasArg("sliderblue"))
     {
      sliderBLUEvalue = server.arg("sliderblue").toInt();
      Serial.print("The slider Blue has value: ");
      Serial.println(sliderBLUEvalue);
      for(int i=0;i<NUMPIXELS;i++)
        {
    // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
    pixels.setPixelColor(i, pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue)); // Moderately bright green color.
    pixels.show(); // This sends the updated pixel color to the hardware.
        }
      } 
  server.send(200, "text/html", getPage());       //Response to the HTTP request
  }  

// =============================================
// Test if commands received
// =============================================
void handleRoot() 
    {   
    if (server.args() ) 
       {
       handleSubmit();
       } 
       else 
       {
       server.send(200, "text/html", getPage());  
       }
    }

// =============================================
// Setup
// =============================================
void setup()
    {
    delay(1000);
    Serial.begin(115200);

    // WiFi initilisation part
    // 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();
     server.on("/", handleRoot);
     server.onNotFound(handleNotFound);
     delay(500);

     pixels.begin(); // This initializes the NeoPixel library.
 
     pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue);
     pixels.show();
     }


// ===============================================
// LOOP
// =============================================== 
void loop()
  {  
  server.handleClient(); 
  delay(50);
  }

The program is almost identical to the program in the previous stories. All explanation therefore has been done in those stories except for the few lines that are required for putting the three sliders on the screen. This is done in the <form> part of the webpage.

First we need to activate the neopixels code and that is done by these entries at the beginning of the program:


#include <Adafruit_NeoPixel.h>

int neoPin = D7;

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, neoPin, NEO_GRB + NEO_KHZ800);

As you can see the neopixels code is attached to I/O pin D7.

int sliderREDvalue = 0;
int sliderGREENvalue = 0;
int sliderBLUEvalue = 0;

The above lines make sure that the neopixels are set off when the program starts.

In the setup() routine at the end of the program the IP adress is printed in the Serialmonitor so you will know which IP adress you should fill in on your computers or phones browser to find the webpage.

  pixels.begin(); // This initializes the NeoPixel library.


  pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue);

  pixels.show();

These lines activate the neopixels and set the color to the initial values which were defined at the start of the program (0,0,0).

The important changes are in the HTML code where the form is made and the sliders defined.

  page += "<form id=\"myForm\" action=\"/\" method=\"post\">";

  page += "Move the slider for choosing the color :<br><br>";

    page += "RED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";

    page += "<input type=\"range\" min=\"0\" max=\"255\" onmouseleave=\"myFunction()\" id=\"sliderred\" name=\"sliderred\" value=\"";

  page += sliderREDvalue;

  page +="\" > <br>";

    page += "GREEN&nbsp;&nbsp;&nbsp;";

    page += "<input type=\"range\" min=\"0\" max=\"255\" onmouseleave=\"myFunction()\" id=\"slidergreen\" name=\"slidergreen\" value=\"";

  page += sliderGREENvalue;

  page +="\" > <br>";

    page += "BLUE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";

    page += "<input type=\"range\" min=\"0\" max=\"255\" onmouseleave=\"myFunction()\" id=\"sliderblue\" name=\"sliderblue\" value=\"";

  page += sliderBLUEvalue;

  page +="\" > <br>";

  page += "</form>";

As you can see there are some things different from the program in the previous story.

page += "RED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";

After each name of the slider I put some &nbsp commands wich will neatly lineup the sliders. You could do this even better by putting the names of the sliders and the sliders itself into a table. A nice HTML excercise for those who want to expand their knowledge.

Now lets look at the html code that defines the red slider.

page += "<input type=\"range\" min=\"0\" max=\"255\" onmouseleave=\"myFunction()\" id=\"sliderred\" name=\"sliderred\" value=\"";

The first part is easy to understand. The type of input is range which means a slider and the minimum and maximum values are defined.
onmouseleave is an HTML function that is activated when the mouse moves over the slider and then leaves its aerea. When that happens the function myFunction() is called.

The code for the green and blue sliders is almost identical as you can see. The difference is in the id and name settings.

page += "<script>";

page += "function myFunction() {";

 page += "document.getElementById(\"myForm\").submit()";

page +="}";

page +="</script>";

At the end of the HTML page the Javascript function myFunction is called when the mouse has left the area of one of the sliders. This code actually takes the values of the sliders from the form and uses submit() to send them to the webserver.

In the handlesubmit() routine the webserver receives the information from the form on the webpage and the slider settings are analysed. Every slider info is analysed individually and processed. Lets look at the information from the green slider coming in:

  if (server.hasArg("slidergreen"))
     {
      sliderGREENvalue = server.arg("slidergreen").toInt();
      Serial.print("The slider Green has value: ");
      Serial.println(sliderGREENvalue);
      for(int i=0;i<NUMPIXELS;i++)
         {
    // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
    pixels.setPixelColor(i, pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue)); // Moderately bright green color.
    pixels.show(); // This sends the updated pixel color to the hardware.
         }
      }

As you can see the name slidergreen is the name that is tested for. That is why each slider got his own name and ID in the above mentioned HTML code.
The received value is put into the sliderGREENvalue variable and printed in the Serialmonitor.
Then the information is send to the neopixels strip. All slider values are send so the settings of the other colors is not changed unless their sliders have been changed.

Dont forget the pixels.show() command. that will actually activate the new settings.

ESP32 code

Well there is not much difference in the ESP8266 and ESP32 code on the software side.

The same Adafruit Neopixel library is used as for the ESP8266 as its compatible for both. We only have to alter the pin assignment as you can see in the breadboard setup. The Neopixels are attached to pin 22.

int neoPin = D7;

And for the webserver we only have to Change the following lines:

#include <ESP8266WiFi.h>;
#include <ESP8266WebServer.h>;

ESP8266WebServer server(80);

into:

#include <WiFi.h>;
#include <WebServer.h>;

WebServer server(80);

Thats all.

Pitfall.

The above code works flawlessly on my PC with Windows 10 in the Firefox and Chrome browsers. There is however a problem with this code which is not really interesting when you use it to control a ledstrip or neopixels. It might impose a problem if you are going to use this for steering a motor, pump or likewise.


As you can see the slidervalues are send very often to the webserver and processed. Indeed evrytime you move your mouse over a slider even if you did not alter it. So if you alter and use this code to send PWM values to a motor or pump the motor might hick up everytime the info is send and processed in the webserver.

You can avoid this by changing onmouseleave in onblur like this in the red slider code:


page += "<input type=\"range\" min=\"0\" max=\"255\" onblur=\"myFunction()\" id=\"sliderred\" name=\"sliderred\" value=\"";

Pitfall 2

Another pitfall is that this code does not work on Android phones (and Iphones for that) because the html onmouse functions are not compatble with touch screens.

So to get this working on PC and on Android systems we sadly have to revert to the old system where a button is pressed after the slider has been changed. I will give you the code for this here in its total without any explanation and am sure you can figure it out yourself.


// ======================================================
// Libraries needed
// ======================================================

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <Adafruit_NeoPixel.h>

// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS      7

// Your credentials
ESP8266WebServer server(80);
const char* ssid = "ZyXEL9680AC";
const char* password = "B9BB53BCA049";

int neoPin = D7; 

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, neoPin, NEO_GRB + NEO_KHZ800);

String responseHTML = ""
  "<!DOCTYPE html><html><head><title>CaptivePortal</title></head><body>"
  "<h1>Hello World!</h1><p>This is a captive portal example. All requests will "
  "be redirected here.</p></body></html>";

// ==========================================
// initial variables
// ========================================== 

int sliderREDvalue = 0;
int sliderGREENvalue = 0;
int sliderBLUEvalue = 0;

// =========================================
// Build the webpage
// =========================================
String getPage()
  {
  String page = "<!DOCTYPE HTML>";
  page += "<html>";
  page += "<head>";
  page += "<meta name = \"viewport\" content = \"width = device-width, initial-scale = 1.0 maximum-scale = 2.5, user-scalable=1\">";
  page += "<title>Luc's Neopixels slider demo</title>";
  page += "<style>";
  page += "body { background-color: powderblue}";
  page += "</style>";
  page += "</head>";
  page += "<body>";
  page += "<h1 style='color:red'>Luc's Neopixels slider demo</h1>";

  page += "<form action=\"/\" method=\"post\">";
  page += "Move the slider for choosing the color :<br><br>";
  page += "RED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
  page += "<input type=\"range\" min=\"0\" max=\"255\" id=\"sliderred\" name=\"sliderred\" value=\"";
  page += sliderREDvalue;
  page +="\" class=\"slider\"> <br>";
  
  page += "GREEN&nbsp;&nbsp;&nbsp;";
  page += "<input type=\"range\" min=\"0\" max=\"255\" id=\"slidergreen\" name=\"slidergreen\" value=\"";
  page += sliderGREENvalue;
  page +="\" class=\"slider\"> <br>";
  
  page += "BLUE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
  page += "<input type=\"range\" min=\"0\" max=\"255\" id=\"sliderblue\" name=\"sliderblue\" value=\"";
  page += sliderBLUEvalue;
  page +="\" class=\"slider\"> <br><br>";
  page += "<input type=\"submit\" value=\"Confirm\">";
  page += "</form>";
  page += "<br>";
  page += "<br>";
  page += "<br>";

  page += "</body>";
  page += "</html>";
  return page;
  }




// ==========================================
// Handle if unknown command
// ==========================================
void handleNotFound()
  {
  server.send(200, "text/html", getPage());
  }


// ===========================================
// Submit page
// ===========================================
void handleSubmit()
  {
  if (server.hasArg("slidergreen"))
     {
      sliderGREENvalue = server.arg("slidergreen").toInt();
      Serial.print("The slider has value: ");
      Serial.println(sliderGREENvalue);
      for(int i=0;i<NUMPIXELS;i++)
         {
          // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
          pixels.setPixelColor(i, pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue)); // Moderately bright green color.
          pixels.show(); // This sends the updated pixel color to the hardware.
         }
      }

   if (server.hasArg("sliderred"))
      {
      sliderREDvalue = server.arg("sliderred").toInt();
      Serial.print("The slider has value: ");
      Serial.println(sliderREDvalue);
      for(int i=0;i<NUMPIXELS;i++)
         {
         // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
         pixels.setPixelColor(i, pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue)); // Moderately bright green color.
         pixels.show(); // This sends the updated pixel color to the hardware.
        }
      }     

   if (server.hasArg("sliderblue"))
      {
      sliderBLUEvalue = server.arg("sliderblue").toInt();
      Serial.print("The slider has value: ");
      Serial.println(sliderBLUEvalue);
      for(int i=0;i<NUMPIXELS;i++)
         {
         // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
         pixels.setPixelColor(i, pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue)); // Moderately bright green color.
         pixels.show(); // This sends the updated pixel color to the hardware.
         }
      } 

  server.send(200, "text/html", getPage());       //Response to the HTTP request
}  

// =============================================
// Test if commands received
// =============================================
void handleRoot() 
     {   
     if (server.args() ) 
        {
        handleSubmit();
        } 
     else 
        {
      server.send(200, "text/html", getPage());  
        }
     }

// =============================================
// Setup
// =============================================
void setup()
  {
  delay(1000);
  Serial.begin(115200);

  // WiFi initilisation part
  // 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();
  server.on("/", handleRoot);
  server.onNotFound(handleNotFound);

  server.begin();
  delay(500);

  pixels.begin(); // This initializes the NeoPixel library.
 
  pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue);
  pixels.show();
  }


//++++++++++++++++++++++++++++++++++++++++++++++//
//              LOOP                            //
//++++++++++++++++++++++++++++++++++++++++++++++//
void loop()
  {  
  server.handleClient(); 
  delay(50);
  }


This will look on your PC screen like this.



On android it looks like this.



Going further.

So there you have it a complete means to put sliders on a webpage and control the ESP8266 or ESP32 with them.

There are several uses for this. You can change the hardware and code to use this with RGB ledstrips in stead of Neopixels. The webserver software should not be difficult to adapt. You can find the hardware here:
https://lucstechblog.blogspot.com/2018/04/rgb-strip-control-over-wifi-part-2.html

You could also use this to rebuild the remote controlled car which I showed you in a previous story and in which the software was written in ESPBasic which you can find here: http://lucstechblog.blogspot.com/2019/02/remote-controlled-car-with-wifi.html

And combining buttons and a slider you could even rewrite the tea-timer I build in this story: https://lucstechblog.blogspot.com/2018/08/teatimer.html

And like stated earlier in this story you could make the webpage more structured by putting the texts and sliders in a table. You can learn how to do that and everything else about HTML and javascript from this website: https://www.w3schools.com/

Now you have all the tools to make a decent webpage and control your ESP8266 or ESP32 from this page and even put all kind of sensor values on this page. 


In the past I always have used ESPBasic as my favorite devellopment environment. Unfortunately ESPBasic is not further develloped and there is no ESP32 version available. This method of building a webserver makes it easy to replace ESPBasic with the standard Arduino environment for both ESP versions. So it is slowly time to say goodbye to ESPBasic. 

Go ahead and build a project using this. I will give you a complete project in an upcoming story. So stay tuned.

Have fun.

Luc Volders