Showing posts with label Pulse-Width Modulation (PWM). Show all posts
Showing posts with label Pulse-Width Modulation (PWM). Show all posts

Tuesday, 28 May 2019

Pan/Tilt Head Using the NodeMCU L293D shield and TowerPro MG-996R Servomotor

Left: Dual TowerPro MG-996R, right: L293D NodeMCU ESp-12E Shield Click on picture for youtube video
I had always wanted a pan/tilt head. There is some deterrence value to the baleful gaze of a pan/tilt camera following your every move. While pan/tilt cameras are cheap and easily available, they are less useful at night: their range is a lot shorter.  It would be nice to move an LED torchlight together with the camera.

You could tape an LED torch onto your favorite IP camera, but the mechanism is often made of  plastic and the extra weight wears it out fast. A homebrew pan/tilt head would be nice ...

The L293D NodeMCU ESP-12E motor shield was such an easy build (see part 1, part 2, and part 3) I quickly bought two Towerpro MG-996R servomotors for just RM22.90 (about USD5) each and matching mounting brackets for RM10.30.

Towerpro MG-996R Servomotor

And matching accessories

The brackets were listed in lelong.com as:

"Arduino Multi Purpose Servo Motor Holder Bracket MG995 MG996"


Arduino Multi Purpose Servo Motor Holder Bracket MG995 MG996

I found the mechanical assembly work quite mystifying, but Starmaxtrek's youtube video was a godsend:

Click on picture for Youtube video on mechanical assembly
Documentation for the MG-996R took a little digging, but you can find a copy here.

For testing, I shorted VM to VCC on the L293D and powered everything from VCC using a USB smartphone charger.  The only change in the ESP-12E program was to change the PWM frequency to 50Hz:
  analogWriteFreq(50);

For my setup I found the servomotors reacted only to PWM values between 1-25%. The shaft rotated some 120 degrees and the pan/tilt head whirred about with a gentle menace.

The youtube video is here.

Life is good - Happy Trails.

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.

Wednesday, 24 April 2019

PWMing the ESP8266 Part 2 of 3: Building the Board

ESP-12E ESP8266 NodeMCU with ULN2003 Darlington IC provides PWM for 12Vdc devices like LED Lantern and DC Fan
Link to youtube video.

In Part 1, we saw that it was fast and easy to test Pulse Width Modulation (PWM) using an ESP8266-based NodeMCU ESP-12E development kit. To control a real device we increase the power rating of the ESP-12E's output pins D5 and D6 from 3.3V to 12V using darlington transistors.

Since we are all about fast and cheap prototyping, a handy darlington is the ULN2003. It is actually 7 darlingtons in a 16-pin package but is rated 50Vdc at 500mA and is cheaper than many single (ie discrete) darlington transistors.

ULN2003 is actually 7 Darlington Transistors
We will need a blank solder-able prototyping PCB - veroboard, stripboard, breadboard. Don't be afraid to try a little soldering- all you need is practice. Bob Pease, the legendary electronics guru, used to say, "My favorite programming language is solder".

Prototyping PCB
Bob "My favorite programming language is solder" Pease

Notice I have used an IC socket for the ULN2003- when dealing with motors and high voltages, mishaps are to be expected; it may need to be changed in a hurry. Notice the socket is only a 6-pin DIP originally meant for optical isolator ICs. This means I am only using 2 out of the 7 darlingtons. Feel free to cut off the dangling IC legs or even better simply turn them up out of harm's way. The extra metal helps to dissipate heat from the darlingtons.

Live bugs: ULN2003 and ESP-12E assembled on prototype board
Same board, solder-side. 

Also I only soldered 3 pins of the ESP-12E board, plus an extra 3 pins at each corner for mechanical strength. This makes it easy to de-solder the ESP-12E for other projects.

There are 3 pairs of wires: one for 12V DC power supply, and one each for each of the two PWM outputs. The idea is to tie one end of the load (fan, LED, etc) to 12V and the other to the darlington output so that the ESP8266 and turn it off and on at will.

I really should have used different colored wires for each pair but I did not have enough wires of the correct gauge, so had to make do with a little duct tape and marker pen.

PWM output wires are 12V (red) and darlington collector (black)
 For a long and happy life for the ULN2003 while driving inductive loads like DC motors, I have also connected a 12V 1W zener diode, forward-biased from 12V to pin 9. It is a little counter-intuitive but you are strongly encouraged to read Douglas W Jones' venerable work on stepper motor drivers, using among others the ULN2003. 

Douglas W Jones: his kung-fu is strong ...

To minimize damage from wiring mistakes and the like, first check the unpowered, unconnected board for short-circuits, especially between 3.3V and GND, 5V and GND and 12V and GND. Next check for ESP-12E and ULN2003 shorts to 3.3V, GND and 12V. 

