Lessons learnt from ESP NOW

For those of you who aren’t aware of ESP NOW, it’s a communication protocol developed by Espressif for their ESP modules.

What began as a simple “hook up my plant lights to HomeKit”, turned into a massive home grown firmware, but more about that in another post.

I’ve successfully setup a bunch of ESP 8266 modules to talk to one “hub” ESP 8266 module, which then talks to my WiFi network and exposes everything as HomeKit accessories.

While doing so, I learnt:

  • It’s not possible to go back to light sleep with GPIO interrupt enabled after sending a payload via ESP NOW.
  • ESP NOW really really likes WiFi channel 1. On the transmitter side, setting the channel via esp_now_add_peer() doesn’t seem to be a reliable way of having it transmit on the desired channel. u/cperiod on Reddit confirmed this. His/her solution was to spawn an AP temporarily to switch the channel: https://www.reddit.com/r/esp8266/comments/lj953m/poor_esp_now_range_if_no_line_of_sight/gnemjps/
  • Since the channel needs to be fixed, ensure that your hone WiFi network doesn’t jump. I configured my router to keep to channel 6 (the best for apartment) always.
  • ESP NOW payloads are usually delivered at their first attempt, but it doesn’t hurt to add an automatic retry feature in your firmware. For me, my low power motion sensors attempt up to ten times.
  • It’s incredibly efficient when it comes to range. Payloads were delivered across two walls in my apartment. No additional antenna was used, just the standard one on the Wemos D1 Mini. Somebody on Reddit claimed four walls in their apartment :)

That’s it! I’ll write a post about the entire setup + my home built plug and play firmware later.

Sending OTA updates over WiFi to your ESP8266

This Christmas, I added a whole bunch of lights powered by 5V power sources. My goal was to switch them on at sunset, and switch them off on sunrise, by using a MOSFET for power control :)

While I was doing this, I wanted to send OTA updates of my Lua files to the ESP8266 via WiFi. For some unknown reason, I couldn’t use luatool.py’s TCP update method.

So, I ended up building my very own OTA update protocol (which turned out to be fun!). To begin, add ota.lua to your project, and invoke it using dofile("ota.lua") in your init.lua:

Send OTA updates to remotely update lua scripts on your ESP8266.
LICENCE: http://opensource.org/licenses/MIT
Created by Jude Pereira <contact@judepereira.com>
See https://judepereira.com/blog/sending-ota-updates-over-wifi-to-your-esp8266/
srv = net.createServer(net.TCP)
current_file_name = nil
srv:listen(8080, function(conn)
conn:on("receive", function(sck, payload)
if string.sub(payload, 1, 5) == "BEGIN" then
current_file_name = string.sub(payload, 7)
file.open(current_file_name, "w")
file.close()
sck:send("NodeMCU: Writing to " .. current_file_name .. '\n')
elseif string.sub(payload, 1, 4) == "DONE" then
sck:send("NodeMCU: Wrote file " .. current_file_name .. "!\n")
current_file_name = nil
elseif string.sub(payload, 1, 7) == "RESTART" then
sck:send("NodeMCU: Restart!\n")
tmr.create():alarm(500, tmr.ALARM_SINGLE, node.restart)
else
if file.open(current_file_name, "a+") then
if file.write(payload) then
file.close()
sck:send("ok\n")
else
sck:send("NodeMCU: Write failed!\n")
end
else
sck:send("NodeMCU: Open failed!\n")
end
end
end)
conn:on("sent", function(sck) sck:close() end)
end)
view raw ota.lua hosted with ❤ by GitHub

Then, to use this shiny new TCP endpoint created on your ESP8266/NodeMCU, create a wrapper shell script:

#!/bin/bash
# Wrapper script for sending OTA updates to your ESP8266 running NodeMCU.
# See https://judepereira.com/blog/sending-ota-updates-over-wifi-to-your-esp8266/
HOST=192.168.178.25
PORT=8080
for i in "$@"; do
FILE=$i
echo "Sending $i"
echo -n "BEGIN $FILE" | nc $HOST $PORT
while read -r line; do
#echo -n "write: $line … "
if ! echo "$line" | nc $HOST $PORT | grep "ok" &>/dev/null; then
echo "Write failed! Please retry…"
exit 1
fi
done <"$FILE"
echo -n "DONE" | nc $HOST $PORT
done
echo -n "RESTART" | nc $HOST $PORT
view raw ota.sh hosted with ❤ by GitHub

