NodeMCU is a versatile board with the WiFi-enabled ESP8266 system-on-chip, USB to UART bridge, voltage regulator and some auxiliary components. With a price tag of about $9 on Aliexpress it allows you develop your own Internet “things” literally in minutes. The killer part is a build-in Lua interpreter, so no toolchain or development kit is required. The language is pretty easy. Text files containing Lua scripts are simply uploaded to the board and executed directly by the interpreter inside ESP8266. You can also type the commands directly via the terminal. NodeMCU firmware can also be uploaded to any ESP8266 module like the ESP-01, ESP-07 or bare ESP-12.
Getting the latest firmware
It is best practice to have the latest firmware as there were many features, improvements and fixes made to the firmware across it’s development and you might not have the latest one programmed in the factory.
Latest firmware -> https://github.com/nodemcu/nodemcu-firmware/tree/master/pre_build/latest (the .bin file)
Upload utility -> https://github.com/themadinventor/esptool
The board has a built-in “indestructible” bootloader. No matter what crashing, buggy scripts with endless loops (especially set to be started automatically at boot) you upload, there is always a way out (and I used it more often then I planned…).
To start the bootloader just keep the FLASH button pressed while plugging in the USB cable. To upload the firmware start esptool with the right serial port, write_flash command, starting address (zero) and file name:
1 2 3 4 5 6 |
$ ./esptool-master/esptool.py --port /dev/ttyUSB0 --baud 9600 write_flash 0x0 nodemcu_latest.bin Connecting... Erasing flash... Writing at 0x00062000... (100 %) Leaving... |
The process can take a couple of minutes. I noticed that it is much faster when done again with the same firmware version (think recovery…). Probably the bootloader is just smart enough not to rewrite the flash with the same data and only removes my custom scripts.
A terminal application will be required for the next steps. I prefer picocom because it is very easy to use and has only the required features. By default NodeMCU starts serial communication at 9600 bps.
You can press enter a couple of times to see the > prompt. If you can see the character it means that the new firmware has started and a Lua interpreter is ready. I typed node.restart(); to reboot the module and see the bootup messages (they are impossible to see right after plugging the cable because the node boots much faster than USB is able to establish a connection with a PC).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
$ picocom -b 9600 /dev/ttyUSB0 picocom v1.7 port is : /dev/ttyUSB0 flowcontrol : none baudrate is : 9600 parity is : none databits are : 8 escape is : C-a local echo is : no noinit is : no noreset is : no nolock is : no send_cmd is : sz -vv receive_cmd is : rz -vv imap is : omap is : emap is : crcrlf,delbs, Terminal ready > > node.restart(); > ��D� ��D���` 9@H�� NodeMCU 0.9.5 build 20150214 powered by Lua 5.1.4 lua: cannot open init.lua > |
Hardly impressive 🙂
Doing something useful – temperature sensing
NodeMCU without any other parts has little practical use. I decided to connect DS18B20 temperature sensor to the board (and pipe it to RRDtool some day). The sensor is originally packaged in a TO-92 case. There are fully-assembled, waterproof ones available on Aliexpress (with a no-brainer price tag below $1,5). The DS18B20 uses a 1-wire bus to communicate (and get power) from the microcontroller, though I use a separate power supply line for simplicity and reliability.
The sensor has three wires – black (ground), red (power), yellow (1-wire data) that have to go to the right pins of NodeMCU. This is the pinout of NodeMCU:
Sensor’s ground should of course be connected to any of the GND pins, power (red wire) to 3,3V.
Most of the GPIO pins can be used for any purpose (like 1-wire) with some exceptions. The ones out of scope are at least: RXD0 and TXD0 (it would interfere with the UART connected to USB), GPIO16 (it is used to wake up the device from deep sleep).
To increase the fun and confusion a pin can have multiple names like: D8 is also GPIO15, TXD2 and HSPICS. The GPIOxx are names of the lines of ESP8266 (they are the same on any board), the Dxx names are used by Lua firmware.
I picked GPIO4 also known as D2 for 1-wire data. In scripts this pin will simply be 2 (without the D).
1-wire requires a pull-up resistor (visible in the first picture) to Vcc (3,3V). I soldered one directly to the board.
The script
That’s how the temperature reading code looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
tmr.delay(1000000); --make a delay to stabilize power pin=2; --onewire pin number ow.setup(pin); --configure that pin for 1-wire p=ow.reset(pin); --send a reset over the bus, returns slave present status print("present="..p); --prints 1 if a slave is present ow.skip(pin); --SKIP ROM command, no address matching is necessary if there is only one device on a bus ow.write(pin, 0x44, 1);--CONVERT T command (start temperature conversion) tmr.delay(1000000); --make a delay to allow the DS18B20 complete temperature conversion (probably too long) p2=ow.reset(pin); print("present2="..p2); ow.skip(pin); ow.write(pin,0xBE,1); --READ SCRATCHPAD command lsb=ow.read(pin); --read the first byte from the scratchpad ram msb=ow.read(pin); --read the second byte from the scratchpad ram temp=bit.lshift(msb,8)+lsb; --combine two bytes into a single integer print("msb="..msb.." lsb="..lsb.." temp="..temp); --print out all the values if (temp > 32767) then --if the temperature looks negative temp = temp - 65536; --convert from two's complement to decimal end Tc100=(6*temp)+temp/4; --multiply by 0,0625 to get centigrade*100 print("Tc100="..Tc100); --print the result temperature=string.format("%d",Tc100); --truncate the fractional part print("temperature="..temperature); --print the final temperature |
How to upload scripts to NodeMCU
Each of the script lines can be simply copied and pasted into a terminal and executed line by line (pasting the whole script at once will not work, as it will overrun the USB to UART chip’s buffers and the Lua interpreter will see junk). NodeMCU has a built-in file system and can store files very similarly to a regular PC. There is a handy tool for uploading files: luatool.
File upload looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
$ ./luatool-master/luatool/luatool.py -p /dev/ttyUSB0 -b 9600 -f gettemperature.lua ->file.open("gettemperature.lua", "w") -> ok ->file.close() -> ok ->file.remove("gettemperature.lua") -> ok ->file.open("gettemperature.lua", "w+") -> ok ->file.writeline([==[tmr.delay(1000000); --make a delay to stabilize power]==]) -> ok ->file.writeline([==[]==]) -> ok ->file.writeline([==[pin=2; --onewire pin number]==]) -> ok ->file.writeline([==[]==]) -> ok ->file.writeline([==[ow.setup(pin); --configure that pin for 1-wire]==]) -> ok ->file.writeline([==[p=ow.reset(pin); --send a reset over the bus, returns slave present status]==]) -> ok ->file.writeline([==[print("present="..p); --prints 1 if a slave is present]==]) -> ok ->file.writeline([==[ow.skip(pin); --SKIP ROM command, no address matching is necessary if there is only one device on a bus]==]) -> ok ->file.writeline([==[ow.write(pin, 0x44, 1);--CONVERT T command (start temperature conversion)]==]) -> ok ->file.writeline([==[]==]) -> ok ->file.writeline([==[tmr.delay(1000000); --make a delay to allow the DS18B20 complete temperature conversion]==]) -> ok ->file.writeline([==[]==]) -> ok ->file.writeline([==[p2=ow.reset(pin);]==]) -> ok ->file.writeline([==[print("present2="..p2);]==]) -> ok ->file.writeline([==[ow.skip(pin);]==]) -> ok ->file.writeline([==[ow.write(pin,0xBE,1); --READ SCRATCHPAD command]==]) -> ok ->file.writeline([==[lsb=ow.read(pin); --read the first byte from the scratchpad ram]==]) -> ok ->file.writeline([==[msb=ow.read(pin); --read the second byte from the scratchpad ram]==]) -> ok ->file.writeline([==[temp=bit.lshift(msb,8)+lsb; --combine two bytes into a single integer]==]) -> ok ->file.writeline([==[print("msb="..msb.." lsb="..lsb.." temp="..temp); --print out all the values]==]) -> ok ->file.writeline([==[]==]) -> ok ->file.writeline([==[if (temp > 32767) then --if the temperature looks negative]==]) ->file.writeline([==[temp = temp - 65536; --convert from two's complement to decimal]==]) -> ok ->file.writeline([==[end]==]) -> ok ->file.writeline([==[]==]) -> ok ->file.writeline([==[Tc100=(6*temp)+temp/4; --multiply by 0,0625 to get centigrade*100]==]) -> ok ->file.writeline([==[print("Tc100="..Tc100); --print the result]==]) -> ok ->file.writeline([==[temperature=string.format("%d",Tc100); --truncate the fractional part]==]) -> ok ->file.writeline([==[print("temperature="..temperature); --print the final temperature]==]) -> ok ->file.flush() -> ok ->file.close() -> ok --->>> All done <<<--- |
Luatool creates file on the NodeMCU and then executes a file write command for each line of the script. It can be quite slow, but it works (increasing the baudrate is an option).
To execute the script you have to open the terminal and type dofile('gettemperature.lua');
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
$ picocom -b 9600 /dev/ttyUSB0 picocom v1.7 port is : /dev/ttyUSB0 flowcontrol : none baudrate is : 9600 parity is : none databits are : 8 escape is : C-a local echo is : no noinit is : no noreset is : no nolock is : no send_cmd is : sz -vv receive_cmd is : rz -vv imap is : omap is : emap is : crcrlf,delbs, Terminal ready > dofile('gettemperature.lua'); present=1 present2=1 msb=1 lsb=186 temp=442 Tc100=2762.5 temperature=2762 > |
The sensor is detected, read and the temperature is 27,62 centigrade 🙂 The delay between conversion and reading can be much shorter. For the networked thermometer I will try to do something useful in between.
Next post wll cover transmitting the temperature to a PC over WiFi and making a small server application.
One thought on “First steps with NodeMCU”
Comments are closed.