Market

A Quick Guide to ESP8266 NodeMCU (Based on Arduino IDE) – Including Example Codes

The ESP8266 NodeMCU is a chip developed by Espress if.

Generally, a development board that looks like the one below, equipped with the ESP8266 chip, is called an ESP8266 NodeMCU.

Although there may be slight variations, any chip labeled ESP8266 is fine. Because anyone can make their own ESP8266 NodeMCU board with an ESP8266 chip.

The ESP8266 integrates WiFi internally, so it can function as a WiFi module (I’ve done it, collecting data with STM32, then transmitting it to ESP8266 via serial communication, and then uploading the data to the server with ESP8266), or as a standalone microcontroller.

Let’s now start writing a program for ESP8266 NodeMCU using Arduino IDE. (Some basic knowledge of microcontrollers is required, otherwise some terms may be confusing.)

Arduino IDE Environment Setup

To write programs for ESP8266 NodeMCU using Arduino IDE, we need the ESP8266 development board package, which we can find in the offline installation package on the official website of Electronic Lamp Technology.

The package also includes resources for ESP32.

After clicking the download button, you will be redirected to the Chinese Arduino website, where you can follow the steps to download. You will get an .exe file, which you can directly execute.

Then, in the Arduino IDE, follow the selections below (I can’t take screenshots because the options disappear when I use the shortcut keys, so I’ll have to describe it instead).

GPIO

To learn about a chip, let’s become masters of lights first.

Actually, we have very few GPIO ports available on ESP8266 NodeMCU.

Although it looks like there are many GPIO ports, we cannot use the GPIO ports on the right side (except for special purposes), as they are used to control internal storage units. Just remember that the GPIO ports on the same side as A0 cannot be used.

Then, among the GPIO ports on the left side, GPIO1 and GPIO3 are used for serial communication and are generally not used for other purposes, so the available GPIO ports are actually quite limited.

Let’s light up an LED.

Blinking LED

Configuring GPIO Mode

pinMode(uint8_t pin, uint8_t mode);

The first parameter can be directly filled with the label on the ESP8266 NodeMCU development board, such as “D0”, or you can enter a number. For example, D0 is actually GPIO16, so entering the number 16 is also acceptable.

The second parameter configures the mode. In simple terms, we use three types: OUTPUT, INPUT, and INPUT_PULLUP, which are used for output, input, and pull-up input, respectively. Although there are more modes that can be configured, these three are the most commonly used.

Digital Output

digitalWrite(uint8_t pin, uint8_t val);

The first parameter specifies the GPIO port, same as above.

The second parameter is a direct numeric value; 1 is for high level and 0 is for low level.

Lighting Up LED

void setup() {

// put your setup code here, to run once:

pinMode(D0, OUTPUT); // equivalent to pinMode(16, OUTPUT);

scss

Copy code

digitalWrite(D0, 1);        // equivalent to digitalWrite(16, 1)

}

void loop() {

// put your main code here, to run repeatedly:

}

In case some friends are not familiar with this code format (because 51 and 32 are writing main functions), let me explain a bit. The setup function is for configuring things and is only executed once, while the loop function contains the code that will be executed repeatedly, similar to the while(1) loop in 51 or 32 code.

So, we first configure the GPIO port, then output a high level, and then connect the LED to achieve the operation of lighting up the LED.

Delay Function

delay(unsigned long ms);

Enter a number to delay for the corresponding number of milliseconds.

delayMicroseconds(unsigned int us);

This is for microsecond delay.

Blinking LED

With the delay function, we can make the LED blink.

void setup() {

// put your setup code here, to run once:

pinMode(D0, OUTPUT);

}

void loop() {

// put your main code here, to run repeatedly:

digitalWrite(D0, 1);

delay(1000);

digitalWrite(D0, 0);

delay(1000);

}

Simply output high level and low level alternately.

Of course, the above code looks too cumbersome, so we have a more concise way.

Digital Reading

digitalRead(uint8_t pin);

With this function, we can read the input level of the corresponding GPIO port.

