A few days ago, I received from AliExpress the amazing ESP32 chip. This microcontroller is a low-cost (i bough it for just $5 USD on a ready-to-use development kit, you can find the URL of the product at the end of the article), low-powered IoT solution with amazing specs:
- Built-in WiFi support
- Built-in Bluetooth and BLE support
- Xtensa® single-/dual-core 32-bit LX6 microprocessor
- 520KB SRAM!
- A lot of peripherals interfaces (34xGPIO, 4xSPI, 2xI2C, 3xUART, 2xI2S, etc.)
- Cryptographic hardware acceleration (AES, SHA-2, etc.)
With those specs, for that price, the ESP32 has no competition overall. So, for all your IoT projects, the ESP32 seems like a really solid product to use.
On this post, I’m going to show how to use the ESP32 on an OSX based computer using all the official tools and docs. To follow this tutorial you are going to need:
- An ESP32 DevKitC compatible board
- A USB to micro USB cable
- An OSX computer
- (Optional) A LED with a 320[Ohm] resistance
GETTING THE ESP32 DEVKIT
The ESP32 is a very affordable microcontroller and as today you can find a lot of kits available on Chinese stores. I got mine from a store on the AliExpress website:

The kit arrived in just 1-2 weeks without any problems. It has a lot of pins to test and the micro USB interface to program the micro works seamlessly.
INSTALLING THE TOOLCHAIN TO PROGRAM THE ESP32
To start programming our ESP32, we need to install the toolchain on our operating system. You can find the official documentation HERE, but you can follow the steps below to get the same results.
GETTING PIP
The toolchain of the ESP32 has some python dependencies. To install those dependencies, you can use pip. If you don’t have pip on your system, you can install it with the next command:
1 |
sudo easy_install pip |
GETTING THE TOOLCHAIN
Now we are ready to download the toolchain:
1 2 3 4 |
mkdir -p ~/esp cd ~/esp wget https://dl.espressif.com/dl/xtensa-esp32-elf-osx-1.22.0-80-g6c4433a-5.2.0.tar.gz tar -xzf xtensa-esp32-elf-osx-1.22.0-80-g6c4433a-5.2.0.tar.gz |
The above steps will download the toolchain and decompress it on the esp folder on your home directory. You will have a folder called xtensa-esp32-elf inside the ~/esp/ folder with the toolchain. To be able to use the toolchain on your command line, you will need to add the toolchain folder to your $PATH variable. So, add the next line to your ~/.profile file (I’m using zsh so I added the line below to my ~/.zshrc):
1 |
export PATH=$HOME/esp/xtensa-esp32-elf/bin:$PATH |
Now, reload your terminal and type:
1 |
xtensa-esp32-elf-gcc -v |
You should see something like:
1 2 3 4 5 6 7 |
Using built-in specs. COLLECT_GCC=xtensa-esp32-elf-gcc COLLECT_LTO_WRAPPER=/Users/.../esp/xtensa-esp32-elf/bin/../libexec/gcc/xtensa-esp32-elf/5.2.0/lto-wrapper Target: xtensa-esp32-elf Configured with: /Volumes/build/idf/crosstool-NG/.build/src/gcc-5.2.0/configure --build=x86_64-build_apple-darwin16.3.0 --host=x86_64-build_apple-darwin16.3.0 --target=xtensa-esp32-elf --prefix=/Volumes/build/idf/crosstool-NG/builds/xtensa-esp32-elf --with-local-prefix=/Volumes/build/idf/crosstool-NG/builds/xtensa-esp32-elf/xtensa-esp32-elf/sysroot --with-sysroot=/Volumes/build/idf/crosstool-NG/builds/xtensa-esp32-elf/xtensa-esp32-elf/sysroot --with-newlib --enable-threads=no --disable-shared --with-pkgversion='crosstool-NG crosstool-ng-1.22.0-80-g6c4433a' --disable-__cxa_atexit --enable-cxx-flags='-fno-rtti -ffunction-sections' --with-gmp=/Volumes/build/idf/crosstool-NG/.build/xtensa-esp32-elf/buildtools --with-mpfr=/Volumes/build/idf/crosstool-NG/.build/xtensa-esp32-elf/buildtools --with-mpc=/Volumes/build/idf/crosstool-NG/.build/xtensa-esp32-elf/buildtools --with-isl=/Volumes/build/idf/crosstool-NG/.build/xtensa-esp32-elf/buildtools --with-cloog=/Volumes/build/idf/crosstool-NG/.build/xtensa-esp32-elf/buildtools --with-libelf=/Volumes/build/idf/crosstool-NG/.build/xtensa-esp32-elf/buildtools --enable-lto --enable-target-optspace --without-long-double-128 --disable-libgomp --disable-libmudflap --disable-libssp --disable-libquadmath --disable-libquadmath-support --disable-nls --enable-languages=c,c++ --disable-libstdcxx-verbose --enable-threads=posix --enable-gcov-custom-rtio Thread model: posix gcc version 5.2.0 (crosstool-NG crosstool-ng-1.22.0-80-g6c4433a) |
which indicates the toolchain has been installed correctly. Now, let’s get the libraries to work with the ESP32.
GETTING API LIBRARIES
To be able to program the ESP32, you will need the libraries to interact with the interfaces and functionalities of the chip. To this, we use the ESP-IDF or Espressif IoT Development Framework. The installation follows as (you will need the “git” command before executing the next lines):
1 2 |
cd ~/esp git clone --recursive https://github.com/espressif/esp-idf.git |
This will install the IDF on the folder ~/esp/esp-idf, you also need to create a variable on your environment called IDF_PATH (on your ~/.profile file):
1 |
export IDF_PATH=~/esp/esp-idf |
Now restart your terminal and check your IDF_PATH variable:
1 2 |
echo $IDF_PATH > /Users/YOUR_USER/esp/esp-idf |
INSTALLING PYTHON REQUIREMENTS
Remember the pip command we installed at the beginning of the tutorial? Now is time to use it!
1 |
python -m pip install --user -r $IDF_PATH/requirements.txt |
The above command should install all python requirements to make the toolchain and IDF to work.
CONNECTING THE ESP32 TO OUR COMPUTER
To be able to program the ESP32, we need to connect the chip to our computer and the computer needs to recognize the chip as a serial port. For this, you need to install the corresponding drivers on your mac. In short, we need an USB to UART driver. You can get the corresponding driver from the next site:
https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers
Or more directly, the driver for OSX, from here:
https://www.silabs.com/documents/public/software/Mac_OSX_VCP_Driver.zip
Install the driver and restart your computer. Now, if you connect your ESP32 kit to your computer (using the USB interface), your computer should recognize the chip:
1 |
ls /dev/tty.* |
and you should see something like:
1 |
crw-rw-rw- 1 root wheel 21, 4 Nov 3 01:50 /dev/tty.SLAB_USBtoUART |
Now, we are good to go!
TESTING OUR INSTALLATION
Finally, we reached the end of the installation process. Now, to test it, we are going to create a simple “hello world” program. Luckily for us, the toolchain provide us a set of examples to test our chip, including a “hello world”. So, copy the hello world example to your ~/esp folder (you can choose whatever folder you want, but to keep things organized is better to use your base esp folder):
1 2 |
cd ~/esp cp -r $IDF_PATH/examples/get-started/hello_world . |
Now, we need to configure the project (to tell the toolchain which serial port use when programming the ESP32):
1 2 |
cd ~/esp/hello_world make menuconfig |
You should see something like this:
Select the “Serial flasher config” option and be sure the correct serial port is selected:
Then, press <Exit> and <Exit> again. Now, compile and flash the project:
1 |
make flash |
You should see something 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 |
Toolchain path: /Users/.../esp/xtensa-esp32-elf/bin/xtensa-esp32-elf-gcc Toolchain version: crosstool-ng-1.22.0-80-g6c4433a Compiler version: 5.2.0 Python requirements from /Users/.../esp/esp-idf/requirements.txt are satisfied. Flashing binaries to serial port /dev/tty.SLAB_USBtoUART (app at offset 0x10000)... esptool.py v2.5.1 Serial port /dev/tty.SLAB_USBtoUART Connecting........_____....._____....._____....._____....._____.....____ Chip is ESP32D0WDQ6 (revision 1) Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse MAC: cc:50:e3:80:28:40 Uploading stub... Running stub... Stub running... Configuring flash size... Auto-detected Flash size: 4MB Flash params set to 0x0220 Compressed 22800 bytes to 13502... Wrote 22800 bytes (13502 compressed) at 0x00001000 in 1.2 seconds (effective 152.7 kbit/s)... Hash of data verified. Compressed 151856 bytes to 73256... Wrote 151856 bytes (73256 compressed) at 0x00010000 in 6.5 seconds (effective 187.5 kbit/s)... Hash of data verified. Compressed 3072 bytes to 103... Wrote 3072 bytes (103 compressed) at 0x00008000 in 0.0 seconds (effective 1781.9 kbit/s)... Hash of data verified. Leaving... Hard resetting via RTS pin... |
Congratulations! you have flashed your first program to your ESP32. The ESP32 toolchain has a nice tool to monitor what’s going on in the chip (really useful to debug your programs):
1 |
make monitor |
and the output of our program:
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
Toolchain path: /Users/.../esp/xtensa-esp32-elf/bin/xtensa-esp32-elf-gcc Toolchain version: crosstool-ng-1.22.0-80-g6c4433a Compiler version: 5.2.0 Python requirements from /Users/.../esp/esp-idf/requirements.txt are satisfied. MONITOR --- WARNING: Serial ports accessed as /dev/tty.* will hang gdb if launched. --- Using /dev/cu.SLAB_USBtoUART instead... --- idf_monitor on /dev/cu.SLAB_USBtoUART 115200 --- --- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- ets Jun 8 2016 00:22:57 rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0xee clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:2 load:0x3fff0018,len:4 load:0x3fff001c,len:6132 load:0x40078000,len:10084 ho 0 tail 12 room 4 load:0x40080400,len:6476 entry 0x40080764 I (30) boot: ESP-IDF v3.2-dev-1250-g221eced06 2nd stage bootloader I (30) boot: compile time 00:41:54 I (30) boot: Enabling RNG early entropy source... I (36) boot: SPI Speed : 40MHz I (41) boot: SPI Mode : DIO I (45) boot: SPI Flash Size : 4MB I (49) boot: Partition Table: I (52) boot: ## Label Usage Type ST Offset Length I (60) boot: 0 nvs WiFi data 01 02 00009000 00006000 I (67) boot: 1 phy_init RF data 01 01 0000f000 00001000 I (74) boot: 2 factory factory app 00 00 00010000 00100000 I (82) boot: End of partition table I (86) esp_image: segment 0: paddr=0x00010020 vaddr=0x3f400020 size=0x078a0 ( 30880) map I (106) esp_image: segment 1: paddr=0x000178c8 vaddr=0x3ff80000 size=0x00000 ( 0) load I (106) esp_image: segment 2: paddr=0x000178d0 vaddr=0x3ff80000 size=0x00000 ( 0) load I (113) esp_image: segment 3: paddr=0x000178d8 vaddr=0x3ffb0000 size=0x02114 ( 8468) load I (125) esp_image: segment 4: paddr=0x000199f4 vaddr=0x3ffb2114 size=0x00000 ( 0) load I (131) esp_image: segment 5: paddr=0x000199fc vaddr=0x40080000 size=0x00400 ( 1024) load 0x40080000: _WindowOverflow4 at /Users/dacuna/esp/esp-idf/components/freertos/xtensa_vectors.S:1685 I (140) esp_image: segment 6: paddr=0x00019e04 vaddr=0x40080400 size=0x0620c ( 25100) load I (159) esp_image: segment 7: paddr=0x00020018 vaddr=0x400d0018 size=0x126b8 ( 75448) map 0x400d0018: _flash_cache_start at ??:? I (186) esp_image: segment 8: paddr=0x000326d8 vaddr=0x4008660c size=0x02a1c ( 10780) load 0x4008660c: get_prev_free_block at /Users/dacuna/esp/esp-idf/components/heap/multi_heap.c:399 I (191) esp_image: segment 9: paddr=0x000350fc vaddr=0x400c0000 size=0x00000 ( 0) load I (193) esp_image: segment 10: paddr=0x00035104 vaddr=0x50000000 size=0x00000 ( 0) load I (202) esp_image: segment 11: paddr=0x0003510c vaddr=0x50000000 size=0x00000 ( 0) load I (216) boot: Loaded app from partition at offset 0x10000 I (217) boot: Disabling RNG early entropy source... I (222) cpu_start: Pro cpu up. I (226) cpu_start: Starting app cpu, entry point is 0x40080e88 0x40080e88: call_start_cpu1 at /Users/dacuna/esp/esp-idf/components/esp32/cpu_start.c:234 I (0) cpu_start: App cpu up. I (236) heap_init: Initializing. RAM available for dynamic allocation: I (243) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM I (249) heap_init: At 3FFB3158 len 0002CEA8 (179 KiB): DRAM I (255) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM I (262) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM I (268) heap_init: At 40089028 len 00016FD8 (91 KiB): IRAM I (274) cpu_start: Pro cpu start user code I (292) cpu_start: Starting scheduler on PRO CPU. I (0) cpu_start: Starting scheduler on APP CPU. Hello world! This is ESP32 chip with 2 CPU cores, WiFi/BT/BLE, silicon revision 1, 4MB external flash Restarting in 10 seconds... Restarting in 9 seconds... Restarting in 8 seconds... Restarting in 7 seconds... Restarting in 6 seconds... Restarting in 5 seconds... Restarting in 4 seconds... Restarting in 3 seconds... Restarting in 2 seconds... Restarting in 1 seconds... Restarting in 0 seconds... Restarting now. |
In the end, you can see the “Hello world!” message and the countdown to restart the chip. That is what our “hello world” program does:
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 39 40 |
/* Hello World Example This example code is in the Public Domain (or CC0 licensed, at your option.) Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" #include "esp_spi_flash.h" void app_main() { printf("Hello world!\n"); /* Print chip information */ esp_chip_info_t chip_info; esp_chip_info(&chip_info); printf("This is ESP32 chip with %d CPU cores, WiFi%s%s, ", chip_info.cores, (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "", (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : ""); printf("silicon revision %d, ", chip_info.revision); printf("%dMB %s flash\n", spi_flash_get_chip_size() / (1024 * 1024), (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external"); for (int i = 10; i >= 0; i--) { printf("Restarting in %d seconds...\n", i); vTaskDelay(1000 / portTICK_PERIOD_MS); } printf("Restarting now.\n"); fflush(stdout); esp_restart(); } |
It just print “Hello word!” followed by some system information and then the countdown. Finally, it does a system restart using the “esp_restart()” function.
SUMMARY
With the simple steps described above, you can start programming the ESP32 using your OSX based computer. The ESP32 give us a lot of posibilities to experiment with IoT in a very cost-effective way. In the next posts, I will be exploring some of the API’s the IDF gives us to create more useful programs. Stay tuned!