[TH] ESP-IDF Ep.2 : Hello World!

บทความนี้ต่อเนื่องจากการติดตั้ง ESP-IDF เพื่อทดลองเขียนโค้ด คอมไพล์ และอัพโหลดเข้าบอร์ด ESP32 เพื่อให้ใจขั้นตอนของการพัฒนาโปรแกรมภาษา C ของบอร์ดไมโครคอนโทรลเลอร์ ESP32

ภาพที่ 1 บอร์ด esp32+OLED

เริ่มต้น

ให้เข้าไปที่ ~/esp-idf/examples/get-started/hello_world เพื่อใช้โค้ดตัวอย่างนี้ในการเริ่มต้นเขียนโปรแกรม การตรวจสอบการรองรับรุ่นของไมโครคอนโทรลเลอร์ของ ESP-IDF ที่ติดตั้งสามารถทำได้ด้วยการสั่งงานดังนี้

ภาพที่ 2 สั่งตรวจสอบรุ่นที่รองรับ

จากภาพที่ 2 จะพบว่า ESP-IDF ที่ติดตั้งนั้นรองรับไมโครคอนโทรลเลอร์ esp32, esp32s2 และ esp32c3 การเลือกเป้าหมายของการคอมไพล์โปรแกรมคือการกำหนด target ด้วยคำสั่งต่อไปนี้

idf.py set-target รุ่นของไมโครคอนโทรลเลอร์

การปรับแต่งคุณสมบัติของไมโครคอนโทรลเลอร์ต้องเข้าไปแก้ไขในหน้า menuconfig ด้วยคำสั่งต่อไปนี้ และจะได้หน้าจอดังภาพที่ 3 ในหน้าจอนี้ให้กำหนดเรื่องของขนาดหน่วยความจำต่าง ๆ ให้เรียบร้อย

idf.py menuconfig

ภาพที่ 3 หน้าจอ menuconfig

ให้แก้ไขโค้ดของ hello_world_main.c ใน ~/esp-idf/examples/get-started/hello_world/main เป็นดังนี้

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <math.h>

#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"

int counter = 0;
float t0, t1;

bool isPrimeNumber(uint16_t x) {
  uint16_t i;
  for (i = 2; i < x; i++) {
    if (x % i == 0) {
      return false;
    }
  }
  if (i == x)
    return true;
  return false;
}

void testPrimeNumber(uint16_t maxN) {
  t0 = esp_timer_get_time()/1000.0;
  for (uint16_t n = 2; n < maxN; n++) {
    if (isPrimeNumber(n)) {
      counter++;
    }
  }
  t1 = esp_timer_get_time()/1000.0;
}

void app_main(void)
{  
    testPrimeNumber(2000);
    printf("Found %d in %f milliseconds.\n", counter, fabs(t1-t0)); 
}

ขั้นตอนต่อไปคือการคอมไพล์ ให้กลับไปที่ ~/esp-idf/examples/get-started/hello_world แล้วสั่งดังนี้

idf.py build

ตัวอย่างของการสั่งงานเป็นดังนี้

