WeMos D1 R1 built-in LED problem

Got an odd one.

WeMos D1 R1.

The first sketch is a test to determine the best LED flash patterns to allow me to test two different events from across a room (or farther). I used the same includes as the EspNowSlave.ino, and structured the LED code in the same way.

This test works fine. The LED flashes as it should.

#include <ESP8266WiFi.h>
extern "C" {
    #include <espnow.h>
}

byte ledPin = 14;

void setup() {
  pinMode(ledPin, OUTPUT);
  ledOff();
}

void loop() {
  loopLED();
}

void loopLED() {
      ledOn();
      delay (300);
      ledOff();
delay(2000);
      ledOn();
      delay (100);
      ledOff();
      delay (75);
      ledOn();
      delay (100);
      ledOff();
 delay(2000);
}

void ledOn() {
  digitalWrite(ledPin,HIGH);
}
void ledOff() {
  digitalWrite(ledPin,LOW);
}

This sketch is using, as a basis, Robin2's excellent code sample (EspNowSlave.ino) in this thread for an ESP-Now implementation for the ESP8266.

In this one, the LED does not flash when it is called from receiveCallBackFunction(). Other than that, the prgram does what it should, receiving the strings "SIP" and "PUFF". I have two Serial.println() lines to tell me when I am in the 'if' code.

// EspnowSlave.ino
// a minimal program derived from
//          https://github.com/HarringayMakerSpace/ESP-Now
// This is the program that receives the data. (The Slave)
// COM5
//=============

#include <ESP8266WiFi.h>
#include <Arduino.h>
extern "C" {
    #include <espnow.h>
}

// it seems that the mac address needs to be set before setup() is called
//      and the inclusion of user_interface.h facilitates that
//      presumably there is a hidden call to the function initVariant()

/* Set a private Mac Address
 *  http://serverfault.com/questions/40712/what-range-of-mac-addresses-can-i-safely-use-for-my-virtual-machines
 * Note: by setting a specific MAC you can replace this slave ESP8266 device with a new one
 * and the new slave will still pick up the data from controllers which use that MAC
 */
uint8_t mac[] = {0x36, 0x33, 0x33, 0x33, 0x33, 0x33};
//==============

void initVariant() {
  WiFi.mode(WIFI_AP);
  wifi_set_macaddr(SOFTAP_IF, &mac[0]);
}

//==============
// For ESP8266
#define WIFI_CHANNEL 4

// must match the controller struct
struct __attribute__((packed)) DataStruct {
    char text[4];
    unsigned int time;
};
DataStruct myData;
char sip[4] = {'S','I','P','\0x0'};
char puf[4] = {'P','U','F','\0x0'};

// For LED and Relax Actuator
byte ledPin = 14;


//============
void setup() {
    Serial.begin(115200); Serial.println();
    Serial.println("Starting EspnowSlave.ino");

    Serial.print("This node AP mac: "); Serial.println(WiFi.softAPmacAddress());
    Serial.print("This node STA mac: "); Serial.println(WiFi.macAddress());

    if (esp_now_init()!=0) {
        Serial.println("*** ESP_Now init failed");
        while(true) {};
    }
    esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);
    esp_now_register_recv_cb(receiveCallBackFunction);
    Serial.println("End of setup - waiting for messages");
    Serial.print("LED pin: ");
    Serial.print(ledPin);
    pinMode (ledPin, OUTPUT);
    ledOff();
}

//============

void loop() {
}

//============

void receiveCallBackFunction(uint8_t *senderMac, uint8_t *incomingData, uint8_t len) {
    memcpy(&myData, incomingData, sizeof(myData));
    Serial.println();
    Serial.println(myData.text);
    
    if ( memcmp (myData.text, sip,sizeof(myData.text) == 0)) {
      Serial.println("In if SIP");
      ledOn();
      delay (300);
      ledOff();
    }
        
    if ( memcmp (myData.text, puf, sizeof(myData.text))== 0) {
      Serial.println("In if PUF");
      ledOn();
      delay (100);
      ledOff();
      delay (75);
      ledOn();
      delay (100);
      ledOff();
    }
}

