Phần 5: Lập trình trực tiếp ESP8266 sử dụng thư viện SDK

Wearable

Chúng ta lại gặp nhau trong loạt bài viết về ESP8266. Trong các bài viết trước, chúng ta đã thảo luận mô hình ứng dụng ESP8266 sử dụng firmware có sẵn và giao tiếp bằng AT commands. Ưu điểm của giải pháp trên là thời gian phát triển nhanh vì chúng ta không cần lập trình cho ESP8266. Tuy nhiên, chúng ta chỉ có thể sử dụng các chức năng được cung cấp sẵn và không tận dụng được các chức năng phần cứng khác như PWM, UART, GPIO, v.v… của ESP8266. Trong bài viết này, chúng ta se thảo luận mô hình ứng dụng khác trong đó chúng ta sẽ phát triển 1 firmware mới cho ESP8266 để vừa đảm nhận giao tiếp Wifi, và đảm nhận vai trò điều khiển trung tâm cho thiết bị IoT mà không cần thêm 1 MCU khác trên board.

Chuẩn bị môi trường để lập trình ESP8266

Mỗi core MCU đều cần 1 bộ compiler/linker để chuyển đổi object code thành machine code trong các file firmware có thể thực thi được. Espressif cung cấp bộ compiler/linker cho ESP8266 trong môi trường Linux, chúng ta có thể cài chương trình máy tính ảo như VMWare và chạy file máy tính ảo trên Linux có cài đặt sẵn đầy đủ compiler và linker tạo firmware cho ESP8266. Đây là phương pháp lập trình được hỗ trợ bởi Espressif – nhà sản xuất ESP8266.

Tuy nhiên, đa số chúng ta thường quen làm việc trên Windows với giao diện người dùng thân thiện sẽ thấy khó làm quen việc sử dụng Linux để sử dụng bộ compiler/linker của Espressif. May mắn là cộng đồng phát triển của ESP8266 đã tạo ra 1 môi trường lập trình có thể chạy trên Windows dựa trên chương trình open-source là Eclipse. Đây sẽ là môi trườn lập trình được chọn giới thiệu và được sử dụng trong loạt bài viết này.

Các bước setup môi trường lập trình như sau:

1/ Đầu tiên là download và cài đặt Eclipse

Chúng ta sẽ sử dụng Eclipse Lura cho C/C++ phiên bản cho Windows. Link download ở đây

2/ Download bộ compiler/linker cho ESP8266 để tích hợp vào Eclipse

Các chương trình này được phát triển bởi 1 người Nga tên là Mikhail Grigoriev’s dựa trên bộ compiler/linker cung cấp bởi Espressif và thường được gọi là môi trường lập trình không chính thống (do không được cung cấp từ Espressif mà thôi 🙂 ). Link download ở đây

Khi cài đặt, chúng ta sẽ cài đặt vào đường dẫn mặc định là C:\Espressif nha vì 1 số chương trình con sẽ sử dụng đường dẫn này

3/ Download và cài đặt MinGW

MinGW chính là bộ chương trình phiên bản Windows của GCC, Bin Utils –  là các chương trình để biên dịch code C sang object code. Link download mingw-get-setup.exe ở đây

Chúng ta cũng cài đặt MinGW vào đường dẫn mặc định là C:\MinGW nha.

Sau khi cài đặt MinGW, chúng ta cần download các file thư viện gọi là Package trong MinGW cần thiết để lập trình ESP8266. Anh phát triển người Nga của chúng ta cũng chu đáo lắm vì đã giúp chuẩn bị 1 file .bat để thực hiện công việc này 1 cách tự động. Download file *.bat ở đây . Chúng ta sẽ mở CMD window để thực thi file bat này (thực thi ở đâu cũng được)

Thế là xong! Chúng ta có thể mở Eclipse và import sample project và test môi trường phát triển như hình sau:

Chọn File -> Import … -> General / Existing Projects into Workspace
Chọn sample project Hello world trong C:\Espressif\examples\ESP8266\hello_world

Eclipse_import   hello_world

Sau khi import, chúng ta sẽ có giao diện như sau trong Eclipse:

Eclipse_screen

Cửa sổ bên phải chứa các nút chức năng được lập trình sẵn để hỗ trợ thực hiện biên dịch và nạp xuống ESP8266. Ví dụ như “clean” để xóa các file đã biên dịch, “rebuild” để biên dịch lại, “flash” để thực hiện download code xuống ESP8266 qua UART, v.v…

