Showing posts with label IoT development. Show all posts
Showing posts with label IoT development. Show all posts

Tuesday, 26 August 2025

FreeRTOS on ESP32: Beginner's Guide with Features, Benefits & Practical Examples

Standard



Introduction

When developing embedded systems, managing tasks, timing, and resources efficiently becomes a challenge as the complexity of the application grows. This is where Real-Time Operating Systems (RTOS) come in.

FreeRTOS is one of the most popular open-source real-time operating systems for microcontrollers. It is small, fast, and easy to integrate into resource-constrained devices like the ESP32, making it ideal for IoT, automation, and robotics projects.

In this blog topic, we will cover:

  • What FreeRTOS is
  • Key features of FreeRTOS
  • Why FreeRTOS is a good choice for ESP32 projects
  • A hands-on example using ESP32

What is FreeRTOS?

FreeRTOS is a lightweight, real-time operating system kernel for embedded devices. It provides multitasking capabilities, letting you split your application into independent tasks (threads) that run seemingly in parallel.

For example, on ESP32, you can have:

  • One task reading sensors
  • Another handling Wi-Fi communication
  • A third controlling LEDs

All running at the same time without interfering with each other.

Key Features of FreeRTOS

1. Multitasking with Priorities

FreeRTOS allows multiple tasks to run with different priorities. The scheduler ensures high-priority tasks get CPU time first, making it suitable for real-time applications.

2. Lightweight and Portable

The kernel is very small (a few KBs), making it ideal for microcontrollers like ESP32 with limited resources.

3. Preemptive and Cooperative Scheduling

  • Preemptive: Higher priority tasks can interrupt lower ones.
  • Cooperative: Tasks voluntarily give up CPU control.

This provides flexibility depending on your project needs.

4. Task Synchronization

Features like semaphores, mutexes, and queues help coordinate tasks and prevent resource conflicts.

5. Software Timers

Timers allow tasks to be triggered at regular intervals without blocking the main code.

6. Memory Management

Multiple memory allocation schemes let you optimize for speed or minimal memory fragmentation.

7. Extensive Hardware Support

FreeRTOS runs on 40+ architectures, including ARM Cortex-M, AVR, RISC-V, and of course, ESP32 (via the ESP-IDF framework).

Why Use FreeRTOS on ESP32?

The ESP32 has:

  • Dual-core processor
  • Wi-Fi + Bluetooth
  • Plenty of GPIOs

With FreeRTOS, you can use these resources efficiently:

  • Run Wi-Fi tasks on Core 0
  • Handle sensor data on Core 1
  • Keep the system responsive and organized

Example: Blinking LED Using FreeRTOS on ESP32

Below is a simple FreeRTOS example using ESP-IDF or Arduino IDE with the ESP32.

Code Example

#include <Arduino.h>

// Task Handles
TaskHandle_t Task1;
TaskHandle_t Task2;