void ledOn() {
  digitalWrite(ledPin,HIGH);
}
void ledOff() {
  digitalWrite(ledPin,LOW);
}

Is it because it is in that function? Do I need to write code to run in loop() for it to work?

Most of esp8266 modules I've seen use GPIO 2 for the on board LED not 14.
And all the ones I have use HIGH for off and LOW for on.

I would use LED_BUILTIN for the pin # as that is set by the variant file for all cores/boards to be the proper value to make sure you are using the proper pin # (GPIO bit number for esp8266 cores).

There was an issue in the early esp8266 cores in that they improperly used the symbol name BUILTIN_LED instead of LED_BUILTIN but that has been fixed in more recent esp8266 cores.

--- bill

I had no problem using the LED from anywhere within setup() or loop(). It was only when I tried to use it from within receiveCallBackFunction(). The WeMos actually has two built-in LEDs. One is on the main board and is labelled as ON, and is on pin 14. The other is on the Esp8266 module itself, and is actuated by Pin 2.

Anyway, did some experimenting and decided that the receiveCallBackFunction() routine must be akin to an embassy; foreign territory, and all it can do is manipulate and call certain things. I can do a Serial print, and can read/write global variables, but can't call a function from it.

So it became clear what to do. I set up two global bool variables, gotSIP and gotPUF, and in my receiveCallBackFunction() I check for a valid message and set the bool to true. In loop(), I simply check the status of the bools and immediately control the LED.

The results are wonderful! I can put the receiver anywhere in the house (main floor or basement) and sip or puff from anywhere else, and successfully trigger the LED routine. Next step is to make it trigger an action with a digital pin.

lar3ry:
I had no problem using the LED from anywhere within setup() or loop(). It was only when I tried to use it from within receiveCallBackFunction(). The WeMos actually has two built-in LEDs. One is on the main board and is labelled as ON, and is on pin 14. The other is on the Esp8266 module itself, and is actuated by Pin 2.

Make sure you keep in mind that with some esp8266 boards like the WeMos boards, digitalWrite(), and digitalRead() take GPIO bit numbers not Arduino digital pin numbers.
Other esp8266 boards do not do this mapping.
This can get confusing on the WeMos boards.

The WeMos D1 board has two LEDs, one on GPIO 14 on the main board, and the other on GPIO 2 on the esp module.
Where things get confusing is that on a Wemos D1 board,
GPIO 14 is Arduino digital pin 13 and GPIO 2 is Arduino digital pin 9.

The WeMos variants include Dn symbols to do the mapping.
i.e. the symbol name D13 is arduino digital pin 13 which maps to GPIO bit 14
the symbol name D9 is arduino digital pin 9 and maps to GPIO bit 2

For example, when you use digitalWrite(14, LOW) you are setting GPIO bit 14 low not Arduino digital pin 14.
And when you use digitalWrite(D13, HIGH) you are setting Arduino digital pin 13 high which on the Wemos D1 board maps to GPIO 14.

Likewise, digitalWrite(D9, LOW) turns on the led on ESP module as D9 maps to GPIO 2.

The WeMos boards do this mapping to try to offer Arduino pin # compatibility with boards like the UNO.
So you can use D13 to control the on board LED the same as using 13 on an UNO.

Also on boards like the WeMos D1, LED_BUILTIN maps to the pin that controls the LED on the esp module rather than the LED on the main board. IMO I thik this is not really correct as on an UNO and all the other Arduino boards, LED_BUILTIN is controls the LED on the main board.

So, IMO, all the mapping done in the WeMos variants doesn't buy much since you must still change code that was for an UNO to use D13 instead of 13 if you were using Arduino digital pin 13 directly to control the on board LED.

Anyway, did some experimenting and decided that the receiveCallBackFunction() routine must be akin to an embassy; foreign territory, and all it can do is manipulate and call certain things. I can do a Serial print, and can read/write global variables, but can't call a function from it.

