Let’s talk about GPIO (part 2)

“The Doctor: Don’t blink. Don’t even blink. Blink and you’re dead. Don’t turn your back. Don’t look away. And don’t blink.”

Steven Moffat

One of the most basic uses of a GPIO pin is to connect to an LED, the ubiquitous Light Emitting Diode. This is so common that many boards actually come with an LED pre-connected onboard. For this example, we shall use the Sparkfun ESP32 Thing+. Whilst not the cheapest of ESP32 boards, this is a nice and particularly well documented board. We will use two of the GPIO pins on this board, one for input and one for output.

Firstly, let us examine the circuit diagram aka schematic of this board. From the schematic we may see that there is an LED connected to ESP32 Pin 13 labelled GPIO13 LED and a momentary contact switch attached to pin GPIO0 (GPIO Zero) .

If we remember from the previous article, then the current available from the GPIO PIN has an absolute maximum of 40mA. In order to safely drive an LED directly from the pin, we need a series resistor to limit the current. On the Thing+ these two components are R4 @ 1K, and D3 a blue diode. Although the specific type of LED is not defined in this schematic, common Surface Mount Device (SMD) blue LEDs have a forward operating voltage of 2.8v, leaving 0.5v across our 1K resistor for a drive current of 0.5mA which is well under the 20mA recommended current draw for a pin. Thus we may drive our LED directly from the pin rather than requiring additional external drive components like a transistor.

The LED equivalent of “Hello World” is “blink” and there is conveniently an example of that in the ESP-IDF examples. To test this example on our Sparkfun ESP32 Thing+ we first ensure that the ESP-IDF is configured correctly by following the installation instructions. Assuming Linux, which makes our environment simpler as the Linux Kernel already has the correct drivers to communicate with the ESP32, we may verify our ESP IDF installation:

jcrisp@embedded-dev:~$ idf.py --version
ESP-IDF v5.0

Now we should copy the example to a working directory

 cp -r tools/esp-idf/examples/get-started/blink/ workspace

Note: I like to have a directory under my home directory ~ called tools and one called workspace

Next, we need to configure the example for our board. This example supports two types of LED, GPIO or addressable. Since our board is using an LED directly connected to a GPIO, we shall choose that option and select the correct GPIO pin – 13 in the case of the ESP32 Thing+. First, let us tell the ESP IDF what ESP32 we’re targeting:

jcrisp@embedded-dev:~/workspace/blink$  idf.py set-target esp32
Adding "set-target"'s dependency "fullclean" to list of commands with default set of options.
Executing action: fullclean
Executing action: set-target
Set Target to: esp32, new sdkconfig created. Existing sdkconfig renamed to sdkconfig.old.
Running cmake in directory /home/jcrisp/workspace/blink/build
Executing "cmake -G 'Unix Makefiles' -DPYTHON_DEPS_CHECKED=1 -DESP_PLATFORM=1 -DIDF_TARGET=esp32 -DCCACHE_ENABLE=0 /home/jcrisp/workspace/blink"...
-- Found Git: /usr/bin/git (found version "2.37.2") 
-- The C compiler identification is GNU 11.2.0
-- The CXX compiler identification is GNU 11.2.0
-- The ASM compiler identification is GNU
-- Found assembler: /home/jcrisp/.espressif/tools/xtensa-esp32-elf/esp-2022r1-11.2.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-gcc
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /home/jcrisp/.espressif/tools/xtensa-esp32-elf/esp-2022r1-11.2.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /home/jcrisp/.espressif/tools/xtensa-esp32-elf/esp-2022r1-11.2.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Project is not inside a git repository, or git repository has no commits; will not use 'git describe' to determine PROJECT_VER.
-- Building ESP-IDF components for target esp32
Processing 2 dependencies:
[1/2] espressif/led_strip (2.3.0)
[2/2] idf (5.0.0)
-- Project sdkconfig file /home/jcrisp/workspace/blink/sdkconfig
-- Looking for sys/types.h
-- Looking for sys/types.h - found
-- Looking for stdint.h
-- Looking for stdint.h - found
-- Looking for stddef.h
-- Looking for stddef.h - found
-- Check size of time_t
-- Check size of time_t - done
-- Found Python3: /home/jcrisp/.espressif/python_env/idf5.0_py3.10_env/bin/python (found version "3.10.7") found components: Interpreter 
-- Performing Test C_COMPILER_SUPPORTS_WFORMAT_SIGNEDNESS
-- Performing Test C_COMPILER_SUPPORTS_WFORMAT_SIGNEDNESS - Success
-- App "blink" version: 1
-- Adding linker script /home/jcrisp/workspace/blink/build/esp-idf/esp_system/ld/memory.ld
-- Adding linker script /home/jcrisp/tools/esp-idf/components/esp_system/ld/esp32/sections.ld.in
-- Adding linker script /home/jcrisp/tools/esp-idf/components/esp_rom/esp32/ld/esp32.rom.ld
-- Adding linker script /home/jcrisp/tools/esp-idf/components/esp_rom/esp32/ld/esp32.rom.api.ld
-- Adding linker script /home/jcrisp/tools/esp-idf/components/esp_rom/esp32/ld/esp32.rom.libgcc.ld
-- Adding linker script /home/jcrisp/tools/esp-idf/components/esp_rom/esp32/ld/esp32.rom.newlib-data.ld
-- Adding linker script /home/jcrisp/tools/esp-idf/components/esp_rom/esp32/ld/esp32.rom.syscalls.ld
-- Adding linker script /home/jcrisp/tools/esp-idf/components/esp_rom/esp32/ld/esp32.rom.newlib-funcs.ld
-- Adding linker script /home/jcrisp/tools/esp-idf/components/soc/esp32/ld/esp32.peripherals.ld
-- Components: app_trace app_update bootloader bootloader_support bt cmock console cxx driver efuse esp-tls esp_adc esp_app_format esp_common esp_eth esp_event esp_gdbstub esp_hid esp_http_client esp_http_server esp_https_ota esp_https_server esp_hw_support esp_lcd esp_local_ctrl esp_netif esp_partition esp_phy esp_pm esp_psram esp_ringbuf esp_rom esp_system esp_timer esp_wifi espcoredump espressif__led_strip esptool_py fatfs freertos hal heap http_parser idf_test ieee802154 json log lwip main mbedtls mqtt newlib nvs_flash openthread partition_table perfmon protobuf-c protocomm pthread sdmmc soc spi_flash spiffs tcp_transport ulp unity usb vfs wear_levelling wifi_provisioning wpa_supplicant xtensa
-- Component paths: /home/jcrisp/tools/esp-idf/components/app_trace /home/jcrisp/tools/esp-idf/components/app_update /home/jcrisp/tools/esp-idf/components/bootloader /home/jcrisp/tools/esp-idf/components/bootloader_support /home/jcrisp/tools/esp-idf/components/bt /home/jcrisp/tools/esp-idf/components/cmock /home/jcrisp/tools/esp-idf/components/console /home/jcrisp/tools/esp-idf/components/cxx /home/jcrisp/tools/esp-idf/components/driver /home/jcrisp/tools/esp-idf/components/efuse /home/jcrisp/tools/esp-idf/components/esp-tls /home/jcrisp/tools/esp-idf/components/esp_adc /home/jcrisp/tools/esp-idf/components/esp_app_format /home/jcrisp/tools/esp-idf/components/esp_common /home/jcrisp/tools/esp-idf/components/esp_eth /home/jcrisp/tools/esp-idf/components/esp_event /home/jcrisp/tools/esp-idf/components/esp_gdbstub /home/jcrisp/tools/esp-idf/components/esp_hid /home/jcrisp/tools/esp-idf/components/esp_http_client /home/jcrisp/tools/esp-idf/components/esp_http_server /home/jcrisp/tools/esp-idf/components/esp_https_ota /home/jcrisp/tools/esp-idf/components/esp_https_server /home/jcrisp/tools/esp-idf/components/esp_hw_support /home/jcrisp/tools/esp-idf/components/esp_lcd /home/jcrisp/tools/esp-idf/components/esp_local_ctrl /home/jcrisp/tools/esp-idf/components/esp_netif /home/jcrisp/tools/esp-idf/components/esp_partition /home/jcrisp/tools/esp-idf/components/esp_phy /home/jcrisp/tools/esp-idf/components/esp_pm /home/jcrisp/tools/esp-idf/components/esp_psram /home/jcrisp/tools/esp-idf/components/esp_ringbuf /home/jcrisp/tools/esp-idf/components/esp_rom /home/jcrisp/tools/esp-idf/components/esp_system /home/jcrisp/tools/esp-idf/components/esp_timer /home/jcrisp/tools/esp-idf/components/esp_wifi /home/jcrisp/tools/esp-idf/components/espcoredump /home/jcrisp/workspace/blink/managed_components/espressif__led_strip /home/jcrisp/tools/esp-idf/components/esptool_py /home/jcrisp/tools/esp-idf/components/fatfs /home/jcrisp/tools/esp-idf/components/freertos /home/jcrisp/tools/esp-idf/components/hal /home/jcrisp/tools/esp-idf/components/heap /home/jcrisp/tools/esp-idf/components/http_parser /home/jcrisp/tools/esp-idf/components/idf_test /home/jcrisp/tools/esp-idf/components/ieee802154 /home/jcrisp/tools/esp-idf/components/json /home/jcrisp/tools/esp-idf/components/log /home/jcrisp/tools/esp-idf/components/lwip /home/jcrisp/workspace/blink/main /home/jcrisp/tools/esp-idf/components/mbedtls /home/jcrisp/tools/esp-idf/components/mqtt /home/jcrisp/tools/esp-idf/components/newlib /home/jcrisp/tools/esp-idf/components/nvs_flash /home/jcrisp/tools/esp-idf/components/openthread /home/jcrisp/tools/esp-idf/components/partition_table /home/jcrisp/tools/esp-idf/components/perfmon /home/jcrisp/tools/esp-idf/components/protobuf-c /home/jcrisp/tools/esp-idf/components/protocomm /home/jcrisp/tools/esp-idf/components/pthread /home/jcrisp/tools/esp-idf/components/sdmmc /home/jcrisp/tools/esp-idf/components/soc /home/jcrisp/tools/esp-idf/components/spi_flash /home/jcrisp/tools/esp-idf/components/spiffs /home/jcrisp/tools/esp-idf/components/tcp_transport /home/jcrisp/tools/esp-idf/components/ulp /home/jcrisp/tools/esp-idf/components/unity /home/jcrisp/tools/esp-idf/components/usb /home/jcrisp/tools/esp-idf/components/vfs /home/jcrisp/tools/esp-idf/components/wear_levelling /home/jcrisp/tools/esp-idf/components/wifi_provisioning /home/jcrisp/tools/esp-idf/components/wpa_supplicant /home/jcrisp/tools/esp-idf/components/xtensa
-- Configuring done
-- Generating done
-- Build files have been written to: /home/jcrisp/workspace/blink/build