// Task 1: Blink LED every 1 second
void TaskBlink1(void *pvParameters) {
  pinMode(2, OUTPUT);  // Onboard LED
  while (1) {
    digitalWrite(2, HIGH);
    vTaskDelay(1000 / portTICK_PERIOD_MS); // 1 second delay
    digitalWrite(2, LOW);
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

// Task 2: Print message every 2 seconds
void TaskPrint(void *pvParameters) {
  while (1) {
    Serial.println("Task 2 is running!");
    vTaskDelay(2000 / portTICK_PERIOD_MS);
  }
}

void setup() {
  Serial.begin(115200);
  
  // Create two FreeRTOS tasks
  xTaskCreate(TaskBlink1, "Blink Task", 1000, NULL, 1, &Task1);
  xTaskCreate(TaskPrint, "Print Task", 1000, NULL, 1, &Task2);
}

void loop() {
  // Nothing here - tasks handle everything
}

How the Code Works

  • xTaskCreate: Creates a FreeRTOS task. Each task runs independently.
  • vTaskDelay: Delays a task without blocking others.
  • Two tasks:
    • Task 1 blinks the LED every second.
    • Task 2 prints a message every two seconds.

Both tasks run in parallel on the ESP32.

In Diagramatically shown below:

The above diagram represents;

  • Groups tasks clearly by Core 0 (Network/IO) and Core 1 (Control/Timing).
  • Places shared Queue/Event Group in the center.
  • Shows ISR → Queue → Tasks data flow with minimal arrows for clarity.

Let’s level this up with practical FreeRTOS patterns on ESP32 (Arduino core or ESP-IDF style APIs). Each example is bite-sized and focused on one RTOS feature so you can mix-and-match in a real project.

More FreeRTOS Examples on ESP32

1) Pin Tasks to Cores + Precise Periodic Scheduling

Use xTaskCreatePinnedToCore to control where tasks run and vTaskDelayUntil for jitter-free loops.

#include <Arduino.h>

TaskHandle_t sensorTaskHandle, wifiTaskHandle;

void sensorTask(void *pv) {
  const TickType_t period = pdMS_TO_TICKS(10);  // 100 Hz
  TickType_t last = xTaskGetTickCount();
  for (;;) {
    // read sensor here
    // ...
    vTaskDelayUntil(&last, period);
  }
}

void wifiTask(void *pv) {
  for (;;) {
    // handle WiFi / MQTT here
    vTaskDelay(pdMS_TO_TICKS(50));
  }
}

void setup() {
  Serial.begin(115200);

  // Run time-critical sensor task on Core 1, comms on Core 0
  xTaskCreatePinnedToCore(sensorTask, "sensor", 2048, NULL, 3, &sensorTaskHandle, 1);
  xTaskCreatePinnedToCore(wifiTask,   "wifi",   4096, NULL, 2, &wifiTaskHandle,   0);
}

void loop() {}

Why it’s useful: keep deterministic work (sensors/control) isolated from network stacks.

2) Queues: From ISR to Task (Button → LED)

Move edge events out of the ISR using queues and process them safely in a task.

#include <Arduino.h>

static QueueHandle_t buttonQueue;
const int BTN_PIN = 0;      // adjust for your board
const int LED_PIN = 2;

void IRAM_ATTR onButtonISR() {
  uint32_t tick = millis();
  BaseType_t hpTaskWoken = pdFALSE;
  xQueueSendFromISR(buttonQueue, &tick, &hpTaskWoken);
  if (hpTaskWoken) portYIELD_FROM_ISR();
}

void ledTask(void *pv) {
  pinMode(LED_PIN, OUTPUT);
  uint32_t eventTime;
  for (;;) {
    if (xQueueReceive(buttonQueue, &eventTime, portMAX_DELAY) == pdPASS) {
      // simple action: blink LED on each press
      digitalWrite(LED_PIN, !digitalRead(LED_PIN));
      Serial.printf("Button @ %lu ms\n", eventTime);
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(BTN_PIN, INPUT_PULLUP);

  buttonQueue = xQueueCreate(8, sizeof(uint32_t));
  attachInterrupt(digitalPinToInterrupt(BTN_PIN), onButtonISR, FALLING);

  xTaskCreate(ledTask, "ledTask", 2048, NULL, 2, NULL);
}

void loop() {}

Tip: keep ISRs tiny; send data to tasks via queues.

3) Mutex: Protect Shared Resources (Serial / I²C / SPI)

Avoid interleaved prints or bus collisions with a mutex.

#include <Arduino.h>

SemaphoreHandle_t ioMutex;

void chatterTask(void *pv) {
  const char *name = (const char*)pv;
  for (;;) {
    if (xSemaphoreTake(ioMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
      Serial.printf("[%s] hello\n", name);
      xSemaphoreGive(ioMutex);
    }
    vTaskDelay(pdMS_TO_TICKS(200));
  }
}

void setup() {
  Serial.begin(115200);
  ioMutex = xSemaphoreCreateMutex();

  xTaskCreate(chatterTask, "chat1", 2048, (void*)"T1", 1, NULL);
  xTaskCreate(chatterTask, "chat2", 2048, (void*)"T2", 1, NULL);
}

void loop() {}

Why it’s useful: prevents priority inversion and corrupted I/O.

4) Binary Semaphore: Signal Readiness (Wi-Fi Connected → Start Task)

Use a binary semaphore to gate a task until some condition is met.

#include <Arduino.h>
SemaphoreHandle_t wifiReady;

void workerTask(void *pv) {
  // wait until Wi-Fi is ready
  xSemaphoreTake(wifiReady, portMAX_DELAY);
  Serial.println("WiFi ready, starting cloud sync…");
  for (;;) {
    // do cloud work
    vTaskDelay(pdMS_TO_TICKS(1000));
  }
}

void setup() {
  Serial.begin(115200);
  wifiReady = xSemaphoreCreateBinary();

  // simulate Wi-Fi connect on another task/timer
  xTaskCreate([](void*){
    vTaskDelay(pdMS_TO_TICKS(2000)); // pretend connect delay
    xSemaphoreGive(wifiReady);
    vTaskDelete(NULL);
  }, "wifiSim", 2048, NULL, 2, NULL);

  xTaskCreate(workerTask, "worker", 4096, NULL, 2, NULL);
}

void loop() {}

5) Event Groups: Wait for Multiple Conditions

