Thursday, November 16, 2017

ESP-8266 and Solving WDT Resets

So without adding much more complexity than WiFi to an ESP8266 sketch, you might get some WDT errors; if you have a serial link you can see the restart message, if not you might just notice a hang followed by a restart. Or not; if the hang is short enough but at the wrong (right?) time, you'll just get a restart.

Long story short: Writing software for the ESP8266 isn't like writing a blink sketch for an Arduino. But it's also not much different than writing a WiFi sketch for an Arduino, either. I think the primary difference is the libraries and Espressif SDK underpinning the ESP are a bit more complex, and it includes a hardware / software watchdog. If the watchdog timer expires, the system assumes that something hung, and restarts to recover.

There are some voodoo-type references to peppering your code with delay() and yield() statements to solve this. What I've done is wrap them in a simple function that manages  when to execute them.

There is one global (counter) and  you can call WDTPulse() whenever you might have a long running routine that won't quickly exit. Inside there is also a call to ESP.wdtFeed(), this requires no special #includes or other declarations as it's declared extern . It really does seem to feed the watchdog.

long counter;

// ******************************** wdt pulse ********************************
void WDTPulse() {
  if ( (counter % 100000) == 0 ) {
    ESP.wdtFeed();
    yield();
    delay(1); 
  }
  counter++;
}

// Example of use; within the possibly-never-ending WiFi setup:
while (stat != WL_CONNECTED) {
  WiFi.begin(ssid, pass);
  WiFi.waitForConnectResult();
  stat = WiFi.status();
  WDTPulse();
}


Or the as yet not totally tested version, which might be better....

// ******************************** wdt pulse ********************************
void WDTPulse() {
  if ( (counter % 100000) == 0 ) {
    ESP.wdtFeed();
    delay(1); 
  }
  yield();
  counter++;
}

Wednesday, November 15, 2017

ESP-8266 - Using UDP as a replacement for serial port debugging messages

The ESP-01 isn't exactly bristling with I/O pins, and running out of pins is a given. Assigning an entire pin for serial debugging output seems silly, so here is a thought; just send the strings via UDP to your workstation instead.

Below are two ways to do this.

In the first instance I'm using a String class which can copy itself to a global buffer, which is fine for quick-n-dirty messages, but as you'll see it is cumbersome for including variables.

It's just as easy to skip the String parameter and use sprintf to pre-fill the global char buffer. You get variable formatting with sprintf for free, too. Either way works and has a place. Unless you *really* hate the String class (some folks do).

// UDP_Test - Noel Dodd 2017
// One day I'll put this on git but for now, enjoy.


#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#define UDP_BUFFER_SIZE 256
#define DEBUG_PORT 10101
WiFiUDP UDP;
char udpBuffer[UDP_BUFFER_SIZE];
IPAddress logIP ( 172, 16, 25, 1 ); // Hardcoded destination IP address

uint8_t counter;

// ************ setup ****************
void setup() {
  // init
  counter = 0;

  // Do WiFi setup; hardcoded here for brevity
  IPAddress ip = IPAddress(172,16,25,100);
  IPAddress gw = IPAddress(172,16,25,1);
  IPAddress subnet = IPAddress(255,255,255,0);
  IPAddress dns = IPAddress(172,168,25,1);

  WiFi.persistent(false);
  WiFi.setAutoConnect(true);
  WiFi.mode(WIFI_STA);
  WiFi.config(ip,gw,subnet,dns); 
  
  int stat = WiFi.status();
  
  while (stat != WL_CONNECTED) {
    WiFi.begin("ssid", "pass");    // replace with real ssid, pass
    WiFi.waitForConnectResult();
    stat = WiFi.status();

    // optionally blink an LED here
    delay(50);
  }
  
  // Log that we are done, String method
  UDPSend( "Setup complete..." );  
}