If you are doing Serial.print() you are calling a function which in turn is calling MANY other functions.
Something else is going on.
It might be that delay() does not work in the callback function since the call back may be being called in a thread context that cannot do delays.
If delay() doesn't work and returns instantly, then the LED blinks would be so short that they would not show up.

--- bill

Make sure you keep in mind that with some esp8266 boards like the WeMos boards, digitalWrite(), and digitalRead() take GPIO bit numbers not Arduino digital pin numbers.
Other esp8266 boards do not do this mapping.
This can get confusing on the WeMos boards.

Sure can. I should really find a proper mapping table or make one.

The WeMos D1 board has two LEDs, one on GPIO 14 on the main board, and the other on GPIO 2 on the esp module.

Yeah, figured that out early on. Got the LED on board working for testing, and changed it to the ESP8266 LED to verify it worked.

For example, when you use digitalWrite(14, LOW) you are setting GPIO bit 14 low not Arduino digital pin 14.
And when you use digitalWrite(D13, HIGH) you are setting Arduino digital pin 13 high which on the Wemos D1 board maps to GPIO 14.

Ahh! Now it's starting to make sense to me.

It might be that delay() does not work in the callback function since the call back may be being called in a thread context that cannot do delays.
If delay() doesn't work and returns instantly, then the LED blinks would be so short that they would not show up.

Just did a test, and I think you are right. I commented out one of the LED routines in loop(), and in receiveCallBackFunction(), added a call to a ledOn() routine that just does a digitalWrite, and sure enough, the LED does come on. One interesting thing is that I changed my ledOn() routine to

void ledOn() {
  digitalWrite(ledPin,HIGH);
  delay(500);
  digitalWrite(ledPin,LOW);
}

and I don't see the LED come on, which tells me (I think) that not only does the delay() not work in the callback, but that anything I call from it is likely in the callback thread too.

Thanks for the insight, Bill.

Turns out it's even worse, as far as the two LEDs go.
The one on the board and the one on the ESP8266 are wired opposite. One turns on with a HIGH and one with a LOW.

#include <ESP8266WiFi.h>
extern "C" {
    #include <espnow.h>
}

byte ledPin = D13;
byte led2Pin = D9;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(led2Pin, OUTPUT);
  ledOff();
}

void loop() {
      ledOn();
      delay (300);
      ledOff();
      delay(600);
      led2On();
      delay (300);
      led2Off();
}

void ledOn() {
  digitalWrite(ledPin,HIGH);
}
void ledOff() {
  digitalWrite(ledPin,LOW);
}
void led2On() {
  digitalWrite(led2Pin,LOW);
}
void led2Off() {
  digitalWrite(led2Pin,HIGH);
}

lar3ry:
I should really find a proper mapping table or make one.

Here it is:

static const uint8_t D0   = 3;
static const uint8_t D1   = 1;
static const uint8_t D2   = 16;
static const uint8_t D3   = 5;
static const uint8_t D4   = 4;
static const uint8_t D5   = 14;
static const uint8_t D6   = 12;
static const uint8_t D7   = 13;
static const uint8_t D8   = 0;
static const uint8_t D9   = 2;
static const uint8_t D10  = 15;
static const uint8_t D11  = 13;
static const uint8_t D12  = 12;
static const uint8_t D13  = 14;
static const uint8_t D14  = 4;
static const uint8_t D15  = 5;

lar3ry:
and I don't see the LED come on, which tells me (I think) that not only does the delay() not work in the callback, but that anything I call from it is likely in the callback thread too.

Does the callback run in interrupt context? If so, does delay() work in interrupt context on this processor?

pert:
Here it is:

Thanks! I was about to ask you where I should put it, but I see it's already there in variants/d1.

gfvalvo:
Does the callback run in interrupt context? If so, does delay() work in interrupt context on this processor?

I really don't know. I may try testing that if I get time after delivering this project.

Got another strangeness.
I get an error compiling my ESPNow slave code (Error compiling for board WeMos D1 R1.)
Here's the sketch, followed by a copy of only the last part of the error messages (otherwise post is too long).

// EspnowSlave.ino
// a minimal program derived from
//          https://github.com/HarringayMakerSpace/ESP-Now
// This is the program that receives the data. (The Slave)
// COM5
//=============