$ idf.py build
Executing action: all (aliases: build)
Running ninja in directory /home/cid/esp-idf/examples/get-started/hello_world/build
Executing "ninja all"...
[0/1] Re-running CMake...
-- Building ESP-IDF components for target esp32
-- Project sdkconfig file /home/cid/esp-idf/examples/get-started/hello_world/sdkconfig
-- App "hello-world" version: v4.4-dev-1594-g1d7068e4b-dirty
-- Adding linker script /home/cid/esp-idf/components/esp_rom/esp32/ld/esp32.rom.ld
-- Adding linker script /home/cid/esp-idf/components/esp_rom/esp32/ld/esp32.rom.api.ld
-- Adding linker script /home/cid/esp-idf/components/esp_rom/esp32/ld/esp32.rom.libgcc.ld
-- Adding linker script /home/cid/esp-idf/components/esp_rom/esp32/ld/esp32.rom.newlib-data.ld
-- Adding linker script /home/cid/esp-idf/components/esp_rom/esp32/ld/esp32.rom.syscalls.ld
-- Adding linker script /home/cid/esp-idf/components/esp_rom/esp32/ld/esp32.rom.newlib-funcs.ld
-- Adding linker script /home/cid/esp-idf/components/esp_rom/esp32/ld/esp32.rom.newlib-time.ld
-- Adding linker script /home/cid/esp-idf/examples/get-started/hello_world/build/esp-idf/esp32/esp32_out.ld
-- Adding linker script /home/cid/esp-idf/components/esp32/ld/esp32.project.ld.in
-- Adding linker script /home/cid/esp-idf/components/esp32/ld/esp32.peripherals.ld
-- Components: app_trace app_update asio bootloader bootloader_support bt cbor cmock coap console cxx driver efuse esp-tls esp32 esp_adc_cal 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_ipc esp_lcd esp_local_ctrl esp_netif esp_phy esp_pm esp_ringbuf esp_rom esp_serial_slave_link esp_system esp_timer esp_websocket_client esp_wifi espcoredump esptool_py expat fatfs freemodbus freertos hal heap idf_test jsmn json libsodium log lwip main mbedtls mdns mqtt newlib nghttp nvs_flash openssl openthread partition_table perfmon protobuf-c protocomm pthread sdmmc soc spi_flash spiffs tcp_transport tcpip_adapter tinyusb ulp unity vfs wear_levelling wifi_provisioning wpa_supplicant xtensa
-- Component paths: /home/cid/esp-idf/components/app_trace /home/cid/esp-idf/components/app_update /home/cid/esp-idf/components/asio /home/cid/esp-idf/components/bootloader /home/cid/esp-idf/components/bootloader_support /home/cid/esp-idf/components/bt /home/cid/esp-idf/components/cbor /home/cid/esp-idf/components/cmock /home/cid/esp-idf/components/coap /home/cid/esp-idf/components/console /home/cid/esp-idf/components/cxx /home/cid/esp-idf/components/driver /home/cid/esp-idf/components/efuse /home/cid/esp-idf/components/esp-tls /home/cid/esp-idf/components/esp32 /home/cid/esp-idf/components/esp_adc_cal /home/cid/esp-idf/components/esp_common /home/cid/esp-idf/components/esp_eth /home/cid/esp-idf/components/esp_event /home/cid/esp-idf/components/esp_gdbstub /home/cid/esp-idf/components/esp_hid /home/cid/esp-idf/components/esp_http_client /home/cid/esp-idf/components/esp_http_server /home/cid/esp-idf/components/esp_https_ota /home/cid/esp-idf/components/esp_https_server /home/cid/esp-idf/components/esp_hw_support /home/cid/esp-idf/components/esp_ipc /home/cid/esp-idf/components/esp_lcd /home/cid/esp-idf/components/esp_local_ctrl /home/cid/esp-idf/components/esp_netif /home/cid/esp-idf/components/esp_phy /home/cid/esp-idf/components/esp_pm /home/cid/esp-idf/components/esp_ringbuf /home/cid/esp-idf/components/esp_rom /home/cid/esp-idf/components/esp_serial_slave_link /home/cid/esp-idf/components/esp_system /home/cid/esp-idf/components/esp_timer /home/cid/esp-idf/components/esp_websocket_client /home/cid/esp-idf/components/esp_wifi /home/cid/esp-idf/components/espcoredump /home/cid/esp-idf/components/esptool_py /home/cid/esp-idf/components/expat /home/cid/esp-idf/components/fatfs /home/cid/esp-idf/components/freemodbus /home/cid/esp-idf/components/freertos /home/cid/esp-idf/components/hal /home/cid/esp-idf/components/heap /home/cid/esp-idf/components/idf_test /home/cid/esp-idf/components/jsmn /home/cid/esp-idf/components/json /home/cid/esp-idf/components/libsodium /home/cid/esp-idf/components/log /home/cid/esp-idf/components/lwip /home/cid/esp-idf/examples/get-started/hello_world/main /home/cid/esp-idf/components/mbedtls /home/cid/esp-idf/components/mdns /home/cid/esp-idf/components/mqtt /home/cid/esp-idf/components/newlib /home/cid/esp-idf/components/nghttp /home/cid/esp-idf/components/nvs_flash /home/cid/esp-idf/components/openssl /home/cid/esp-idf/components/openthread /home/cid/esp-idf/components/partition_table /home/cid/esp-idf/components/perfmon /home/cid/esp-idf/components/protobuf-c /home/cid/esp-idf/components/protocomm /home/cid/esp-idf/components/pthread /home/cid/esp-idf/components/sdmmc /home/cid/esp-idf/components/soc /home/cid/esp-idf/components/spi_flash /home/cid/esp-idf/components/spiffs /home/cid/esp-idf/components/tcp_transport /home/cid/esp-idf/components/tcpip_adapter /home/cid/esp-idf/components/tinyusb /home/cid/esp-idf/components/ulp /home/cid/esp-idf/components/unity /home/cid/esp-idf/components/vfs /home/cid/esp-idf/components/wear_levelling /home/cid/esp-idf/components/wifi_provisioning /home/cid/esp-idf/components/wpa_supplicant /home/cid/esp-idf/components/xtensa
-- Configuring done
-- Generating done
-- Build files have been written to: /home/cid/esp-idf/examples/get-started/hello_world/build
[2/7] Performing build step for 'bootloader'
[0/1] Re-running CMake...
-- Building ESP-IDF components for target esp32
-- Project sdkconfig file /home/cid/esp-idf/examples/get-started/hello_world/sdkconfig
-- Adding linker script /home/cid/esp-idf/components/esp32/ld/esp32.peripherals.ld
-- Adding linker script /home/cid/esp-idf/components/esp_rom/esp32/ld/esp32.rom.ld
-- Adding linker script /home/cid/esp-idf/components/esp_rom/esp32/ld/esp32.rom.api.ld
-- Adding linker script /home/cid/esp-idf/components/esp_rom/esp32/ld/esp32.rom.libgcc.ld
-- Adding linker script /home/cid/esp-idf/components/esp_rom/esp32/ld/esp32.rom.newlib-funcs.ld
-- Adding linker script /home/cid/esp-idf/components/bootloader/subproject/main/ld/esp32/bootloader.ld
-- Adding linker script /home/cid/esp-idf/components/bootloader/subproject/main/ld/esp32/bootloader.rom.ld
-- Components: bootloader bootloader_support efuse esp32 esp_common esp_hw_support esp_rom esp_system esptool_py freertos hal log main micro-ecc newlib partition_table soc spi_flash xtensa
-- Component paths: /home/cid/esp-idf/components/bootloader /home/cid/esp-idf/components/bootloader_support /home/cid/esp-idf/components/efuse /home/cid/esp-idf/components/esp32 /home/cid/esp-idf/components/esp_common /home/cid/esp-idf/components/esp_hw_support /home/cid/esp-idf/components/esp_rom /home/cid/esp-idf/components/esp_system /home/cid/esp-idf/components/esptool_py /home/cid/esp-idf/components/freertos /home/cid/esp-idf/components/hal /home/cid/esp-idf/components/log /home/cid/esp-idf/components/bootloader/subproject/main /home/cid/esp-idf/components/bootloader/subproject/components/micro-ecc /home/cid/esp-idf/components/newlib /home/cid/esp-idf/components/partition_table /home/cid/esp-idf/components/soc /home/cid/esp-idf/components/spi_flash /home/cid/esp-idf/components/xtensa
-- Configuring done
-- Generating done
-- Build files have been written to: /home/cid/esp-idf/examples/get-started/hello_world/build/bootloader
[1/1] cd /home/cid/esp-idf/examples/get-started/hello_world/build/bootloader/esp-idf/esptool_py && /home/cid/.espressif/python_env/idf4.4_py3.8_env/bin/python /home/cid/esp-idf/components/partition_table/check_sizes.py --offset 0x8000 bootloader 0x1000 /home/cid/esp-idf/examples/get-started/hello_world/build/bootloader/bootloader.bin
Bootloader binary size 0x60e0 bytes. 0xf20 bytes (16%) free.
[4/5] Generating binary image from built executable
esptool.py v3.1-dev
Merged 2 ELF sections
Generated /home/cid/esp-idf/examples/get-started/hello_world/build/hello-world.bin
[5/5] cd /home/cid/esp-idf/examples/get-started/hello_world/bu...esp-idf/examples/get-started/hello_world/build/hello-world.bin
hello-world.bin binary size 0x280f0 bytes. Smallest app partition is 0x100000 bytes. 0xd7f10 bytes (84%) free.