// ************ Loop ****************
void loop() {
   // Log something (messy way, with Strings)
   String val = String(counter,DEC);
   String msg = String("String counter = " + val );
   UDPSend( msg );

   // Log something else (tidy, with sprintf)
   sprintf( udpBuffer, "Variable counter = %i", counter );
   UDPBufSend ( );

   // Do some work...
   delay(500);

   // Inc counter to see it work; let it wrap over 255
   counter++;
}

// ************ UDPSend ****************
void UDPSend ( String msg ) {

  // copy msg into buffer
  msg.toCharArray( udpBuffer, UDP_BUFFER_SIZE );
  
  UDP.beginPacket( logIP, DEBUG_PORT );
  UDP.write( udpBuffer, UDP_BUFFER_SIZE );
  UDP.endPacket();
  
}


// ************ UDPBufSend ****************
void UDPBufSend ( ) {

  UDP.beginPacket( logIP, DEBUG_PORT );
  UDP.write( udpBuffer, UDP_BUFFER_SIZE );
  UDP.endPacket();
  
}

Monday, November 13, 2017

Very quick note on eBay PIR HC-SR501 sensors and the ESP-8266

This is regarding the HC-SR501 sensor.

1. They work just fine.

2. Supply with +5V, and the signal output is +3.3V, so safe for the ESP-8266. I've measured mine as +3.33v output when supplied +5.04v from USB.

I can cite no written source for #2. "I read it somewhere on teh interwebz" so I tested it, and it appears safe.

I've also read that Espressif might have let the cat out of the bag on a forum post that the GPIO's are +5V tolerant.

Recovering from mixed EEPROM and WiFi 'boot loop of death' on ESP-8266's

Short answer: Keep simplifying code until you get some serial output before the WDT triggers. Then move on and recover. If you still get WDT stack dumps, then check your power supply, supply capacitors (470uF + 0.1 uF), and run a delay(1) to yield processing once in a while. (the last one is questionable, because a delay(1000) in setup used to kill WiFi...).

Longer answer: I recovered a stuck ESP-01 that seemed to boot, but not join the WiFi network AFTER I enabled EEPROM code. I've also heard the loading a 'blank' hex/bin file might be advised, as well as reloading the original firmware to rebuild the hidden sectors of the EEPROM. Use CheckFlashConfig.ino to ensure your IDE config is good.

Longest answer: Possibly related steps:

  • Changed back / forward between 2.3.0 and 2.2.0 several times to wipe the EEPROM areas, as the WiFi library apparently writes out SSID and PASS to private sectors of SPIFFS. Not sure but since the Flash is an external chip I think this means they aren't protected from external reading with an SPI bus master hack.
  • Simplified to the point of being silly, then expanded code.
  • Enabled debugging in the IDE and in code with Serial.setDebugOutput(true);
  • Enabled debug printing during WiFi setup with WiFi.printDiag(Serial);
The code; this is probably the most reasonable (?) mix of recommendations from the ESP8266 community forums. In any event it works, and might prevent early flash wear death.


// Debug
  Serial.print("State set, starting network; status = ");

  WiFi.persistent(false);
  WiFi.setAutoConnect(true);
  WiFi.mode(WIFI_STA);
  WiFi.config(ip,gw,subnet,dns); 

  int stat = WiFi.status();
  Serial.println( stat );

  // Possibly superfluous step - see below:
  if ( stat != WL_CONNECTED ) {
    WiFi.begin(ssid, pass);
  }
  
  // Debug
  WiFi.printDiag(Serial);
  
  Serial.println("Wait for network status...");
  
  while (stat != WL_CONNECTED) {
    WiFi.begin(ssid, pass);
    WiFi.waitForConnectResult();
    stat = WiFi.status();
    Serial.print("  S:");
    Serial.print(stat);
    blink(cRed , 50); 
  }

   

Neopixel / WS2812 Notes with the ESP-01