#include <ESP8266WiFi.h>
#include <Arduino.h>
extern "C" {
    #include <espnow.h>
}

// it seems that the mac address needs to be set before setup() is called
//      and the inclusion of user_interface.h facilitates that
//      presumably there is a hidden call to the function initVariant()

/* Set a private Mac Address
 *  http://serverfault.com/questions/40712/what-range-of-mac-addresses-can-i-safely-use-for-my-virtual-machines
 * Note: by setting a specific MAC you can replace this slave ESP8266 device with a new one
 * and the new slave will still pick up the data from controllers which use that MAC
 */
uint8_t mac[] = {0x36, 0x33, 0x33, 0x33, 0x33, 0x33};
//==============

void initVariant() {
  WiFi.mode(WIFI_AP);
  wifi_set_macaddr(SOFTAP_IF, &mac[0]);
}

//==============
// For ESP8266
#define WIFI_CHANNEL 4

// must match the controller struct
struct __attribute__((packed)) DataStruct {
    char text[4];
    unsigned int time;
};

DataStruct myData;
char sip[4] = {'S','I','P','\0x0'};
char puf[4] = {'P','U','F','\0x0'};

// For LED, Relax Actuator, and Testing

byte sipPin = D6;
byte pufPin = D7;
byte ledPin = LED_BUILTIN;
bool gotSIP = false;
bool gotPUF = false;
bool cfg = true;

//============
void setup() {
  myData.text[0] = '\0x0';
  Serial.begin(115200); Serial.println();
  Serial.println("Starting EspnowSlave.ino");

  Serial.print("This node AP mac: "); Serial.println(WiFi.softAPmacAddress());
  Serial.print("This node STA mac: "); Serial.println(WiFi.macAddress());

  if (esp_now_init()!=0) {
      Serial.println("*** ESP_Now init failed");
      while(true) {};
  }
  
  esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);
  esp_now_register_recv_cb(receiveCallBackFunction);

  printf("sipPin: %d, pufPin: %d\n",sipPin,pufPin);
  
  Serial.println("End of setup - waiting for messages");
  pinMode(LED_BUILTIN,OUTPUT);
  pinMode(sipPin,OUTPUT);
  digitalWrite(sipPin,HIGH);
  pinMode(pufPin,OUTPUT);
  digitalWrite(pufPin,HIGH);  
  ledOff();
}

//============

void loop() {
  if (cfg) { // for testing and configuration
    if (gotSIP) {
    ledOn();
    delay (500);
    ledOff();
    gotSIP = false;
    Serial.println(millis());
    Serial.println();
    digitalWrite(sipPin,HIGH);
    }
  
    if (gotPUF) {
      ledOn();
      delay (500);
      //ledOff();
      //delay (100);
      ledOn();
      delay (200);
      ledOff();
      gotPUF = false;
      digitalWrite(pufPin,HIGH);
    }
  }
}

//============

void receiveCallBackFunction(uint8_t *senderMac, uint8_t *incomingData, uint8_t len) {
Serial.println(millis());
  memcpy(&myData, incomingData, sizeof(myData));
  Serial.println();
  Serial.println(myData.text);

  if ( myData.text[0] == 'S') {
//    Serial.println("In if SIP");
      gotSIP = true;
      digitalWrite(sipPin,LOW);
  }
  if ( myData.text[0] == 'P') {
//    Serial.print("In if PUF");
    gotPUF = true;
      digitalWrite(pufPin,LOW);
  }
Serial.println(millis());
}

void ledOn() {
  digitalWrite(ledPin,LOW);
}
void ledOff() {
  digitalWrite(ledPin,HIGH);
}
Arduino: 1.8.10 (Windows 7), Board: "WeMos D1 R1, 80 MHz, Flash, Legacy (new can return nullptr), All SSL ciphers (most compatible), 4MB (FS:2MB OTA:~1019KB), v2 Lower Memory, Disabled, SSL, Only Sketch, 921600"

... snippage ...