Project build complete. To flash, run this command:
/home/cid/.espressif/python_env/idf4.4_py3.8_env/bin/python ../../../components/esptool_py/esptool/esptool.py -p (PORT) -b 460800 --before default_reset --after hard_reset --chip esp32  write_flash --flash_mode dio --flash_size detect --flash_freq 40m 0x1000 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0x10000 build/hello-world.bin
or run 'idf.py -p (PORT) flash'

เมื่อ build ผ่านให้สั่ง flash ลง esp32 โดยระบุพอร์ตของ USB ให้ถูกต้อง เช่น ของทีมงาน JarutEx พบพอร์ต USB ของ ESP32 อยู่ที่ /dev/ttyUSB0 จึงสั่งงานดังนี้ และได้ภาพผลลัพธ์ดังภาพที่ 4

idf.py -p /dev/ttyUSB0 flash

ภาพที่ 4 ตัวอย่างผลลัพธ์ของขั้นตอน flash

เมื่อรันโปรแกรมโดยดูจาก Serial Monitor ของ Arduino IDE จะแสดงผลลัพธ์ดังภาพที่ 5

ภาพที่ 5 ผลลัพธ์จากการทำงานของโปรแกรม

เนื่องจากค่าปกติของสัญญาณนาฬิกาที่กำหนดมาเป็น 160MHz แต่ esp32 รองรับได้สูงสุด 240MHz ดังนั้น ถ้าลองเปลี่ยนให้สูงขึ้นโดยเข้าไปปรับแก้ด้วย idf.py menuconfig เพื่อเข้าหน้าจอปรับแต่งดังภาพที่ 6 ให้เข้าไปยัง Component config จะเป็นดังภาพที่ 7 หลังจากนั้นเข้าที่ ESP32-specific เพื่อเลือกปรับความถี่สัญญาณนาฬิกาเป็น 240MHz ดังภาพที่ 8 สุดท้ายทำการบันทึกด้วยแป้น S จะรายงานดังภาพที่ 9 และออกจากโปรแกรมด้วยการกดแป้น Q ส่วนผลลัพธ์ของการทำงานเป็นดังภาพที่ 10