Reminders for using WS281x's with the ESP-8266:
  1. Library: NeoPixelBus and wire them to the RX pin. 
  2. This lib works with Arduino IDE + ESP Board version 2.3.0
  3. The pinout for the 5mm single pixels is here. They use Rgb instead of Grb!!!
    • NeoPixelBus<NeoRgbFeature, NeoWs2813Method> strip(PixelCount,NEO_PIN);
  4. A large differential between output signal and Vcc won't work for the 5mm ones. It works ok for the standalone 5050 style, like the Sparkfun breakouts, but not the 5mm version. You must use a Vcc close to logic high on the ESP-01's, like 3.3V. They are dimmer, but they work.
  5. Some logic level shifters don't work.



Saturday, November 11, 2017

ESP-8266, EEPROM, and config registers

I'm not 100% sold on the idea that using byte-wise simulated EEPROM on the ESP - as opposed to some other SPIFFS system - is the 'best' way. But I have a lot of existing code that wouldn't need much modification, so EEPROM it is.

Differences from AVR / Arduino:


  1. It's not EEPROM, but actually Flash backed RAM under the hood.
  2. The maximum size defined by SPI_FLASH_SEC_SIZE = 4096, so that's likely the max EEPROM size.
  3. Call EEPROM.begin(size) before using.
  4. Call EEPROM.commit() to actually write it.
  5. EEPROM.read(addr) and EEPROM.write(addr, value) are RAM based, and fast.
Other Notes
  1. For my own libraries there isn't much need to duplicate the RAM storage of things like config registers in an additional uint8_t reg[size] array, but for 32 bytes it's not catastrophic.
  2. Stick to the standard of:
    1. EEPROM_init() writes hard-coded values into EEPROM and commits them.
    2. EEPROM_close() just calls EEPROM.end() - for now.
    3. EEPROM_save(len) changes the len byte and commit() writes.
    4. EEPROM_load() does an initial read at the default length (i.e. 32 bytes) and if the len is different, then perform an EEPROM.end() and EEPROM.begin(len) to adjust.
  3. Config reg array layout is:
    1. Byte 0: Version [0..255]
    2. Byte 1: Length in bytes
    3. Bytes [2..Length] are EEPROM written
    4. Bytes above Length, i.e. [32..63], are dynamic variables and used for RAM management on space constrained systems; not written to EEPROM.

Friday, November 10, 2017

"Just put a pulldown in there somewhere"

If you are new to electronics you might here a phrase like "Just add a pulldown to that". Or "Cap the crap outta that.". In Arduino-land you might also hear "I2C needs pullups on the SDA and SCL lines".

And the advice from the wizards will be something like "Meh, just throw some components at it.". At least that is how it will sound at first. So many options in the datasheets; so little actual guidance.

I came across an example with my 'on the side' project today. A floating input pin on the ESP 8266 wasn't always reading as digital high or low. No real surprise, actually; that's what floating pins do. So I thought I'd write this one up for reference, since the component values are general enough to be useful.

First off, realize that if something is going to be sent a signal as digital high (ie digitalRead(pin) == HIGH) then to give it stability you're going to have to pull it down to near ground; at least below where the voltage will register as digital LOW. The reverse holds true also; if something is going to get a signal as 0V reference, it probably needs to be pulled up to Vcc or whatever digital HIGH is. I2C is like this, and Nick Gammon has some good advice and examples of why resistor values for pullups aren't set in stone.

My example is the first type; I'm switching +Vcc but otherwise I have to keep the pin pulled to ground to stop spurious readings. The other bit of magic is to select minimal values that prevent excess current flow that either drain the power supply or damage the digital pins input circuits.

Now if you sketch this out you'll realize it looks like a voltage divider... so now it's just a matter of calculating the top and bottom values according to how badly it could misconfigured in software. Imagine that you plug in an ESP-01 which has a pin set HIGH and the circuit grounds that pin directly. Or +3.3V is applied directly to a pin with no current limiting resistor. Ouch.