C:\Users\Admin\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.6.2\libraries\ESP8266WiFi\src\CertStoreBearSSL.cpp:25:31: error: 'DEBUG_ESP_PORT' was not declared in this scope
 #define DEBUG_BSSL(fmt, ...)  DEBUG_ESP_PORT.printf_P((PGM_P)PSTR( "BSSL:" fmt), ## __VA_ARGS__)
                               ^

C:\Users\Admin\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.6.2\libraries\ESP8266WiFi\src\CertStoreBearSSL.cpp:56:5: note: in expansion of macro 'DEBUG_BSSL'
     DEBUG_BSSL("CertStore::_preprocessCert: OOM\n");
     ^

C:\Users\Admin\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.6.2\libraries\ESP8266WiFi\src\CertStoreBearSSL.cpp: In static member function 'static const br_x509_trust_anchor* BearSSL::CertStore::findHashedTA(void*, void*, size_t)':
C:\Users\Admin\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.6.2\libraries\ESP8266WiFi\src\CertStoreBearSSL.cpp:25:31: error: 'DEBUG_ESP_PORT' was not declared in this scope
 #define DEBUG_BSSL(fmt, ...)  DEBUG_ESP_PORT.printf_P((PGM_P)PSTR( "BSSL:" fmt), ## __VA_ARGS__)
                               ^

C:\Users\Admin\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.6.2\libraries\ESP8266WiFi\src\CertStoreBearSSL.cpp:204:9: note: in expansion of macro 'DEBUG_BSSL'
         DEBUG_BSSL("CertStore::findHashedTA: OOM\n");
         ^

Using previously compiled file: C:\Users\Admin\AppData\Local\Temp\arduino_build_346642\libraries\ESP8266WiFi\ESP8266WiFi.cpp.o
Multiple libraries were found for "ESP8266WiFi.h"
 Used: C:\Users\Admin\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.6.2\libraries\ESP8266WiFi
Using library ESP8266WiFi at version 1.0 in folder: C:\Users\Admin\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.6.2\libraries\ESP8266WiFi 
exit status 1
Error compiling for board WeMos D1 R1.

I have searched for ESP8266WiFi.h in:

Program Files (x86)\Arduino\libraries
Sketchbook\libraries
Appdata\Arduino15
Sketchbook (just in case I copied it into there with my code)

and the ONLY place it shows up is in the ESP8266 code in Appdata, so I think it should only be able to find one library of that name.

I have had the error before, I think, and somehow got it going again, but can't remember how.

Anyone have any ideas?

This:

lar3ry:

Multiple libraries were found for "ESP8266WiFi.h"

Used: C:\Users\Admin\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.6.2\libraries\ESP8266WiFi
Using library ESP8266WiFi at version 1.0 in folder: C:\Users\Admin\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.6.2\libraries\ESP8266WiFi

is a red herring. It's not the cause of the compilation error. It's just some helpful information provided by the Arduino IDE. In fact, it's not even helpful in this case because there is a bug in Arduino IDE 1.8.10 that causes this message to be displayed even when there really are no multiple libraries. Please ignore it.

This is the error you need to focus on:

lar3ry:

C:\Users\Admin\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.6.2\libraries\ESP8266WiFi\src\CertStoreBearSSL.cpp:25:31: error: 'DEBUG_ESP_PORT' was not declared in this scope

#define DEBUG_BSSL(fmt, ...)  DEBUG_ESP_PORT.printf_P((PGM_P)PSTR( "BSSL:" fmt), ## VA_ARGS)
                              ^

C:\Users\Admin\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.6.2\libraries\ESP8266WiFi\src\CertStoreBearSSL.cpp:56:5: note: in expansion of macro 'DEBUG_BSSL'
    DEBUG_BSSL("CertStore::_preprocessCert: OOM\n");
    ^

C:\Users\Admin\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.6.2\libraries\ESP8266WiFi\src\CertStoreBearSSL.cpp: In static member function 'static const br_x509_trust_anchor* BearSSL::CertStore::findHashedTA(void*, void*, size_t)':
C:\Users\Admin\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.6.2\libraries\ESP8266WiFi\src\CertStoreBearSSL.cpp:25:31: error: 'DEBUG_ESP_PORT' was not declared in this scope
#define DEBUG_BSSL(fmt, ...)  DEBUG_ESP_PORT.printf_P((PGM_P)PSTR( "BSSL:" fmt), ## VA_ARGS)
                              ^