ภาพที่ 6 เลือก Component config
ภาพที่ 7 รายการเลือก ESP32-specific
ภาพที่ 8 รายการ CPU frequency ที่ต้องปรับเป็น 240MHz
ภาพที่ 9 หน้าจอยืนยันการบันทึกเมื่อกดแป้น S
ภาพที่ 10 ผลลัพธ์เมื่อตั้งค่าความถี่สัญญาณนาฬิกาเป็น 240MHz

กรณีที่ต้องการใช้ ESP-IDF เป็นเครื่องมือมอนิเตอร์ (Monitor) โดยไม่ต้องใช้โปรแกรมอื่น ๆ สามารถกระทำได้ด้วยคำสั่งต่อไปนี้ และออกจากโปรแกรมมอนิเตอร์ด้วยแป้น Ctrl+]

idf.py -p พอร์ตสื่อสาร monitor

เมื่อเปรียบเทียบกับโค้ดที่เขียนด้วยภาษาไพธอนของ Micropython จะได้ดังภาพที่ 11

ภาพที่ 11 ตัวอย่างการหา Prime Number ด้วยภาษาไพธอน

และเมื่อนำโค้ดจากบทความก่อนหน้านี้มาปรับเปลี่ยน Serial.begin( 115200 ); และอัพเข้าบอร์ด ESP32 จะได้ผลลัพธ์ของการทำงานเป็นดังภาพที่ 12

ภาพที่ 12 ผลลัพธ์ที่ได้จากการรันโค้ดที่เขียนจาก Arduino

และเมื่อเปลี่ยนเป็น testPrimeNumber(40000); จะได้ผลลัพธ์สำหรับบอร์ด ESP32 ดังภาพที่ 13 และ stm32f103c8 ในภาพที่ 14 และในภาพที่ 15 และ 16 เป็นผลลัพธ์เมื่อใช้กับ ESP-IDF ที่ใช้กับ esp32 และ esp32s2 ตามลำดับ

ภาพที่ 13 ผลลัพธ์ของ esp32 เมื่อทำงาน 40,000 รอบ
ภาพที่ 14 ผลลัพธ์ของ stm32f103 เมื่อทำงาน 40,000 รอบ
ภาพที่ 15 ผลลัพธ์ของ esp32 เมื่อทำงาน 40,000 รอบ บน ESP-IDF
ภาพที่ 16 ผลลัพธ์ของ esp32s2 เมื่อทำงาน 40,000 รอบ บน ESP-IDF

สรุป

จากบทความนี้จะพบว่า การเขียนโปรแกรมด้วยภาษา C ของ ESP-IDF นั้นมีความเร็วในการทำงานสูงกว่าภาษาไพธอนของ Micropython แต่เครื่องมือในการพัฒนาและขั้นตอนของการเขียนนั้นมีขั้นตอนมากกว่าและต้องใช้เครื่องมือหลายตัว ซึ่งข้อนี้ทาง espressive ได้ปรับปรุงด้วยการใช้ eclipse เป็น ide รวมการทำงานทั้งหมดให้ แต่ทำงานบนระบบปฏิบัติการ Windows ดังภาพที่ 17 ทำให้นักพัฒนาโปรแกรมใช้งานได้สะดวกยิ่งขึ้น นอกจากนี้เมื่อเปรียบเทียบการทำงานกับการเขียนผ่าน Arduino จะพบว่า ESP-IDF ทำงานได้เร็วกว่า แต่ยังมีประเด็นเรื่องคลังไลบรารีที่ Arduino นั้นมีให้ใช้หลากหลายกว่า หวังว่าบทความนี้คงพอให้เห็นทางเลือกสำหรับผู้ที่สนใจ และสุดท้ายขอให้สนุกกับการเขียนโปรแกรมครับ

ภาพที่ 17 ESP-IDF+Eclipse บน Windows

(C) 2020-2021, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-06-27, 2021-10-04