The only real critical value is that I'd like to maintain is the output above about +2.6V, which is what the ESP-8266 uses for digital HIGH. The input is +3.3V, so the rest is simple:


I still need to deploy this in the real world for testing; the wiring for this circuit involves many meters of wire between the ESP and the two switches which might alter the effective value of R1. But as a start it's ok. In terms of ratios I could also go for 3.3k and 15k; and in fact I might if the value of R1 turns out to be closer to 4k or 5k; I can use a 10k fixed value plus the middle of a 10k trimmer pot to dial in an optimal ratio around 15k.

Thursday, November 9, 2017

Pseudo Analog Input on a Digital Pin

I was using a simple pair of resistors to form a voltage divider to feed bright/dark info from the LED of a USB NiteLite to an ESP-01, and in trying to tweak the design a bit I came across an idea on the Adafruit site: Using a Photocell I'd forgotten about. I've used this before on an ESP-12 project, so it's perfect for those times you don't have a free analog input pin. The example below is wired to the TX pin (pin 1) of an ESP-01. If this is going to be exposed to bright light an additional resistor before the input pin to limit current flow might be a good idea.


The idea is that by changing the pin state to cause the capacitor to alternately drain or store a charge, and timing the rise to logic-high while it's charging, you can get an idea of the amount light falling on an LDR (light dependent resistor, or CdS photocell).

A graph from my 'scope shows what a 10k photocell and a 0.1uF capacitor looks like in moderate light:



The code below does this. Not shown is a delay(1)  to space things out for the scope image which isn't needed for real use. It returns the counts (not milliseconds) from initialization to logic-high.


int getLDR () {
  // Set and then read an LDR

  int counter = 0;

  // Discharge to initialize
  pinMode(AMB_PIN,OUTPUT);  
  digitalWrite(AMB_PIN,LOW);          

  // Block the pin by setting it to input
  pinMode(AMB_PIN,INPUT);
  while ( digitalRead( AMB_PIN ) == LOW ) {
    counter++;

    if ( counter > 32000 ) {
      break;
    }
  }

  // done
  //Serial.println(counter);
  return counter;
  
}


Sunday, November 5, 2017

Status - 5 Nov 2017

Ok, some progress on a secondary project with the ESP8266 built into a dollar store nightlight / USB wall socket thing:

  • Arduino environment - works. This worked last year for the interactive TV backlight / notification project, so no surprise. Upgraded to 1.8.5; seems ok; nice serial monitor behaviours.
  • NeoPixels - works via NeoPixelBus on the ESP-01 (I think I used the Adafruit library on the ESP-12). Configuration:
    • Use NeoWs2813Method
    • Use 3.3v instead of 5v for powering 5mm WS2812(?)'s. Level shifter no help (wrong type?
    • Very stable - no glitching, wrong colors, blinkies, etc
  • SPIFFS - works. I use it to hold web pages/objects to serve via wifi; use CheckFlashConfig to validate the IDE has the right flash mem size settings. Must close serial window to upload(?)
  • ESP8266WebServer library - works. Can serve content stored in SPIFFS (it's not SPIFFS aware, but that's trivial).
  • Suspect that there is a ~1 second setup() bug(?) so get thru wifi join ASAP in setup.
  • HTTP POST variables - can exchange named key/value pairs with a web client via POST.


Next up:

  • Check the output of the night-light LED via the voltage divider; should be ok to trigger GPIO0.
  • Add code to check (udp?) a service to fetch time & status as provided by a hub of some kind:
    • 16 byte payload (also fits in payload of nRF devices)
      • 6 bytes; date (3) + time (3)
      • 8 bytes; status values of 7 classes + 1 aggregate status - intended to feed an 8 neopixel stick
      • 2 bytes; flags? reserved?
    • Loose protocol; use udp to send request; always listen for reply; no tcp timeouts
  • Use fetched time:
    • Dim display if dark or p.m.
    • Provide ambient light in the a.m.
    • Timestamp for logging, etc.