Thursday 16 May 2019

PWMing the ESP8266 - Programming: Part 3 of 3

L293D motor shield for NodeMCU ESP-12E. Note green 5V jumper supplying test power to L293D IC. Click on the picture for youtube video. 
In Part 2 I built a DC motor speed controller cum LED light dimmer with the parts I had on hand, mainly so I did not have to wait to test my software. In due course the part arrived: the L293 motor control shield for the ESP8266 NodeMCU ESP-12E development board. It cost me only RM10 (about USD2). The build quality was a little rough; I had some difficulty mounting the ESP-12E into the socket, but it worked as advertised.

L293D motor control shield


Tony Kambourakis has an excellent writeup on the L293D, so please head over there for details.

While testing the program I set the 5V USB power to drive the L293D by shorting VM to VIN, and connecting VIN to my Android smartphone charger. Try not to use the laptop/desktop USB port- it minimizes the damage should things go wrong. Be sure to remove the Vin-VM short when you progress to 12-36VDC. VIN still needs to be at 5VDC.

I only had to make a slight change to my program: the ULN2003 used different pins for PWM and added 2 GPIO pins for motor direction control. We do not really need to change the fan rotation direction, plus reversing power to LED lights may cause them not to turn on, so the additional GPIO pins are used to lock in the output pin assignments on the shield.

The http interface allows for easy webhooking into Google Home/Google Assistant for Voice Control.

#include <ESP8266WiFi.h>

const char* ssid = "YourAccessPoint";    
const char* password = "YourPassword";
IPAddress staticIP(123,45,67,89); // Change IP to suit your AP
IPAddress gateway(123,45,67,1);  
IPAddress subnet(255,255,255,0);
WiFiServer server (8080);
uint8_t PWMpin1 = D1;
uint8_t PWMpin2 = D2;
uint8_t Dirpin1 = D3;
uint8_t Dirpin2 = D4;

void setup ()
{
  delay (10);
  Serial.begin (115200);
  analogWrite(PWMpin1, 0);  /* set initial duty cycle */
  analogWrite(PWMpin2, 0);  /* set initial duty cycle */
  pinMode(Dirpin1, OUTPUT);
  pinMode(Dirpin2, OUTPUT);
  digitalWrite(Dirpin1, 1); // 2019-05-15 set output polarities to conform to shield
  digitalWrite(Dirpin2, 1);
  analogWriteFreq(10);    /* default is 1KHz range is 1Hz-1000kHz */

  // Connect to WiFi network
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.mode(WIFI_OFF); // wifi reconnect workaround
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  WiFi.config(staticIP, gateway, subnet); // Static IP. Not required for dhcp
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("Static IP address: ");
  Serial.println(WiFi.localIP());

  // Start the server
  server.begin();
  delay(50);
}

static int dots = 0;
static int val = 0; // 2018-10-25
static int val2 = 0; // 2019-04-19
static int commas = 0;
static unsigned long timeNow = 0; // 2019-03-17
static unsigned long timeLast = 0;
static int seconds = 0;

uint16_t dutycycle = 0; /* Set PWM duty cycle */

