This tutorial describes the steps to set up an ESP8266 and get it talking to both a temperature sensor and LED strip, while also being able to receive input and send output with MQTT over WiFi. The project was made for a course taken at Cal Poly San Luis Obispo in Fall 2016- CPE 439: Real Time Embedded Systems. The overall goal was to demonstrate the ease of creating an internet-connected "thing" with cheap hardware.
- NodeMCU ESP8266 dev board
- WS2812B LED strip
- MAX31820 Temperature sensor
- 4.7K ohm resistor
- 220 ohm resistor
- jumper wires
- micro-usb cable
- PC (or VM) running linux (eg. Ubuntu)
- experience with using command-line tools and installing packages on a debian-based distro
- basic understanding of Makefile syntax
- connecting wires
Step 1: Creating a Build Environment
To build the project, you'll need esp-open-sdk installed on your machine. Follow the link and read the build instructions. In short you'll be doing some sudo apt-get commands to install dependencies, a git clone --recursive to clone/download esp-open-sdk, and finally a make command to build esp-open-sdk.
Step 2: Get Source Code, Configure, and Build
Now that esp-open-sdk is built, clone the project repository.
git clone https://github.com/someburner/esp-rtos-tests.git
Change into the project directory, create a .local folder, and copy the example settings.
cd esp-rtos-tests mkdir -p .local cp settings.example.mk .local/settings.mk
Now open up .local/settings.mk with any text editor and change the following settings:
- OPENSDK_ROOT: The absolute path for the location of esp-open-sdk you built in step 1
- WIFI_SSID: The SSID of your WiFi network
- WIFI_PASS: The password of your WiFi network
- PIXEL_COUNT: The number of pixels on your WS2812B LED strip
Note: Since this project uses SPI to drive the LEDs and uses the NodeMCU 3.3v to supply them, you probably will not be able to drive more than ~60 LEDs.
Note: The other settings do not need to be changed, but can be if desired. It is recommended to keep the order of the task priorities. The lower the priority number, the lower the priority of the task.
Now build the project:
make -C examples/cpe439
If everything is set up correctly, it should start compiling. At the end you should see:
Successfully created 'firmware/cpe439.bin'
Step 3: Connect Hardware Components
Now that the code is compiled, it's time to connect our peripherals.
First, stick the NodeMCU onto the breadboard, then use jumper wires to make the connections as shown in the diagram.
A couple things to be aware of:
- Important: The WS2812B data line is not bi-directional. If you look closely at the markings on the LED side of the strip, you should see little arrows pointing one direction. The output from D7 of the NodeMCU needs to be heading into the WS2812B the same way as the direction marker, which you can see in the diagram if you look closely.
- Depending on what sort of connectors your WS2812B comes with, you may need to do some modifications to make them connect securely into the breadboard. You can also use alligator clips to connect them to breadboard-able jumper cables.
- The MAX31820 pins have a smaller pitch and are thinner than standard 0.1"/2.54mm jumpers, making them tricky to connect. One way around this is to use female-to-male jumper wires, take off the plastic case from the female side, then use some pliers to crimp the female jumper ends tightly around the smaller MAX31820 pins.
Double-check the connections before powering the NodeMCU on so as not to damage the components.
Step 4: Flash and Run
With all the hardware connected, plug in your NodeMCU and flash with the following command:
make flash -C examples/cpe439 ESPPORT=/dev/ttyUSB0
/dev/ttyUSB0 is the serial com the NodeMCU should show up under. If you have other serial devices connected, it may show up as /dev/ttyUSB1 or some other number. To check you can run this command twice, once with NodeMCU unplugged, and once with it plugged in, and compare the difference:
Another issue you may encounter is not having permission to access the device. Two ways to fix this are:
Add your user to the dialout group:
sudo adduser $(whoami) dialout
chmod or chown the device:
sudo chmod 666 /dev/ttyUSB0The first method is preferred as it is a permanent solution.
sudo chown $(whoami):$(whoami) /dev/ttyUSB0
After running the flash command successfully, the device will immediately boot and begin running the compiled code. At any point after flashing you can run the following command to watch serial output:
python3 -m serial.tools.miniterm --eol CRLF --exit-char 003 /dev/ttyUSB0 500000 --raw -q
To save time you can add this to your ~/.bashrc file:
alias nodemcu='python3 -m serial.tools.miniterm --eol CRLF --exit-char 003 /dev/ttyUSB0 500000 --raw -q'
..which allows you to simply type "nodemcu" as an alias for that command.
If everything is configured correctly, your LED strip should light up green, and on serial you should see WiFi connect, get an IP address, connect to MQTT, and messages that temperature data is being pushed out.
connected with MyWiFiSSID, channel 1
dhcp client start...
wifi_task: status = 1
wifi_task: status = 1
Request temp OK
wifi_task: status = 5
mqtt_task: (Re)connecting to MQTT server test.mosquitto.org ...
xQueueSend ok done
Send MQTT connect ... MQTTv311
xQueueReceive +25.56 xQueueSend ok
Step 5: Interacting
Assuming your device has connected to WiFi and the MQTT broker succesfully, you'll be able to send and receive data from the NodeMCU with MQTT. If you haven't already, install the mosquitto clients package:
sudo apt-get install mosquitto-clients
You should now be able to use the mosquitto_pub and mosquitto_sub programs from your shell.
Receiving temperature updates
To receive temperature data we'll want to use the mosquitto_sub command to subscribe to the topic which the NodeMCU is publishing to.
mosquitto_sub -h test.mosquitto.org -t /cpe439/temp
You should see temperature data (in Celsius), arriving in the terminal.
Setting the LED strip color remotely
A simple message format is used to send RGB values to the NodeMCU over MQTT. The command format looks like this:
Where RRR, GGG, BBB correspond to RGB values (0-255) of the color you want to send. To send our command, we'll use the mosquitto_pub command. Here are some examples:
mosquitto_pub -h test.mosquitto.org -t /cpe439/rgb -m 'r:255g:0b:0~' # red
mosquitto_pub -h test.mosquitto.org -t /cpe439/rgb -m 'r:0g:255b:0~' # green
mosquitto_pub -h test.mosquitto.org -t /cpe439/rgb -m 'r:0g:0b:255~' # blue
If you want to get creative, find a color-picker online like this one, and edit the command with whatever RGB value you choose.
The topics in this project are set to /cpe439/rgb and /cpe439/temp on apublic MQTT broker, which means that there is nothing preventing someone else from publishing or subscribing to the same topics as you. For trying things out, using a public broker is fine, but for more serious projects you'll want to connect to a broker with password protection, or run your own broker on server.
Step 6: Implementation Details
The ESP8266 has only 1 core, so long, blocking tasks such as waiting 750ms for the temperature sensor to perform a temperature measurement would normally result in WiFi not functioning well, and maybe even a crash. In the FreeRTOS paradigm, you call vTaskDelay() to handle these long waits, but there are also many shorter waits required between reads and writes that are shorter than the FreeRTOS system tick, and thus cannot be avoided with vTaskDelay(). To also get around these, the onewire driver in this project was written to run off of a state-machine that is driven by the ESP8266's hardware timer, which can trigger events as low as every 10 micro-seconds, which happens to be the shortest required time between onewire read/write operations. Most other implementations use a blocking call to delay_us() or similar to handle this, but if you are constantly taking temperature updates, all those delays start to add up, resulting in a less responsive application. The source for this portion of code is located in the extras/onewire folder.
The ESP8266 does not have any standard hardware options for PWM fast enough to drive LED strips at 800KHz. To get around this, this project uses the SPI MOSI pin to drive the LEDs. By adjusting the clock rate of SPI, and changing the SPI payload around, you can achieve fairly reliable control of each individual LED. This method is not without it's flaws- for one the LEDs should be powered with a 5V source and a level shifter should be added to the output of the SPI pin. But 3.3V does work. Second, there are glitches that occur due to imperfect timing using the SPI method. And third is now you can't use SPI for anything else. Additional background on this method can be found here, and the source for this portion of code is located in the extras/ws2812 folder.
A more reliable method for driving LED strips is to use i2s. However this method has a lot of chip-specific hacks, so SPI seemed to be a better choice as a learning exercise.