We just need to make the output level of the GPIO port different from the input level.

Blinking LED 2.0

void setup() {

// put your setup code here, to run once:

pinMode(D0, OUTPUT);

}

void loop() {

// put your main code here, to run repeatedly:

digitalWrite(D0, !digitalRead(D0));

delay(1000);

}

You may be confused why we can read when the GPIO port is configured as an output.

I’ve tested it, and it works. When the GPIO port is in output mode, what is actually read is its own output. If you want to read the input from other modules, you still need to configure it as an input (INPUT) mode, otherwise the read data may be incorrect, or even affect the module to be connected.

Timing Function

First, you need to include .

Then define a Ticker type object, and the name cannot be “time” (learned it the hard way).

Call the member function of this object to complete the timing operation.

Scheduled Execution

attach()

The first parameter of this member function is filled with an integer, indicating the timing in seconds.

The second parameter is filled with the function name, which can have at most one integer parameter. So, the function will be executed every number of seconds specified in the first parameter.

The third parameter, if the function filled in earlier has no parameters, then the third position can be left empty, otherwise the third parameter is the parameter passed to the function when it’s called.

This function can be called multiple times, but only the last one will take effect.

For millisecond-level timing, use the following member function.

attach_ms()

Cancel Timing

detach()

This member function has no parameters. After calling it, the previous timing will be canceled.

Timer Function

If you only need to call it once and don’t need looping timing, you can use the timer function, which has the same parameters as the timing function and will only be called once.

once()

once_ms()

It should be noted that each Ticker object can only have one task at the same time.

Blinking LED 3.0

#include

Ticker t0;

Ticker t1;

void test(int flag){

digitalWrite(D0, !digitalRead(D0));

}