void loop() {

  // 2019-03-17
  timeNow = millis()/1000; // the number of milliseconds that have passed since boot
  if (timeLast==0 || timeNow<timeLast)
    timeLast = timeNow; // Start with time since bootup. Note millis() number overflows after 50 days
  seconds = timeNow - timeLast;//the number of seconds that have passed since the last time 60 seconds was reached.

  // 2019-02-24 Check for broken wifi links
  if (WiFi.status() != WL_CONNECTED) {
    delay(10);
    // Connect to WiFi network
    Serial.println();
    delay(10);
    Serial.print("Reconnecting WiFi ");
    delay(10);
    WiFi.mode(WIFI_OFF); // 2018-10-09 workaround
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    WiFi.config(staticIP, gateway, subnet); // Static IP. Not required for dhcp

    while (WiFi.status() != WL_CONNECTED) {
      Serial.print("x");
      delay(500);
      dots++;
      if (dots >= 1000)
      {
        dots = 0;
        Serial.println("Giving up! Waiting for WDT reset ...");
        delay(20);
        while (1)
        {}
      }
    }
    Serial.println("");
    delay(10);
    Serial.println("WiFi reconnected");

    // Start the server
    server.begin();
    Serial.println("Server restarted");
    delay(10);
    // Print the IP address
    Serial.print("IP address: ");
    delay(10);
    Serial.println(WiFi.localIP());
    delay(10);
  }

  // Check if a client has connected
  WiFiClient client = server.available();
  if ( ! client ) {
    return;
  }

  //Wait until the client sends some data
  while ( ! client.available () )
  {
    delay (10); // 2019-02-28 reduced from 100
    Serial.print(".");
    if (commas++ > 5) {
      commas = 0;
      Serial.println("Terminating client connecting without reading");
      delay(20);
      client.stop();
      return;
    }
  }

  client.setTimeout(3000); // 2019-03-19
  Serial.println("new client connect, waiting for data ");

  // Read the first line of the request
  String req = client.readStringUntil ('\r');
  client.flush ();

  // Match the request
  String s = "";
  // Prepare the response
  if (req.indexOf ("/pwm1") != -1) // first channel
  {
    Serial.println("Command for PWM1 received");  

    int value_index = req.indexOf("/pwm1/");// Here you get the index to split the response string where it says "pwm1"
    String value_string = req.substring(value_index);
    String value = value_string.substring(6, value_string.indexOf("\r"));// Skip first 6 characters, get value
    Serial.println(value);
 
    s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\nValue entered ";
    s += value;
    s += "\n\r";
    val = value.toInt();
 
    if (val < 0) // 2019-04-19 value must be 0-100%
      val = 0;
    else if (val > 100)
      val = 100;
    s += "Duty cycle (percent) used is: ";  
    s += String(val);
    s += "\n\r</html>\n";
    client.print (s);
 
    Serial.print("Duty cycle (percent) used is: ");
    Serial.println(val);

    dutycycle = (val * 1023) / 100; // Convert to 16-bit unsigned
    analogWrite(PWMpin1, dutycycle);
 
  } else if (req.indexOf ("/pwm2") != -1) {
      Serial.println("PWM2 command received");  

      int value_index = req.indexOf("/pwm2/");// Here you get the index to split the response string where it says "pwm1"
      String value_string = req.substring(value_index);
      String value = value_string.substring(6, value_string.indexOf("\r"));// Skip first 6 characters, get value
      Serial.println(value);

      s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\nValue entered ";
      s += value;
      s += "\n\r";
      val2 = value.toInt();
 
      if (val2 < 0) // 2019-04-19 value must be 0-100%
        val2 = 0;
      else if (val2 > 100)
        val2 = 100;
      s += "Duty cycle (percent) used is: ";  
      s += String(val2);
      s += "\n\r</html>\n";
      client.print (s);
 
      Serial.print("Duty cycle (percent) used is: ");
      Serial.println(val2);

      dutycycle = (val2 * 1023) / 100; // Convert to 16-bit unsigned
      analogWrite(PWMpin2, dutycycle);
  } else if (req.indexOf("/index.html") != -1) {
      s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\nStatus: PWM1 ";
      s += String(val);
      s += "% PWM2 ";
      s += String(val2);
      s += "%\r\n</html>\n";
      client.print (s);
      Serial.println("Status command received");
  }  
  else {
    Serial.println("invalid request");
    s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\nInvalid request</html>\n";
    client.print (s);
  }

  delay (10);
  client.stop(); // 2019-02-24
  Serial.println("Client disonnected");
  delay(10);
}

And that was it- it worked first time. Having said that do test your system is stages: at first with no power to the L293D while you worked out the bugs in the program, and then with low (5V current-limited) power from the USB power supply. You can check the motor output pins with a voltmeter for polarity before connecting up the fan and LED lamp. 

I also use a 60W 230Vac mains isolation transformer for safety - it limits mains power in case of catastrophic failures. 

My test setup: clockwise from top left: isolation transformer, LED lamp, phone charger as 5V supply, ancient 12VDC power adapter, smartphone with chrome browser, ESP-12E with L293D motor shield, and 12VDC fan. Click on picture for youtube video.

On the smartphone browser, I used:
http://123.45.67.89:8080/pwm1/100

On my Linux desktop I used 
lynx -dump -read_timeout=5 -cmd_script=./lynx_script http://123.45.67.89:8080/pwm2/0

Make sure the phone or desktop is logged into the same WiFi access point. Notice the IP address here does not really work- they are not even Class C (ie your AP will not use them) so substitute your own.

There you have it: IoT PWM DC fan and LED lamp controller, all set for Google Home Voice Control. It cost me only RM30 (about USD8) per unit. Making electronics stuff can be cheap, fun and fast.

Happy Trails.

No comments:

Post a Comment