Synchronize on multiple bits (e.g., Wi-Fi + Sensor) before proceeding.

#include <Arduino.h>
#include "freertos/event_groups.h"

EventGroupHandle_t appEvents;
const int WIFI_READY_BIT  = BIT0;
const int SENSOR_READY_BIT= BIT1;

void setup() {
  Serial.begin(115200);
  appEvents = xEventGroupCreate();

  // Simulate async readiness
  xTaskCreate([](void*){
    vTaskDelay(pdMS_TO_TICKS(1500));
    xEventGroupSetBits(appEvents, WIFI_READY_BIT);
    vTaskDelete(NULL);
  }, "wifi", 2048, NULL, 2, NULL);

  xTaskCreate([](void*){
    vTaskDelay(pdMS_TO_TICKS(800));
    xEventGroupSetBits(appEvents, SENSOR_READY_BIT);
    vTaskDelete(NULL);
  }, "sensor", 2048, NULL, 2, NULL);

  // Wait for both bits
  xTaskCreate([](void*){
    EventBits_t bits = xEventGroupWaitBits(
      appEvents, WIFI_READY_BIT | SENSOR_READY_BIT,
      pdFALSE,  /* don't clear */
      pdTRUE,   /* wait for all */
      portMAX_DELAY
    );
    Serial.printf("Ready! bits=0x%02x\n", bits);
    vTaskDelete(NULL);
  }, "gate", 2048, NULL, 3, NULL);
}

void loop() {}

6) Software Timers: Non-Blocking Periodic Work

Use xTimerCreate for periodic or one-shot jobs without dedicating a full task.

#include <Arduino.h>

TimerHandle_t blinkTimer;
const int LED = 2;

void blinkCb(TimerHandle_t) {
  digitalWrite(LED, !digitalRead(LED));
}

void setup() {
  pinMode(LED, OUTPUT);
  blinkTimer = xTimerCreate("blink", pdMS_TO_TICKS(250), pdTRUE, NULL, blinkCb);
  xTimerStart(blinkTimer, 0);
}

void loop() {}

Why it’s useful: frees CPU and stack compared to a dedicated blink task.

7) Task Notifications: Fast 1-to-1 Signal (Lighter than Queues)

Direct-to-task notifications are like super-light binary semaphores.

#include <Arduino.h>

TaskHandle_t workTaskHandle;

void IRAM_ATTR quickISR() {
  BaseType_t xHigher = pdFALSE;
  vTaskNotifyGiveFromISR(workTaskHandle, &xHigher);
  if (xHigher) portYIELD_FROM_ISR();
}

void workTask(void *pv) {
  for (;;) {
    ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // waits, clears on take
    // handle event fast
    Serial.println("Notified!");
  }
}

void setup() {
  Serial.begin(115200);
  xTaskCreate(workTask, "work", 2048, NULL, 3, &workTaskHandle);

  // simulate an interrupt source using a timer
  hw_timer_t *timer = timerBegin(0, 80, true); // 1 us tick
  timerAttachInterrupt(timer, &quickISR, true);
  timerAlarmWrite(timer, 500000, true); // 500ms
  timerAlarmEnable(timer);
}

void loop() {}

8) Producer–Consumer with Queue + Backpressure

Avoid overruns by letting the queue throttle the producer.

#include <Arduino.h>

QueueHandle_t dataQ;

void producer(void *pv) {
  uint16_t sample = 0;
  for (;;) {
    sample++;
    if (xQueueSend(dataQ, &sample, pdMS_TO_TICKS(10)) != pdPASS) {
      // queue full -> dropped (or handle differently)
    }
    vTaskDelay(pdMS_TO_TICKS(5)); // 200 Hz
  }
}