void setup() {

// put your setup code here, to run once:

pinMode(D0, OUTPUT);

t0.attach(1, test, 1);

t1.once(10, &{

t0

Additionally, some other information I found mentioned that there are functions to modify the PWM output frequency. However, after testing with a passive buzzer, I found that these functions didn’t seem to work, and I couldn’t find the reason why.

One of these functions is as follows. You can try it with your own board, just in case it works:

“`cpp

analogWriteFreq(uint32_t freq);

“`

External Interrupts

External Interrupt Configuration

“`cpp

attachInterrupt(uint8_t pin, void (*)(), int mode);

“`

The first parameter specifies the GPIO pin, but unlike before, here you need to use `digitalPinToInterrupt()` to wrap the GPIO pin. This indicates that the GPIO pin is used for external interrupts, and note that D0 cannot be used for this purpose.

The second parameter is a function without parameters or return value. This function will be called when an external interrupt is triggered. When defining the function, you need to include `ICACHE_RAM_ATTR` at the beginning of the function.

The third parameter specifies the conditions for triggering the external interrupt. There are three options: `RISING`, `FALLING`, and `CHANGE`, representing rising edge, falling edge, and both rising and falling edges, respectively.

For specific details, you can refer to the code below.

Disabling Interrupts

“`cpp

detachInterrupt(uint8_t pin);

“`

If we need a GPIO pin to stop triggering external interrupts, we use this function. Here, the parameter is directly written without using the `digitalPinToInterrupt()` function.

Controlling LED with a Switch

“`cpp

int count = 0;

ICACHE_RAM_ATTR void test() {

digitalWrite(D0, !digitalRead(D0));

if (++count >= 10) detachInterrupt(D1);

}

void setup() {

pinMode(D0, OUTPUT);

pinMode(D1, INPUT_PULLUP);

attachInterrupt(digitalPinToInterrupt(D1), test, RISING);

}

void loop() {

// Main code

}

“`

Here, D0 is set as output mode, D1 as input mode, and external interrupt on rising edge of D1. Each time it triggers, it toggles the level of D0. After ten times, it detaches the interrupt on D1.

Serial Communication

After connecting ESP8266 NodeMCU to the computer via a data cable, you can directly use serial communication with the computer.

Arduino IDE has a built-in serial monitor, located in the upper right corner of the page. Clicking on it will bring up the serial monitor at the bottom of the page, where you can easily send and receive data.

Serial Initialization

“`cpp

Serial.begin(unsigned long baud);

“`

There is only one initialization function, but there are five overloaded versions. The most commonly used one is shown above, where we specify the baud rate, and the default settings are 8 data bits, 1 stop bit, and no parity bit.

Outputting Data

There are many functions for outputting data. Here, I’ll briefly introduce a few that I often use.

**write**

“`cpp

Serial.write();

“`

This function has twelve overloaded versions. Generally, when we need to output raw data in hexadecimal format, we use this function.

**print & println & printf**

“`cpp

Serial.print();

Serial.println();

Serial.printf();

“`

These three output text format, so they are commonly used for debugging information.

– `print`: Normal output.

– `println`: Similar to `print`, but adds a newline character (`rn`) at the end.

– `printf`: Used for formatted output, similar to how we use it in C language.

Reading Data

There are also many functions for reading data. Here, I’ll briefly explain a few that I personally use frequently.

**read**

“`cpp

Serial.read()

“`

This function has two overloaded versions. Let me explain both:

– The first version doesn’t have any parameters. It simply reads one byte and returns it.

– The second version looks like this:

“`cpp

Serial.read(char *buffer, size_t size);

“`

It reads the number of data specified by the second parameter into the buffer provided by the first parameter. However, if the size of the second parameter is larger than the available data to be read, it will only read the available amount of data and return the actual number of bytes read.

**readString**

“`cpp

Serial.readString()

“`

This function reads data and returns it as a String type.

Checking for Data Availability

If we want to read data, we first need to know if there is any data available. How do we know when there is data available? We use the following function:

“`cpp

Serial.available()

“`

It returns an `int` value to indicate whether there is data available to be read. But we can treat its return value as a `bool` type.

Serial Echo Experiment

Now that we understand these functions, we can use ESP8266 NodeMCU for serial communication.

The following code is an echo experiment. When data is received, it is echoed back unchanged. This is

The effect is also very good. This is my own mobile hotspot that I connected to. I deliberately disconnected the interrupt once, but it could reconnect again.

MQTT

Now that we have connected the ESP8266 NodeMCU development board to the internet, we are just one step away from mastering the ESP8266.

Microcontroller + Internet = IoT. Speaking of the Internet of Things, the unavoidable communication protocol is MQTT. Of course, HTTP/HTTPS can also be used, and there are corresponding libraries for ESP8266 for that purpose. However, we’ll focus on how to communicate using MQTT.

Environment Configuration

First, we need to use the PubSubClient library. So we need to download it.

Download it from the following website:

https://www.arduino.cc/reference/en/libraries/pubsubclient/

After downloading the zip file from this website, import it into the Arduino IDE.

Then include ``.

And with this library, we also need to include ``.

Yes, that’s the library we used to connect to Wi-Fi.

Initialization

First, we need to create two objects.

“`cpp

WiFiClient wc;

PubSubClient pc(wc);

“`

The first object of type `WiFiClient` requires the `ESP8266WiFi` library.

The second object of type `PubSubClient` requires the `PubSubClient` library, and we need to pass a `WiFiClient` type object to initialize it.

It’s that simple.

Settings

Secondly, we can start setting the MQTT server and port.

“`cpp

pc.setServer(“xx.xx.xx.xx”, 1883);

“`

There are free public MQTT servers that we can use directly. You can find them online. As for the port, MQTT is basically 1883.

It’s that simple.

Connection

Thirdly, we can start connecting.

“`cpp

pc.connect(WiFi.macAddress().c_str());

“`

We need to pass the ID used to connect to the MQTT server, which cannot be the same as the IDs of other devices connected to the MQTT server at the same time. Generally, I use the physical address of the device, so it won’t overlap with others. There are other overloaded versions where you can set other information needed to connect to the MQTT server, but I won’t demonstrate them here.

It will return whether the connection is successful.

It’s that simple.

Publish Topic Message

After connecting successfully, we can subscribe and publish.

“`cpp

pc.publish(topic, data);

“`

In the first parameter, fill in the topic name to be published. In the second parameter, fill in the content to be published.

It’s that simple.

Subscribe to Topic

“`cpp

pc.subscribe(topic);

“`

Fill in the topic name to subscribe to, and that’s it.

It’s that simple.

But after subscribing, we’re not done yet. Where do we go to retrieve the subscribed messages?

Subscribe Callback Function

“`cpp

pc.setCallback(getMQTT);

“`

Fill in the name of the callback function. When a message is received on the subscribed topic, this callback function will be automatically called.

So how does this function know which topic the message came from and what the message is?

That’s because this callback function has a fixed parameter format.

“`cpp

void getMQTT(char* topic, byte* payload, unsigned int length)

“`

I arbitrarily named this function, and you can too. The key is that the parameter types after the function name must be consistent.

The first one is the topic name from which the message came.

The second is the actual message data.

The third is the length of the message.

One more thing, we need to periodically report to the MQTT server that we’re still alive, meaning sending a message to tell MQTT that we’re alive. This is called heartbeat information. We can set the heartbeat interval before connecting to the MQTT server.

“`cpp

pc.setKeepAlive(n);

“`

If not set, it defaults to 15 seconds. If we don’t send heartbeat information within 15 seconds, MQTT will think we’re dead, and we won’t receive subscribed messages. So, we need to send heartbeat information frequently within the heartbeat interval.

There’s also the following function:

“`cpp

pc.loop();

“`

At the same time, we also have a function to determine whether we are connected to the MQTT server. If we are connected, we can send heartbeat information. If disconnected, we can reconnect.

“`cpp

pc.connected();

“`

So far, we can handle MQTT. I believe the following code segment can help you deepen your understanding. It’s best for everyone to try it out yourself.

“`cpp

#include

#include

const char* WIFINAME = “xxx”;               // WIFI name

const char* WIFIPASSWORD = “xxx”;         // WIFI password

// Parameter one is WIFI name, parameter two is WIFI password, parameter three is the maximum waiting time set

void connectWifi(const char* wifiName, const char* wifiPassword, uint8_t waitTime) {

WiFi.mode(WIFI_STA);                    // Set to wireless terminal mode

WiFi.disconnect();                      // Clear configuration cache

WiFi.begin(wifiName, wifiPassword);      // Start connecting

uint8_t count = 0;

while (WiFi.status() != WL_CONNECTED) {     // Wait until connected

delay(1000);

Serial.printf(“connect WIFI…%dsrn”, ++count);

if (count >= waitTime) {                // Exit if exceeding the set waiting time

Serial.println(“connect WIFI fail”);

return;

}

}

// Connection successful, print the connected WIFI name and local IP

Serial.printf(“connect WIFI %s success, local IP is %srn”, WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());

}

void getMQTT(char* topic, byte* payload, unsigned int length) {

Serial.printf(“get data from %srn”, topic);    // Output debugging information to know which topic the message came from

for (unsigned int i = 0; i < length; ++i) {             // Read each byte of the message

Serial.print((char)payload[i]);             // Read it as text, or remove (char) to read it in hexadecimal

}

Serial.println();

}

WiFiClient wc;

PubSubClient pc(wc);

uint8_t connectMQTT() {

if (WiFi.status() != WL_CONNECTED) return -1;      // Return directly if not connected to the network

pc.setServer(“xx.xx.xx.xx”, 1883);               // Set MQTT server IP address and port (usually fixed as 1883)

if (!pc.connect(WiFi.macAddress().c_str())) {     // Connect to MQTT server using physical address as ID

Serial.println(“connect MQTT fail”);

return -1;

}

String topic = WiFi.macAddress() + “-receive”;      // Subscribe to a topic, topic name is physical address + “-receive”

pc.subscribe(topic.c_str());

pc.setCallback(getMQTT);                        // Bind subscription callback function

Serial.println(“connect MQTT success”);

return 0;

}

void setup() {

// put your setup code here

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button