Cửa sổ bên trái chứa thông tin các file thư viện và file source code của project. Chúng ta có thể thấy một số file header để biên dịch C code từ MinGW và thư viện SDK của ESP8266, và các file code chương trình bao gồm thư viện driver để lập trình phần cứng trên ESP8266 (uart.c, uart_register.h, uart.h) và file code ứng dụng user_main.c. Ngoài ra chúng ta cũng thấy 1 file tên là Makefile, đây chính là file để chạy các chương trình để thực hiện quá trình biên dịch, nạp code. Chúng ta sẽ thảo luận Makefile, cách viết file code trong môi trường Eclipse này trong bài viết tiếp theo.

Đến đây các bạn có thể build và nạp code xuống module ESP8266 rồi đấy. Chú ý là phải kết nối phần cứng khi nạp như đã giới thiệu trong các bài trước, và chọn COM port cũng như tốc độ baud trong file C:\Espressif\examples\ESP8266\setting.mk nha:

ESPPORT ?= COM3
ESPBAUD ?= 256000

4/ Cập nhật SDK mới từ Espressif 

Hiện tại anh người Nga phát triển bộ software cho ESP8266 dựa trên thư viện SDK phiên bản 1.0.1. Trong tương lai, nếu Espressif cung cấp phiên bản mới cho thư viện SDK, chúng ta có thể thay thế thư viện SDK hiên tại như sau:

  • Đổi tên C:\Espressif\ESP8266_SDK thành C:\Espressif\ESP8266_SDK_1.0.1
  • Download SDK phiên bản mới từ website của Espressif (esp_iot_sdk_v1.1.1*****.zip)  và giải nén ngay trong thư mục C:\Espressif\ESP8266_SDK
  • Di chuyển tất cả files, thư mục bên trong C:\Espressif\ESP8266_SDK\esp_iot_sdk_v1.1.1 ra ngoài C:\Espressif\ESP8266_SDK và xóa thư mục esp_iot_sdk_v1.1.1

Done! Các bạn có thể build và nạp các sample project khác trong C:\Espressif\examples\ESP8266 để test môi trường Eclipse.

Flow lập trình C cho ESP8266

Đến đây chúng ta đã chuẩn bị xong môi trường trên Eclipse để biên dịch và nạp firmware xuống module ESP8266. Công việc tiếp theo là bắt tay vào viết code cho ứng dụng tương tự như chúng ta đã làm cho các dòng chip khác như AVR, PIC, ARM v.v… Trong phần này chúng ta sẽ thảo luận flow lập trình cho ESP8266 sử dụng bộ thư viện SDK đã tích hợp trong Eclipse.

Thực tế ESP8266 sẽ chạy 1 chương trình mặc định đã được nạp sẵn trong ROM và chúng ta sẽ kết nối những function chức năng ứng dụng của mình vào flow chương trình mặc định này thông qua cơ chế callback từ các events hoặc timer timeout. Do đó, chúng ta sẽ không thấy hàm main() như khi lập trình các dòng MCU khác.

Chương trình của chúng ta sẽ bắt đầu từ hàm user_init(). Đây là hàm sẽ được gọi bởi chương trình cài đặt sẵn trong ESP8266. Đây có thể xem là hàm main() của chúng ta khi lập trình cho ESP8266.

Nhiệm vụ của hàm user_init() có thể bao gồm:

  • Khởi động các module peripheral cần sử dụng (như GPIO, PWM, Timer, v..v…)
  • Thiết lập ngắt để tạo các events và đăng ký hàm callback để xử lý
  • Cuối cùng có thể tạo 1 timer để gọi hàm xử lý kiểm tra và xử lý các chức năng theo thời gian

Đến đây, các bạn có thể thấy mô hình lập trình của ESP8266 tương tự như bên Arduino phải không nào. Hàm user_init() chính là hàm setup() bên Arduino và hàm loop() bên Arduino sẽ là hàm timer callback bên ESP8266.

Tóm lại, chúng ta sẽ hiện thực hàm user_init() để khởi động các phần cứng cần thiết và viết các hàm callback xử lý các events và timeout khi lập trình trực tiếp ESP8266. Flow lập trình cũng đơn giản phải không nào!

Sử dụng thư viện SDK khi lập trình

OK! Môi trường build với Eclipse xong, cách thức lập trình cũng đã thông. Giờ chỉ ngồi viết code cho user_init() và các hàm callback cho ứng dụng đúng không nào. Đây là lúc chúng ta sẽ tìm hiểu các function có sẵn trong thư viện SDK để gọi trong các hàm chúng ta cần viết. Tới đây những bạn đã có kinh nghiệm sử dụng bộ thư viện driverlib của TI hoặc STM32 sẽ thấy rất quen thuộc và dễ học để sử dụng vì bản chất đều như nhau.