void consumer(void *pv) {
  uint16_t s;
  for (;;) {
    if (xQueueReceive(dataQ, &s, portMAX_DELAY) == pdPASS) {
      // heavy processing
      vTaskDelay(pdMS_TO_TICKS(20)); // slower than producer
      Serial.printf("Processed %u\n", s);
    }
  }
}

void setup() {
  Serial.begin(115200);
  dataQ = xQueueCreate(16, sizeof(uint16_t));
  xTaskCreatePinnedToCore(producer, "prod", 2048, NULL, 2, NULL, 1);
  xTaskCreatePinnedToCore(consumer, "cons", 4096, NULL, 2, NULL, 0);
}

void loop() {}

9) Watchdog-Friendly Yields in Busy Tasks

Long loops should yield to avoid soft WDT resets and keep the system responsive.

#include <Arduino.h>

void heavyTask(void *pv) {
  for (;;) {
    // do chunks of work…
    // ...
    vTaskDelay(1); // yield to scheduler (~1 tick)
  }
}

void setup() {
  xTaskCreate(heavyTask, "heavy", 4096, NULL, 1, NULL);
}

void loop() {}

10) Minimal ESP-IDF Style (for reference)

If you’re on ESP-IDF directly:

// C (ESP-IDF)
void app_main(void) {
  xTaskCreatePinnedToCore(taskA, "taskA", 2048, NULL, 3, NULL, 1);
  xTaskCreatePinnedToCore(taskB, "taskB", 4096, NULL, 2, NULL, 0);
}

APIs are the same FreeRTOS ones; you’ll use ESP-IDF drivers (I2C, ADC, Wi-Fi) instead of Arduino wrappers.

Practical Stack/Perf Tips

  • Start with 2 ~ 4 KB stack per task; raise if you see resets. Use uxTaskGetStackHighWaterMark(NULL) to check headroom.
  • Prefer task notifications over queues for single-bit triggers; they’re faster and lighter.
  • Keep ISRs tiny; do work in tasks.
  • Use vTaskDelayUntil for fixed-rate loops (control systems).
  • Group readiness with Event Groups; single readiness with binary semaphores.

Real-World Use Cases on ESP32

  • Home Automation: Sensor monitoring + Wi-Fi communication + relay control.
  • Industrial IoT: Data acquisition + edge processing + cloud integration.
  • Wearables: Health data collection + Bluetooth communication.

FreeRTOS turns your ESP32 into a powerful multitasking device capable of handling complex, real-time applications. Its lightweight nature, multitasking support, and rich feature set make it perfect for IoT, robotics, and industrial projects.

By starting with simple tasks like LED blinking, you can gradually build more complex systems involving sensors, communication, and user interfaces; all running smoothly on FreeRTOS.

Bibliography

Sunday, 15 July 2018

INTERFACING NODEMCU ESP 8266 with LUA Programming Language

Standard

NodeMCU ESP8266

Image result for lolin nodemcu v3 pinout

Image result for esp8266 architecture

What is ESP8266?

The Chips

The ESP8266 series, or family, of Wi-Fi chips is produced by Espressif Systems, a fabless semiconductor company operating out of Shanghai, China. The ESP8266 series presently includes the ESP8266EX and ESP8285 chips.
ESP8266EX (simply referred to as ESP8266) is a system-on-chip (SoC) which integrates a 32-bit Tensilica microcontroller, standard digital peripheral interfaces, antenna switches, RF balun, power amplifier, low noise receive amplifier, filters and power management modules into a small package. It provides capabilities for 2.4 GHz Wi-Fi (802.11 b/g/n, supporting WPA/WPA2), general-purpose input/output (16 GPIO), Inter-Integrated Circuit (I²C), analog-to-digital conversion (10-bit ADC), Serial Peripheral Interface (SPI), I²S interfaces with DMA (sharing pins with GPIO), UART (on dedicated pins, plus a transmit-only UART can be enabled on GPIO2), and pulse-width modulation (PWM). The processor core, called "L106" by Espressif, is based on Tensilica's Diamond Standard 106Micro 32-bit processor controller core and runs at 80 MHz (or overclocked to 160 MHz). It has a 64 KiB boot ROM, 32 KiB instruction RAM, and 80 KiB user data RAM. (Also, 32  KiB instruction cache RAM and 16 KiB ETS system data RAM.) External flash memory can be accessed through SPI. The silicon chip itself is housed within a 5 mm × 5 mm Quad Flat No-Leads package with 33 connection pads — 8 pads along each side and one large thermal/ground pad in the center.
ESP8285 is a variation of ESP8266 with 1 MiB of embedded flash memory.
Now, if you're looking for something a bit more capable, check out ESP32 — it has more memory, more GPIO, hardware encryption, Bluetooth, and all sorts of other additional bells and whistles.