When testing, it is advisable to first not to connect to 12V power and just test with a power bank. Then test with 12V on. If it works well enough test with a powered USB hub rather than directly from your laptop/desktop. If you must test with a desktop try using a USB port from a PCI daughter card rather than from the mainboard. This is to avoid damaging expensive kit like laptops, desktops and servers.

There you have it, a PWM board you can build in half a day. It can control DC devices up to 50V at 500mA like CPU cooling fans, LED lights, and alarm sirens. 

Happy trails.

Friday, 19 April 2019

PWMing the ESP8266 Part 1 of 3

Pulse Width Modulation can be used to dim LED lights and set DC motor speed
Link to youtube video.

Some time ago, the dual fans on my laptop stand froze solid. I had been re-purposing old laptops as IoT servers and running them 24/7 wore out the internal CPU fans, so I ventilated the bottom with a dual-fan laptop stand.

Laptop stand with dual slow fans
That lasted only a few months. Fans have a surprisingly low temperature rating, often lower than that of a CPU. A failing fan will slow down causing the CPU temperature to rise, which further shortens fan life. So the trick is not to overheat the fan (the CPU is usually tougher).  I would rather replace the fans in the stand - it is much harder to replace the one in the laptop. So, I replaced the weedy 5V USB fans and shoehorned in two 12V 80mm x 800mm power supply ball-bearing fans.

Take this, hot stuff
This sorted the immediate problem: the laptop runs a lot cooler but it is also a lot noisier. If I left the study door open the wife would think the kettle has been left on. And, the increased vibration cannot be good for the laptop.

Pulse-Width Modulation can be used to control fan speed by turning off its power some of the time. And making an IoT PWM device would be a nice project.

Using the Linux command sensors I can even detect the CPU temperature and speed up the fan accordingly with a bash shell:

$sensors
coretemp-isa-0000
Adapter: ISA adapter
Package id 0:  +63.0°C  (high = +100.0°C, crit = +100.0°C)
Core 0:        +63.0°C  (high = +100.0°C, crit = +100.0°C)
Core 1:        +60.0°C  (high = +100.0°C, crit = +100.0°C)

nouveau-pci-0100
Adapter: PCI adapter
GPU core:     +0.60 V  (min =  +0.60 V, max =  +1.20 V)

With DevOps, continuous improvement is the thing, so there is no need to have an all-singing all-dancing system done in one go. Instead we aim always for a working set of hardware and software, and to make sure little can go wrong, we implement the barest minimum, ie we start off with the smallest set of features we can.This means just the PWM.

ElectronicWings has a good writeup, and I will not replicate it here. There is minimal hardware (wiring) involved. The PWM output is indicated by the brightness of the LED.

Look, Ma, no soldering: Eazyhooks and PVC terminals are used to connect an LED and 330R resistor to the ESP-12E's D6 pin


The program is equally simple:

uint8_t LEDpin = D6; /* By default PWM frequency is 1000Hz and we are using same for this application hence no need to set */ void setup(){ Serial.begin(9600); analogWrite(LEDpin, 512); /* set initial 50% duty cycle */ } void loop(){ uint16_t dutycycle = analogRead(A0); /* read continuous POT and set PWM duty cycle according */ if(dutycycle > 1023) dutycycle = 1023;/* limit dutycycle to 1023 if POT read cross it */ Serial.print("Duty Cycle: "); Serial.println(dutycycle); analogWrite(LEDpin, dutycycle); delay(100); }

Instead of obtaining dutycycle from analogRead() I simply hardcoded a value between 0 and 1023 and reprogrammed and re-ran the ESP-12E a few times. This meant I did not need to wire up the trimpot. And since the difference in brightness can be a little hard to distinguish between reprograms, I modified it slightly so it changed dutycycle every 1.5s:

uint8_t LEDpin = D6;

/*
 *
   By default PWM frequency is 1000Hz and we are using same
   for this application hence no need to set
 
   https://www.electronicwings.com/nodemcu/nodemcu-pwm-with-arduino-ide
 */

void setup(){
  Serial.begin(115200);
  analogWrite(LEDpin, 1);  /* set initial duty cycle */
  analogWriteFreq(10);    /* default is 1KHz range is 1Hz-1000kHz */
}

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

void loop(){
  if(dutycycle > 1023) dutycycle = 1023;/* limit dutycycle to 1023 if POT read cross it */
  Serial.print("Duty Cycle: ");  Serial.println(dutycycle);
  analogWrite(LEDpin, dutycycle);
  delay(10000);
  if (dutycycle < 1023)
    dutycycle += 100;
  else
    dutycycle = 0;
}

And there you have it, PWM on the ESP8266-based NodeMCU ESP-12E board. Took hardly an hour. In Part 2, we will connect it to the cooling fans and in Part 3 set it up to webhook into Google Assistant.

Happy Trails.