Now the IDF is configured to know how to compile for and flash our chip. Next we can configure the example via menuconfig

idf.py menuconfig

Select the section Example Configuration and ensure that the fields Blink LED Type is set to GPIO and the Blink GPIO Number is set to the correct pin, 13 in our case. Save the configuration.

Now we may flash the blink code to the chip

jcrisp@embedded-dev:~/workspace/blink$ idf.py flash
Executing action: flash
Serial port /dev/ttyUSB0
Connecting....
Detecting chip type... Unsupported detection protocol, switching and trying again...
Connecting....
Detecting chip type... ESP32
Running make in directory /home/jcrisp/workspace/blink/build
Executing "make -j 18 flash"...
... bunch of compile output ...
[100%] Built target blink.elf
[100%] Built target gen_project_binary
blink.bin binary size 0x2d2c0 bytes. Smallest app partition is 0x100000 bytes. 0xd2d40 bytes (82%) free.
[100%] Built target app_check_size
[100%] Built target app
esptool esp32 -p /dev/ttyUSB0 -b 460800 --before=default_reset --after=hard_reset write_flash --flash_mode dio --flash_freq 40m --flash_size 2MB 0x1000 bootloader/bootloader.bin 0x10000 blink.bin 0x8000 partition_table/partition-table.bin
esptool.py v4.4
Serial port /dev/ttyUSB0
Connecting.........
Chip is ESP32-D0WD (revision v1.0)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: a4:e5:7c:c0:04:a0
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 460800
Changed.
Configuring flash size...
Flash will be erased from 0x00001000 to 0x00007fff...
Flash will be erased from 0x00010000 to 0x0003dfff...
Flash will be erased from 0x00008000 to 0x00008fff...
Compressed 26368 bytes to 16418...
Writing at 0x00001000... (50 %)
Writing at 0x000076e6... (100 %)
Wrote 26368 bytes (16418 compressed) at 0x00001000 in 0.7 seconds (effective 296.6 kbit/s)...
Hash of data verified.
Compressed 185024 bytes to 96446...
Writing at 0x00010000... (16 %)
Writing at 0x0001c09d... (33 %)
Writing at 0x00021914... (50 %)
Writing at 0x00027582... (66 %)
Writing at 0x0003002e... (83 %)
Writing at 0x00037df1... (100 %)
Wrote 185024 bytes (96446 compressed) at 0x00010000 in 2.7 seconds (effective 540.8 kbit/s)...
Hash of data verified.
Compressed 3072 bytes to 103...
Writing at 0x00008000... (100 %)
Wrote 3072 bytes (103 compressed) at 0x00008000 in 0.0 seconds (effective 551.1 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...
[100%] Built target flash
Done

Now you should see your LED flashing!

In this post we have covered basic output, next time we’ll look at momentary input!