Cấu trúc của bộ thư viện SDK

Trong các bài viết này, chúng ta sẽ tập trung vào bộ thư viện non-RTOS SDK vì nó đơn giản và dễ hiểu với đa số bạn đọc so với bộ thư viện RTOS SDK. Do đó khi đề cập thư viện SDK có nghĩa là chúng ta đang nói về non-RTOS SDK.

Cấu trúc và chức năng của các thư mục trong thư viện SDK được mô tả trong hình bên dưới:

SDK structure

Thư mục bin/ chứa các file *.bin cho firmware giao tiếp bằng AT command mà chúng ta đã thảo luận trong các bài trước. Chúng ta sẽ quan tâm các thư mục driver_lib/,  include/ và lib/ vì các thư  mục này chứa các file thư viện cần sử dụng khi lập trình cho ESP8266.

Có 2 loại thư viện trong SDK:

  • Thư viện cho các hàm để làm việc với phần cứng của ESP8266 như UART, Timer, I2C, SPI, v.v…. Espressif cung cấp các file source code cho các hàm thư viện này trong thư mục driver_lib/ trong đó thư mục driver_lib/driver/ chứa các file C code như uart.c, spi.c và thư mục driver_lib/include/ chứa các file header *.h
  • Thư viện cho các hàm thực hiện các chức năng software cần thiết khi kết nối Wifi như encryption, WPA, TCP/IP, v.v… Espressif không cung cấp source code mà cung cấp  các file lib *.a trong thư mục lib/ . Do đó chúng ta có thể thấy các file lib *.a như libssl.a, libwpa.a, libmesh.a, v.v… trong thư mục lib/ này

Đối với các hàm thư viện có file C code, khi cần sử dụng thì chúng ta chỉ việc thêm file tương ứng vào Eclipse như các file code ứng dụng bình thường khác. Đối với các file thư viện *.a, chúng ta thêm vào dự án bằng cách thay đổi nội dung của Makefile. Trong bài viết tiếp theo khi demo quá trình lập trình cho ESP8266, chúng ta sẽ thảo luận chi tiết quá trình thêm thư viện vào để build code ứng dụng

Để tìm hiểu về các hàm thư viện cần cho 1 ứng dụng, chúng ta có thể tham khảo tài liệu về tất cả hàm thư viện được cung cấp bởi Espressif.  Các bạn có thể download ESP8266 Non-OS SDK API Reference để đọc các hàm thư viện và cách sử dụng chúng. Ngoài ra, chúng ta có thể tham khảo các sample project trong thư mục C:\Espressif\examples\ESP8266\ để học cách sử dụng các hàm API cho các ứng dụng cụ thể 1 cách nhanh chóng.

Demo lập trình blink LED cho ESP8266

Trong phần này chúng ta sẽ giới thiệu 1 demo nhỏ để thấy được flow chương trình của ESP8266 và các hàm API của thư viện SDK được sử dụng thế nào. Demo này sẽ thiết lập GPIO để xử lý nút nhấn và blink LED.

Như đã giới thiệu, chúng ta luôn bắt đầu với hàm user_init() trong file user_main.c như sau:

void user_init(void)
{
system_init_done_cb(app_init);
}

system_init_done_cb() là hàm API để đăng ký hàm callback sau khi firmware có sẵn trong ESP8266 đã khởi động hệ thống. Ở đây chúng ta sẽ đăng ký hàm app_init() chính là hàm main() của ứng dụng.

Trong hàm app_init(), chúng ta sẽ sử dụng các hàm API để khởi động GPIO và Timer để blink LED như sau:

void app_init()
{
uart_div_modify(0, UART_CLK_FREQ / 115200);
os_delay_us(1000000);
os_printf(“\r\nHardware testing example\r\n”);
// Cấu hình wifi ngắt kết nối và không tự động connect
wifi_station_disconnect();
wifi_station_set_auto_connect(0);
//Thiết lập “gpio_int_handler” là hàm xử lý ngắt cho các chân GPIO
ETS_GPIO_INTR_ATTACH(gpio_int_handler, NULL);
//Vô hiệu hóa ngắt GPIO để việc cấu hình không ảnh hưởng
ETS_GPIO_INTR_DISABLE();
//Lựa chọn chức năng cho GPIO0 sử dụng In/Out Logic
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0);
//Cấu hình GPIO0 hoạt động như ngõ vào (Input)
gpio_output_set(0, 0, 0, GPIO_ID_PIN(BTN_PIN));
gpio_register_set(GPIO_PIN_ADDR(BTN_PIN), GPIO_PIN_INT_TYPE_SET(GPIO_PIN_INTR_DISABLE)
| GPIO_PIN_PAD_DRIVER_SET(GPIO_PAD_DRIVER_DISABLE)
| GPIO_PIN_SOURCE_SET(GPIO_AS_PIN_SOURCE));
// Xóa cờ ngắt cho nút nhấn
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, BIT(BTN_PIN));
// Enable ngắt cho nút nhấn với cạnh lên
gpio_pin_intr_state_set(GPIO_ID_PIN(BTN_PIN), GPIO_PIN_INTR_POSEDGE); //xem thêm tại include/gpio.h để biết các GPIO_INT_TYPE
//Enable ngắt GPIO
ETS_GPIO_INTR_ENABLE();
// Cấu hình chân cho Led
PIN_FUNC_SELECT(LED_GPIO_MUX, LED_GPIO_FUNC);
/* Cấu hình timer để blink led */
os_timer_disarm(&blink_timer);
os_timer_setfn(&blink_timer, (os_timer_func_t *)timer_handler, (void *)0); // Thiết lập callback cho timer
os_timer_arm(&blink_timer, blink_interval, 1); // Kích hoạt timer với mode repeat
}

Ở đây chúng ta thiết lập GPIO BTN_PIN (là GPIO0) là input, có ngắt cạnh lên và đăng ký hàm xử lý ngắt là gpio_int_handler(), thiết lập LED_PIN (GPIO2) là output để lái LED. Cuối cùng, chúng ta thiết lập 1 hàm timer timer_handler() để blink LED.

Trong hàm gpio_int_handler(), chúng ta sẽ thay đổi thời gian của timer_handler() để thay đổi tốc độ blink LED khi ấn nút như sau:

void gpio_int_handler(void *args)
{
// Đọc trang thái thanh ghi GPIO
uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
// Kiểm tra chân GPIO cho nút nhấn có ở mức cao
if (gpio_status & BIT(BTN_PIN))
{
// Thay đổi thời gian blink
if (blink_interval == FAST_DELAY)
{
blink_interval = LONG_DELAY;
os_printf(“\r\nMode blink chuyen sang long\r\n”);
}
else
{
blink_interval = FAST_DELAY;
os_printf(“\r\nMode blink chuyen sang fast\r\n”);
}
// Kích hoạt lại thời gian blink cho timer
os_timer_arm(&blink_timer, blink_interval, 1);
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, BIT(BTN_PIN)); //xóa cờ interrupt cho nút nhấn
}

Và trong hàm timer_handler(), chúng ta sẽ toogle GPIO2 chính là chân điều khiển LED trên board

void timer_handler(void *args)
{
led_value ^= 0x01; // đảo trạng thái
// Thay đổi trang thái của Led GPIO
GPIO_OUTPUT_SET(LED_PIN, led_value);
if (led_value)
{
os_printf(“\r\nLed da bat\r\n”);
}
else
{
os_printf(“\r\nLed da tat\r\n”);
}
}

Các bạn có thể download file user_main.c của demo ở đây . Các bạn có thể bỏ thay thế hàm user_main.c trong sample project helloworld để compile và nạp ứng dụng demo này xuống ESP8266

Trong demo này, chúng ta có thể thấy flow chương trình dựa trên event callback và timer callback khi lập trình ứng dụng trên ESP8266 sử dụng các hàm API của thư viện SDK. Phần lớn code chương trình là sử dụng các hàm thư viện API và các macro được định nghĩa trước để cấu hình các module phần cứng trong ESP8266 nên viết khá dễ dàng và nhanh chóng.

Bài viết đến đây là hết rồi! Chúng ta đã thảo luận cách setup môi trường Eclipse để lập trình cho ESP8266 trong Windows, bản chất flow chương trình của code ứng dụng trên ESP; cũng như thảo luận về cách sử dụng thư viện non-RTOS khi lập trình và demo với 1 ứng dụng nhỏ là đọc nút nhấn và blink LED. Trong bài viết tiếp theo, chúng ta sẽ đi sâu vào quá trình lập trình trong Eclipse, sử dụng các hàm thư viện API phổ biến và quan trọng khi lập trình ESP8266 cùng các demo thực tế.

Nguồn htelectronics.vn