Heads up! Replace HOST with the IP of your NodeMCU.

The wrapper script will automatically trigger a restart at the end. To use the wrapper script:

$ chmod +x ota.sh
$ ./ota.sh file1.lua file2.lua init.lua

And that’s it! OTA update away!

nRF52840 – CircuitPython 5.0.0 pinout

I recently got CircuitPython running on my SparkFun nRF52840 Pro Mini.

Compared to what SparkFun says the pin mappings should be, I found them to be quite different. Perhaps they changed with CircuitPython 5.0.0?

Here’s what the pin mapping looks like, when superimposed over SparkFun’s pinout diagram:

SparkFun Pro nRF52840 Mini pinout with CircuitPython superimposed

Sources:

nRF52840 – flashing the s340 v6.1.1 SoftDevice

This post is a work in progress (WIP). The result of this experiment is a success. I have flashed my SparkFun nRF52840 mini, and I’m able to run the bicycle combined speed & cadence sensor example.

Before we begin,  a big hats off to Charles, who brought support for the SparkFun board I have to the Adafruit nRF52 bootloader. Cheers Charles! I owe you a beer :) – GitHub profile, blog

Important software versions:

nRF SDK: nRF5_SDK_15.3.0_59ac345
ARM GCC: 8.2.1
s340: s340_nrf52_6.1.1
board: SparkFun Pro nRF52840 mini

Rough outline:

1. Checkout ‘s PR

2. Copy over src/linker/s140_v6.ld to src/linker/s340_v6.ld – there are zero differences between these two files

3. Patch your main.c from the checked out source to initialise the soft device with the ANT_LICENSE_KEY


diff –git a/src/main.c b/src/main.c
index 8ac1dba..2e43f49 100644
— a/src/main.c
+++ b/src/main.c
@@ -301,7 +301,7 @@ static uint32_t softdev_init(bool init_softdevice)
.accuracy = NRF_CLOCK_LF_ACCURACY_250_PPM
};
APP_ERROR_CHECK( sd_softdevice_enable(&clock_cfg, app_error_fault_handler) );
+ APP_ERROR_CHECK( sd_softdevice_enable(&clock_cfg, app_error_fault_handler, ANT_LICENSE_KEY) );
sd_nvic_EnableIRQ(SD_EVT_IRQn);
/*————- Configure BLE params ————-*/

view raw

main.diff

hosted with ❤ by GitHub

4. Patch the Makefile to use the s340 soft device files


diff –git a/Makefile b/Makefile
index 6dbaf98..4acd319 100644
— a/Makefile
+++ b/Makefile
@@ -104,7 +104,7 @@ ifneq ($(IS_52832),)
SD_NAME = s132
DFU_DEV_REV = 0xADAF
else
SD_NAME = s140
+SD_NAME = s340
DFU_DEV_REV = 52840
endif
@@ -275,7 +275,7 @@ CFLAGS += -DNRF52832_XXAA
CFLAGS += -DS132
else
CFLAGS += -DNRF52840_XXAA
CFLAGS += -DS140
+CFLAGS += -DS340
endif
@@ -314,7 +314,7 @@ ASMFLAGS += -DNRF52
ASMFLAGS += -DS132
else
ASMFLAGS += -DNRF52840_XXAA
ASMFLAGS += -DS140
+ASMFLAGS += -DS340
endif
C_SOURCE_FILE_NAMES = $(notdir $(C_SOURCE_FILES))

view raw

Makefile.diff

hosted with ❤ by GitHub

5. Place the contents of the s340 archive (sign up for the evaluation licence from thisisant.com, wait for 1 business day, and then download the s340 soft device)


$ tree lib/softdevice/s340_nrf52_6.1.1/
lib/softdevice/s340_nrf52_6.1.1/
├── s340_nrf52_6.1.1_API
│   └── include (all header files must be under here)
└── s340_nrf52_6.1.1_softdevice.hex

view raw

tree.sh

hosted with ❤ by GitHub

6. Flash your nRF52840 device (double reset to enter the DFU mode)

$ make BOARD=sparkfun_pro_nrf52840_mini SERIAL=/dev/tty.usbmodem14301 dfu-flash

7. Verify

When you enter DFU mode after the above command completes, the contents of INFO_UF2.TXT must look something like the contents here:


UF2 Bootloader 0.2.10-4-g79fe6cc-dirty lib/nrfx (v1.1.0-1-g096e770) lib/tinyusb (legacy-755-g55874813) s340 6.1.1
Model: SparkFun Pro nRF52840 Mini
Board-ID: SparkFun-Pro-nRF52840-Mini
Bootloader: s340 6.1.1
Date: Jul 12 2019

view raw

INFO_UF2.TXT

hosted with ❤ by GitHub

Very important – update your app’s linker script:

Since your board now runs the s340 soft device, update the FLASH and RAM values in your app’s linker script:


# diff diff ~/developer/em/nRF5_SDK_15.3.0_59ac345/examples/ant/ant_plus/ant_bsc/bsc_tx/pca10040/s212/armgcc/ant_bsc_tx_gcc_nrf52.ld ~/developer/em/bia/src/ant_bsc_tx_gcc_nrf52.ld
8,9c8,9
< FLASH (rx) : ORIGIN = 0x12000, LENGTH = 0x6e000
< RAM (rwx) : ORIGIN = 0x20000b80, LENGTH = 0xf480
> FLASH (rx) : ORIGIN = 0x00031000, LENGTH = 0x000F4000-0x00031000
> RAM (rwx) : ORIGIN = 0x20002000, LENGTH = 0xf480

view raw

a.diff

hosted with ❤ by GitHub

The new values are not black magic. They’re documented here: https://devzone.nordicsemi.com/nordic/short-range-guides/b/getting-started/posts/adjustment-of-ram-and-flash-memory

Resources:

  • RAM and FLASH addresses: https://devzone.nordicsemi.com/nordic/short-range-guides/b/getting-started/posts/adjustment-of-ram-and-flash-memory
  • MBR and boot loader info from Nordic: https://infocenter.nordicsemi.com/index.jsp?topic=%2Fsds_s132%2FSDS%2Fs1xx%2Fmbr_bootloader%2Fmbr_bootloader.html&cp=3_4_1_0_11
  • Reading boot loader settings: https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_settings_generate_display.html&cp=6_5_6
  • Usage of MBR params: https://devzone.nordicsemi.com/f/nordic-q-a/22329/mbr-params-page
  • Segger J-Link Mini: https://www.adafruit.com/product/3571

 

Random notes below, don’t follow any of it, or execute any commands from here on out. You’ve been warned.

FLASH and RAM for s340 6.1.1:

S340- 6.1.1

Min RAM start: 0x20002000

Flash start: 0x31000

 

Generate boot loader settings:

(nrfutil) h2:nrfutil jude$ nrfutil settings generate –family NRF52840 –softdevice ../nRF5_SDK_15.3.0_59ac345/components/softdevice/s112/hex/s112_nrf52_6.1.1_softdevice.hex –bootloader-version 1 –bl-settings-version 1 a.hex

 

$ git status


h2:Adafruit_nRF52_Bootloader jude$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>…" to update what will be committed)
(use "git checkout — <file>…" to discard changes in working directory)
modified: Makefile
modified: src/main.c
Untracked files:
(use "git add <file>…" to include in what will be committed)
lib/softdevice/s340_nrf52_6.1.1/
src/linker/s340_v6.ld
no changes added to commit (use "git add" and/or "git commit -a")
h2:Adafruit_nRF52_Bootloader jude$

view raw

a.txt

hosted with ❤ by GitHub

MPU 6050 and it’s DMP over time

Using Jeff’s brilliant library for using the DMP on the MPU 6050, here are graphs of the DMP filling the FIFO buffer at 200 Hz, 100 Hz, and 50 Hz.

At 200 Hz, I found that while the MPU did interrupt my Arduino Due at 200 Hz, I could only read off the FIFO at 150 Hz. It overflowed twice or thrice every second. To be sure, I used the examples from the library itself.

In the graphs below, red is yaw, green is pitch, and blue is roll (time in seconds is on the x-axis, and the values are on the y-axis).
The MPU was kept on steady on the floor at all times during this test.

FIFO rate at 200 Hz:

MPU 6050 at 200 Hz

It’s unbelievably unstable. The values randomly shoot to 50 – 70 and stay there for a while. Before the MPU gives away (it doesn’t fill the FIFO for over 100ms), it’s about 17 minutes.

FIFO rate at 100 Hz:

MPU 6050 at 100 Hz
The values look stable enough for use. It’s just 6 minutes before the MPU gives away this time.

FIFO rate at 50 Hz:

MPU 6050 at 50 Hz
At 50 Hz, the values are very good. They stabilise within 10 seconds. However, the MPU stops filling the FIFO buffer after about 36 minutes.

Does anybody know what’s wrong here?

Arduino Due – Absolute Maximum Ratings

The table below lists the maximum current which can be sourced or sinked for a given pin on an Arduino Due board:

Due Pin Number SAM3X Pin Name Mapped Pin Name IOH (or ISOURCE) in mA IOL (or ISINK) in mA
0 PA8 RX0 3 6
1 PA9 TX0 15 9
2 PB25 Digital Pin 2 3 6
3 PC28 Digital Pin 3 15 9
4 connected to both PA29 and PC26 Digital Pin 4 15 9
5 PC25 Digital Pin 5 15 9
6 PC24 Digital Pin 6 15 9
7 PC23 Digital Pin 7 15 9
8 PC22 Digital Pin 8 15 9
9 PC21 Digital Pin 9 15 9
10 connected to both PA28 and PC29 Digital Pin 10 15 9
11 PD7 Digital Pin 11 15 9
12 PD8 Digital Pin 12 15 9
13 PB27 Digital Pin 13 / Amber LED “L” 3 6
14 PD4 TX3 15 9
15 PD5 RX3 15 9
16 PA13 TX2 3 6
17 PA12 RX2 3 6
18 PA11 TX1 3 6
19 PA10 RX1 3 6
20 PB12 SDA 3 6
21 PB13 SCL 3 6
22 PB26 Digital Pin 22 3 6
23 PA14 Digital Pin 23 15 9
24 PA15 Digital Pin 24 15 9
25 PD0 Digital Pin 25 15 9
26 PD1 Digital pin 26 15 9
27 PD2 Digital Pin 27 15 9
28 PD3 Digital Pin 28 15 9
29 PD6 Digital Pin 29 15 9
30 PD9 Digital Pin 30 15 9
31 PA7 Digital Pin 31 15 9
32 PD10 Digital Pin 32 15 9
33 PC1 Digital Pin 33 15 9
34 PC2 Digital Pin 34 15 9
35 PC3 Digital Pin 35 15 9
36 PC4 Digital Pin 36 15 9
37 PC5 Digital Pin 37 15 9
38 PC6 Digital Pin 38 15 9
39 PC7 Digital Pin 39 15 9
40 PC8 Digital Pin 40 15 9
41 PC9 Digital Pin 41 15 9
42 PA19 Digital Pin 42 15 9
43 PA20 Digital Pin 43 3 6
44 PC19 Digital Pin 44 15 9
45 PC18 Digital Pin 45 15 9
46 PC17 Digital Pin 46 15 9
47 PC16 Digital Pin 47 15 9
48 PC15 Digital Pin 48 15 9
49 PC14 Digital Pin 49 15 9
50 PC13 Digital Pin 50 15 9
51 PC12 Digital Pin 51 15 9
52 PB21 Digital Pin 52 3 6
53 PB14 Digital Pin 53 15 9
54 PA16 Analog In 0 3 6
55 PA24 Analog In 1 3 6
56 PA23 Analog In 2 3 6
57 PA22 Analog In 3 3 6
58 PA6 Analog In 4 3 6
59 PA4 Analog In 5 3 6
60 PA3 Analog In 6 3 6
61 PA2 Analog In 7 3 6
62 PB17 Analog In 8 3 6
63 PB18 Analog In 9 3 6
64 PB19 Analog In 10 3 6
65 PB20 Analog In 11 3 6
66 PB15 DAC0 3 6
67 PB16 DAC1 3 6
68 PA1 CANRX 3 6
69 PA0 CANTX 15 9
70 PA17 SDA1 3 6
71 PA18 SCL2 15 9
72 PC30 LED “RX” 15 9
73 PA21 LED “TX” 3 6
74 PA25 (MISO) 15 9
75 PA26 (MOSI) 15 9
76 PA27 (SCLK) 15 9
77 PA28 (NPCS0) 15 9
78 PB23 (unconnected) 15 9
USB PB11 ID 15 9
USB PB10 VBOF 15 9

References:

  1. Arduino Due – Pin Mapping for SAM3X
  2. Arduino Due – Broad Specifications
  3. SAM3X datasheet

Note: The ratings are on pages 1390 through 1392 of the datasheet for the SAM3X.

Thanks to the folks at the Arduino IRC channel(#arduino on freenode) for pointing me in the right direction!