The MCU Architecture and Pinouts

Image result for esp8266 architecture

PINOUTS:


Image result for esp8266 pinouts

Image result for esp8266 pinouts


Related image


The Modules

Vendors have consequently created a multitude of compact printed circuit board modules based around the ESP8266 chip. Some of these modules have specific identifiers, including monikers such as "ESP-WROOM-02" and and "ESP-01" through "ESP-14"; while other modules might be ill-labeled and merely referred to by a general description — e.g., "ESP8266 Wireless Transceiver." ESP8266-based modules have demonstrated themselves as a capable, low-cost, networkable foundation for facilitating end-point IoT developments. Espressif's official modules are presently ESP-WROOM-02 and ESP-WROOM-S2. The Ai-Thinker modules are succinctly labeled ESP-01 through ESP-14. (Note: many people refer to the Ai-Thinker modules with the unofficial monikers of "ESP8266-01" through "ESP8266-14" for clarity.) See the ESP8266 article on Wikipedia for more information about popular ESP8266 modules.
  • Current models:
    ModelFlash memoryAntennaSize (mm)
    ESP-01S1 MiBPCB trace24.7 × 14.4 × 11
    ESP-01M1 MiBPCB trace18 × 18 × 2.8
    ESP-07S4 MiBI-PEX17 × 16 × 3
    ESP-08S4 MiB(None)17.6 × 16.2 × 3
    ESP-12F4 MiBPCB trace24 × 16 × 3
    ESP-12S4 MiBPCB trace24 × 16 × 3
  • Older models: ESP-01, ESP-7, ESP-08, ESP-09, ESP-10, ESP-11, ESP-12, ESP-12E, and ESP-13.
  • Discontinued models: ESP-02, ESP-03, ESP-04, ESP-05, ESP-06, and ESP-14.
  • Ai-Thinker wiki: ESP8266 section
  • Current models:
    ModelFlash memoryAntennaSize (mm)
    ESP-11 MiBCeramic & I-PEX24.5 × 14 × 3
    ESP-F4 MiBPCB trace24 × 16 × 3
    ESP-M11 MiBI-PEX15 × 12.3 × 3
    ESP-M21 MiBPCB trace20 × 12.3 × 3
    ESP-M31 MiBPCB trace26.8 × 16
    ESP-S4 MiBPCB trace24 × 16 × 3
  • ESP8285 is used in ESP-1, ESP-M1, ESP-M2, and ESP-M3.
  • Discontinued model: ESP-E.

The Development Boards/Modules

ESP8266 based development boards/modules often incorporate a surface-mount PCB module, a on-board USB-to-serial bridge, and breakout to 0.1 inch pitch connections. For example, the NodeMCU Development Kits use Ai-Thinker modules, the Adafruit Feather HUZZAH uses an Ai-Thinker ESP-12S module with a SiLabs CP2104 USB-to-serial bridge chip, and the WEMOS D1 Mini version 2.3 uses an Ai-Thinker ESP-12S module with a WinChipHead CH340G USB-to-serial bridge chip. Other development boards don't use an intermediary module and instead directly incorporate the chip itself on-board — for example, WEMOS D1 Mini Pro uses ESP8266EX and WEMOS D1 Mini Lite uses ESP8285.

Where can I learn more about it?

Forums & Chat

Readings & Resources


Software Platforms, Firmwares & Frameworks


Where can I get it?

Disclaimer: Vendors are listed for informational purposes only. Buyers should use prudence and careful judgement when ordering. Before ordering, read all product descriptions and check vendor ratings when possible. Prices listed below are approximate and do not include shipping costs. Furthermore, prices listed below may be outdated, so be diligent and check for yourself.