C:\Users\Admin\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.6.2\libraries\ESP8266WiFi\src\CertStoreBearSSL.cpp:204:9: note: in expansion of macro 'DEBUG_BSSL'
        DEBUG_BSSL("CertStore::findHashedTA: OOM\n");
        ^

The clue is the part where it says "error".

This error is caused by having Tools > Debug Level set to something like "SSL" while you have Tools > Debug port set to "Disabled". To fix it, you need to either set Tools > Debug port to something other than "Disabled", or else set Tools > Debug Level to "None".

Thanks pert! That did the trick.

I spent a lot of time tracing down includes, starting with ESP8266WiFi.h and .cpp. Found DEBUG_ESP_PORT in there but it was inside an #ifdef, and I could not find the #define for the #ifdef.

Don't know if I should start another thread, but I am wondering if I am able to set the power level when using ESP-Now. I tried setOutputPower() in setup(), but I get 'setOutputPower' was not declared in this scope

I found the routine in ESP8266WiFiGeneric.h, which is included from ESP8266WiFi.h

lar3ry:
Thanks pert! That did the trick.

You're welcome. I'm glad it's working now.

lar3ry:
I spent a lot of time tracing down includes, starting with ESP8266WiFi.h and .cpp. Found DEBUG_ESP_PORT in there but it was inside an #ifdef, and I could not find the #define for the #ifdef.

It's a tricky one. It's defined according to the Tools > Debug port menu selection, so it's actually done (or not done) in boards.txt:

d1.menu.dbg.Disabled=Disabled
d1.menu.dbg.Disabled.build.debug_port=
d1.menu.dbg.Serial=Serial
d1.menu.dbg.Serial.build.debug_port=-DDEBUG_ESP_PORT=Serial
d1.menu.dbg.Serial1=Serial1
d1.menu.dbg.Serial1.build.debug_port=-DDEBUG_ESP_PORT=Serial1

That is passed to the compiler as the {build.debug_port} property in the platform.txt compilation recipes:

Ideally, the ESP8266 developers would have done something like this:

#if !defined(DEBUG_ESP_PORT) && (defined(DEBUG_ESP_SSL) || defined(DEBUG_ESP_TLS_MEM) || defined(DEBUG_ESP_HTTP_CLIENT) || defined(DEBUG_ESP_HTTP_SERVER))
#error You must select a port from the Tools > Debug port when Tools > Debug level is set to anything above "None"
#endif

Then you would get a nice helpful error instead of a confusing one.

lar3ry:
I tried setOutputPower() in setup(), but I get 'setOutputPower' was not declared in this scope

I just tried this minimal sketch and I don't get any error:

#include <ESP8266WiFi.h>
void setup() {
  WiFi.setOutputPower(42);
}
void loop() {}

Does that work for you?

pert:
Ideally, the ESP8266 developers would have done something like this:
--- snip---.
Then you would get a nice helpful error instead of a confusing one.

True enough, but all in all, they have done a great job on this whole system. I did a LOT of Atmel AVR programming a decade or so ago, on the 8515 and 2313, all in assembler, and the Arduino is a positively great advancement.

I just tried this minimal sketch and I don't get any error:

#include <ESP8266WiFi.h>

void setup() {
 WiFi.setOutputPower(42);
}
void loop() {}



Does that work for you?

Yes it does! I did not have the WiFi. part in there. I really should have noticed the WiFi.mode() and WiFi.disconnect further up the sketch. I think what fooled me was the lack of an xxx.begin().

I'm happy to be able to lower the power. This will be used only in one room, and my test yesterday showed I could get reliable transmissions out to about 70 meters with the WeMos in a 3D printed case, and through a double-paned window. I didn't try farther than that.

Anyway, Thanks again!

You're welcome. I'm glad to hear it's working now. Enjoy!
Per