In this post, we will be looking at getting an ESP32 working with NuttX and WIFI networking enabled, using a Mac as the development platform. The ESP hardware is a LOLIN32, but any dev-kit C ESP32 hardware will work fine.
The inspiration for this post is based on Sara Monteiro's nuttx+esp32 getting started article article, but adapted for MacOS and extended to support WIFI networking configuration.
Environment Setup
For the sake of simplicity, I will be using a environment variable to point of the folder used for this project. I am also using fish as the default shell.
export NUTTX_SPACE=(realpath ~/projects/iot/nuttxspace/)
Download NuttX
First step is to checkout the source NuttX code:
cd $NUTTX_SPACE
git clone https://bitbucket.org/nuttx/tools.git
git clone https://github.com/apache/incubator-nuttx.git nuttx
git clone https://github.com/apache/incubator-nuttx-apps.git apps
Build kconfig configuration tool.
cd $NUTTX_SPACE/tools/kconfig-frontends
./configure --enable-mconf
make
make install
Bootloader
mkdir {$NUTTX_SPACE}/esp-bins
curl -L "https://github.com/espressif/esp-nuttx-bootloader/releases/download/latest/bootloader-esp32.bin" -o $NUTTX_SPACE/esp-bins/bootloader-esp32.bin
curl -L "https://github.com/espressif/esp-nuttx-bootloader/releases/download/latest/partition-table-esp32.bin" -o $NUTTX_SPACE/esp-bins/partition-table-esp32.bin
Alternatively, building your own version of the boot-loader can be done quite easily, provided you have docker installed.
git clone https://github.com/espressif/esp-nuttx-bootloader.git {$NUTTX_SPACE}/esp-bootloader
docker run --rm -v {$NUTTX_SPACE}/esp-bootloader:/work -w /work espressif/idf:release-v4.3 ./build.sh
If all works fine, you should be able to see the built files in the out_ folder:
ls -la {$NUTTX_SPACE}/out/
drwxr-xr-x 8 ron staff 256 28 Feb 10:19 ./
drwxr-xr-x 14 ron staff 448 28 Feb 10:18 ../
-rw-r--r-- 1 ron staff 23824 28 Feb 10:17 bootloader-esp32.bin
-rw-r--r-- 1 ron staff 18528 28 Feb 10:19 bootloader-esp32c3.bin
-rw-r--r-- 1 ron staff 3072 28 Feb 10:17 partition-table-esp32.bin
-rw-r--r-- 1 ron staff 3072 28 Feb 10:19 partition-table-esp32c3.bin
-rw-r--r-- 1 ron staff 36748 28 Feb 10:17 sdkconfig-esp32
-rw-r--r-- 1 ron staff 33935 28 Feb 10:19 sdkconfig-esp32c3
ESP-IDF
Assuming that you have already installed the ESP-IDF, you should be able to
export IDF_PATH=(realpath ~/projects/iot/esp-idf/)
. $IDF_PATH/export.fish
Building the app
App Configuration
Generate the kernel/app configuration for the ESP32 platform.
cd {$NUTTX_SPACE}/nuttx
./tools/configure.sh esp32-devkitc:nsh
This will create the file .config
which contains all the necessary flags for ESP32. For example, this is the generated config for the devkit-C:
CONFIG_ARCH_XTENSA=y
CONFIG_ARCH="xtensa"
CONFIG_ARCH_CHIP="esp32"
CONFIG_ARCH_BOARD="esp32-devkitc"
CONFIG_ARCH_CHIP_ESP32=y
CONFIG_ARCH_FAMILY_LX6=y
CONFIG_XTENSA_CP_INITSET=0x0001
CONFIG_XTENSA_DUMPBT_ON_ASSERT=y
CONFIG_XTENSA_BTDEPTH=50
#
# ESP32 Configuration Options
#
CONFIG_ARCH_CHIP_ESP32WROVER=y
CONFIG_ESP32_DUAL_CPU=y
CONFIG_ESP32_FLASH_4M=y
CONFIG_ESP32_PSRAM_8M=y
CONFIG_ESP32_ESP32DXWDXX=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240
Build
make
If all goes fine, you should be able to see this at the end of the compilation
AR (create): libboard.a esp32_boot.o esp32_bringup.o esp32_appinit.o
LD: nuttx
CP: nuttx.hex
CP: nuttx.bin
MKIMAGE: ESP32 binary
esptool.py --chip esp32 elf2image --flash_mode dio --flash_size "4MB" -o nuttx.bin nuttx
esptool.py v3.1-dev
Generated: nuttx.bin (ESP32 compatible)
Flash
I am using a Wemos LOLIN32 1.0
make download ESPTOOL_PORT=/dev/cu.SLAB_USBtoUART ESPTOOL_BAUD=115200 ESPTOOL_BINDIR={$NUTTX_SPACE}/esp-bins
Connecting........_
Chip is ESP32-D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: b4:e6:2d:95:b1:05
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Compressed 23824 bytes to 14851...
Wrote 23824 bytes (14851 compressed) at 0x00001000 in 1.6 seconds (effective 118.2 kbit/s)...
Hash of data verified.
Compressed 3072 bytes to 69...
Wrote 3072 bytes (69 compressed) at 0x00008000 in 0.1 seconds (effective 454.4 kbit/s)...
Hash of data verified.
Compressed 124896 bytes to 52034...
Wrote 124896 bytes (52034 compressed) at 0x00010000 in 4.8 seconds (effective 207.1 kbit/s)...
Hash of data verified.
Leaving...
Hard resetting via RTS pin...
Connecting to NuttX shell
screen /dev/cu.SLAB_USBtoUART 115200
Just write help
and you'll should see this:
help usage: help [-v] [<cmd>]
. cd echo hexdump mh rm time xd
[ cp exec kill mount rmdir true
? cmp exit ls mv set uname
basename dirname false mb mw sleep umount
break dd free mkdir ps source unset
cat df help mkrd pwd test usleep
Builtin Apps:
nsh sh
nsh>
Voila, that's it for the basic config. Next step is to enable the WIFI connectivity.
Enabling WIFI Networking
NuttX uses the uIP networking stack, unlike ESP-IDF which uses LWiP.
Basic Network config
Enabling WIFI can be done by configuring the nuttx app:
make -C {$NUTTX_SPACE}/nuttx distclean
{$NUTTX_SPACE}/nuttx/tools/configure.sh esp32-devkitc:nsh
make -C {$NUTTX_SPACE}/nuttx menuconfig
Go to Networking Support
and enable it, as well as
* Networking Support: yes
* Link layer support
* Late driver initialization: yes
* System Type
* ESP32 Peripheral Selection
* Wireless: yes
* RTOS Features
* Work queue support
* Generic work notifier
* High priority (kernel) worker thread
* Pthread Options
* Enable mutex types
* Device Drivers
* Wireless Device Support
* IEEE 802.11 Device Support
To make it easier to debug, I also enabled the traces from:
* Build Setup
* Debug Options
* Enable Error Output
* Enable Debug Features
* Network Debug Features
* Wireless Debug Features
After flashing the, the following logs can be seen:
I (266) boot: Disabling RNG early entropy source...
esp32_rng_initialize: Initializing RNG
esp32_net_initialize: B4:E6:2D:95:B1:05
netdev_register: Registered MAC: b4:e6:2d:95:b1:05 as dev: wlan0
I (16) wifi:wifi driver task: 5, prio:253, stack:3584, core=0
I (18) wifi:wifi firmware version: 3cc2254
I (18) wifi:wifi certification version: v7.0
I (18) wifi:config NVS flash: disabled
I (22) wifi:config nano formating: disabled
I (26) wifi:Init data frame dynamic rx buffer num: 32
I (31) wifi:Init management frame dynamic rx buffer num: 32
I (36) wifi:Init management short buffer num: 32
I (40) wifi:Init dynamic tx buffer num: 32
I (44) wifi:Init static rx buffer size: 1600
I (48) wifi:Init static rx buffer num: 10
I (52) wifi:Init dynamic rx buffer num: 32
netdev_ifr_ioctl: cmd: 1794
tcp_callback: flags: 0040
netdev_ifr_ioctl: cmd: 1796
tcp_callback: flags: 0040
netdev_ifr_ioctl: cmd: 1800
tcp_callback: flags: 0040
netdev_ifr_ioctl: cmd: 1818
wlan_ifup: Bringing up: 10.0.0.2
tcp_callback: flags: 0040
NuttShell (NSH) NuttX-10.0.1
nsh> ifconfig
wlan0 Link encap:Ethernet HWaddr b4:e6:2d:95:b1:05 at UP
inet addr:10.0.0.2 DRaddr:10.0.0.1 Mask:255.255.255.0
Accessing WAPI
WAPI is a lightweight wrapper for iwconfig, wlanconfig, ifconfig, and route commands, and that's the command we need to use to scan the av available access points.
The first attempt resulted in a failure due to the missing ioctl
support.
nsh> wapi scan wlan0
netdev_ifr_ioctl: cmd: 35608
ioctl(SIOCSIWSCAN): 25
ERROR: Process command (scan) failed.
After enabling the Wireless IOCTL
* Networking support
* Network Device Operations
* Enable Wireless ioctl()
Unfortunately, at the time of writing (Feb. 2021), one can notice that from the esp32_wlan.c, the scan is not currently supported. So, the only possiblity is to connect manually:
wapi psk wlan0 access_point_password 0
wapi essid wlan0 access_point_ssid 0
From my access point running OpenWRT, I could notice that the ESP32 was connected, and was allocated an IP address. However, from the NSH CLI, the IP address remmained unchanged:
nsh> ifconfig
wlan0 Link encap:Ethernet HWaddr b4:e6:2d:95:b1:05 at UP
inet addr:10.0.0.2 DRaddr:10.0.0.1 Mask:255.255.255.0
Also, pinging the device from my Mac laptop to the IP address mentionned on the OpenWRT router, would result in frames beeing dropped (or unanswered) since uIP did not get the correct IP config.
nsh> wlan_rxpoll: ARP frame
arp_arpin: ARP request for IP da01a8c0
Enabling DHCP support
To enable the DHCP client:
* Networking Support
* UDP Networking
* UDP Networking
* UDP broadcast Rx support
* Socket Support
* Socket options
* Library Routines
* NETDB Support
* DNS Name resolution
* Application
* System Libraries and NSH Add-Ons
* DHCP Address Renewal (NEW)
* Wireless Libraries and NSH Add-Ons
* IEEE 802.11 Configuration Library
* IEEE 802.11 Command Line Tool
After that, and refering to this post on esp32.com, it is possible to enable the DHCP for the network initlization using:
* Application
* Network Utilities
* DHCP client
* Network initialization
* IP Address Configuration
* Use DHCP to get IP address
* Router IPv4 address: 0xc0a80101
It is quite weird to have to configure the Router IPv4 address, and further more having to do it using hexadecimal (0xc0a80101 in my case), but well, that's the only way to get things working.
Unfortunately, that was not enough. After trying to setup the SSID and passkey from WAPI, it always ended-up with errors:
NuttShell (NSH) NuttX-10.0.1
nsh> wapi psk wlan0 my-ap-password 1
netdev_ifr_ioctl: cmd: 35636
nsh> wapi essid wlan0 my-ap-ssid 1
netdev_ifr_ioctl: cmd: 35610
phy_version: 4500, 0cd6843, Sep 17 2020, 15:37:07, 0, 2
wifi_set_intr: cpu_no=0, intr_source=0, intr_num=0, intr_prio=1I (6220) wifi:mode : sta (b4:e6:2d:95:b1:05)
I (6221) wifi:enable tsf
esp_event_post: Event: base=WIFI_EVENT id=2 data=0 data_size=0 ticks=4294967295
I (6231) wifi:Set ps type: 0
I (6597) wifi:new:<3,0>, old:<1,0>, ap:<255,255>, sta:<3,0>, prof:1
I (7249) wifi:state: init -> auth (b0)
I (7256) wifi:state: auth -> assoc (0)
I (7262) wifi:state: assoc -> run (10)
I (7276) wifi:connected with my-ap-ssid, aid = 2, channel 3, BW20, bssid = ee:41:18:0c:53:dd
I (7277) wifi:security: WPA2-PSK, phy: bgn, rssi: -29
I (7278) wifi:pm start, type: 0
esp_event_post: Event: base=WIFI_EVENT id=4 data=0x3ffe5bb0 data_size=44 ticks=4294967295
nsh> I (7301) wifi:AP's beacon interval = 102400 us, DTIM period = 2
The only way to overcome this issue was to set the SSID and passkey directly in the menu config, under Application -> Network Utilities -> Network initialization -> WAPI Configuration (SSID / Passprhase)
. And fortunately, after that, it was possible to the the correct IP address:
nsh> ifconfig
wlan0 Link encap:Ethernet HWaddr b4:e6:2d:95:b1:05 at UP
inet addr:192.168.1.218 DRaddr:192.168.1.1 Mask:255.255.255.0
Pinging the Internet
Unfortunately, even after having the correct IP condiguration, PING would still not work, failing with socket address family unsupported
.
nsh> ping baidu.com
dns_recv_response: ID 36690
dns_recv_response: Query 128
dns_recv_response: Error 0
dns_recv_response: Num questions 1, answers 2, authrr 0, extrarr 0
dns_recv_response: Question: type=0001, class=0001
dns_parse_name: Compressed answer
dns_recv_response: Answer: type=0001, class=0001, ttl=0000bf, length=0004
dns_recv_response: IPv4 address: 220.181.38.148
dns_parse_name: Compressed answer
dns_recv_response: Answer: type=0001, class=0001, ttl=0000bf, length=0004
dns_recv_response: IPv4 address: 39.156.69.79
psock_socket: ERROR: socket address family unsupported: 2
socket: ERROR: psock_socket() failed: -106
The missing link was to enable IPPROTO_ICMP socket support
under Networking Support -> ICMP Networking Support
NuttShell (NSH) NuttX-10.0.1
nsh>
nsh> ping baidu.com
sendto_eventhandler: Send ICMP request
sendto_request: Outgoing ICMP packet length: 84 (84)
icmp_poll_eventhandler: flags: 0002
icmp_datahandler: Buffered 81 bytes
icmp_readahead: Received 64 bytes (of 81)
56 bytes from 39.156.69.79: icmp_seq=0 time=40 ms
Voila, finally, it's working :-)
Also, just in case, I could see some random failures during the ping: up_assert: Assertion failed at file:mm_heap/mm_free.c line: 170 task: ping
Conclusions
NuttX is definitely a promising solution - especially considering the eco-system that is forming arround it. However, at this stage the ESP32 support is quite limited. But the good news is that Espressif seems to be proactively adding support for their chip, so let's hope that within a few weeks WIFI - and other drivers - will be completely supported.