EXAMPLES:

Connect to the wireless network
              
print(wifi.sta.getip())
--nil
wifi.setmode(wifi.STATION)
wifi.sta.config("SSID","password")
print(wifi.sta.getip())
--192.168.18.110
              
            
Arduino like IO access
              
pin = 1
gpio.mode(pin,gpio.OUTPUT)
gpio.write(pin,gpio.HIGH)
gpio.mode(pin,gpio.INPUT)
print(gpio.read(pin))

              
            
HTTP Client
              
-- A simple http client
conn=net.createConnection(net.TCP, false) 
conn:on("receive", function(conn, pl) print(pl) end)
conn:connect(80,"121.41.33.127")
conn:send("GET / HTTP/1.1\r\nHost: www.nodemcu.com\r\n"
    .."Connection: keep-alive\r\nAccept: */*\r\n\r\n")

              
            
HTTP Server
              
-- a simple http server
srv=net.createServer(net.TCP) 
srv:listen(80,function(conn) 
    conn:on("receive",function(conn,payload) 
    print(payload) 
    conn:send("

Hello, NodeMcu.

"
) end) end)
PWM
              
function led(r,g,b) 
    pwm.setduty(1,r) 
    pwm.setduty(2,g) 
    pwm.setduty(3,b) 
end
pwm.setup(1,500,512) 
pwm.setup(2,500,512) 
pwm.setup(3,500,512)
pwm.start(1) 
pwm.start(2) 
pwm.start(3)
led(512,0,0) -- red
led(0,0,512) -- blue
              
            
Blinking Led / HELLO WORLD ! in MCU & MPU WORLD
              

lighton=0
tmr.alarm(0,1000,1,function()
if lighton==0 then 
    lighton=1 
    led(512,512,512) 
    -- 512/1024, 50% duty cycle
else 
    lighton=0 
    led(0,0,0) 
end 
end)

              
            
Bootstrap
              
--init.lua will be excuted
file.open("init.lua","w")
file.writeline([[print("Hello World!")]])
file.close()
node.restart()  -- this will restart the module.
              
            
Use timer to repeat
              
tmr.alarm(1,5000,1,function() print("alarm 1") end)
tmr.alarm(0,1000,1,function() print("alarm 0") end)
tmr.alarm(2,2000,1,function() print("alarm 2") end)
-- after sometime
tmr.stop(0)
              
            
A pure lua telnet server
              
-- a simple telnet server
s=net.createServer(net.TCP,180) 
s:listen(2323,function(c) 
    function s_output(str) 
      if(c~=nil) 
        then c:send(str) 
      end 
    end 
    node.output(s_output, 0)   
    -- re-direct output to function s_ouput.
    c:on("receive",function(c,l) 
      node.input(l)           
      --like pcall(loadstring(l)), support multiple separate lines
    end) 
    c:on("disconnection",function(c) 
      node.output(nil)        
      --unregist redirect output function, output goes to serial
    end) 
    print("Welcome to NodeMcu world.")
end)

              
            
Interfacing with sensor
              
-- read temperature with DS18B20
t=require("ds18b20")
t.setup(9)
addrs=t.addrs()
-- Total DS18B20 numbers, assume it is 2
print(table.getn(addrs))
-- The first DS18B20
print(t.read(addrs[1],t.C))
print(t.read(addrs[1],t.F))
print(t.read(addrs[1],t.K))
-- The second DS18B20
print(t.read(addrs[2],t.C))
print(t.read(addrs[2],t.F))
print(t.read(addrs[2],t.K))
-- Just read
print(t.read())
-- Just read as centigrade
print(t.read(nil,t.C))
-- Don't forget to release it after use
t = nil
ds18b20 = nil
package.loaded["ds18b20"]=nil
              
            
Source: https://cdn-images-1.medium.com/max/1388/1*_WSqG1NE4ofI_dM6eSOfuA.png
Source:https://www.google.co.in/search?q=esp8266+architecture&safe=strict&source=lnms&tbm=isch&sa=X&ved=0ahUKEwiU8rPHvaHcAhWDfysKHQ3kBwQQ_AUICigB&biw=1692&bih=873#imgrc=RP97KzgNsCC2-M:
Source: http://nodemcu.com/