LWIP – Light weight IP là một bộ thư viện mã nguồn mở được thiết kế dành cho những hệ thống có tài nguyên tương đối hạn chế, phù hợp với các hệ thống nhúng. Hỗ trợ tương đối đầy đủ các giao thức mạng trên nền TCP/IP. Có thể hỗ trợ giao tiếp với vi điều khiển thông qua Serial hoặc Ethernet. TAPIT chia sẻ đến các bạn chuỗi bài viết hướng dẫn về lý thuyết thư viện, porting các function, kết nối MQTT, kết nối HTTP trên nền tảng LWIP và tối ưu chương trình trên vi điều khiển STM32.
Tại phần 1, mình sẽ giới thiệu đến các bạn bộ thư viện LWIP, hướng dẫn cách add thư viện, cách để port các file và port các function khi sử dụng LWIP.
Phần 1. Porting LWIP cho vi điều khiển STM32
1. Giới thiệu về LWIP
1.1 Mô hình kết nối giữa vi điều khiển và server
LWIP hỗ trợ các cách kết nối vật lí chính:
- Serial
- Ethernet
- Các chuẩn kết nối khác (WiFi…)
a. Sử dụng serial UART
Ở mô hình kết nối này, vi điều khiển sẽ kết nối với modem mạng qua chuẩn giao tiếp nối tiếp UART, phù hợp với nhiều dòng VDK khác nhau, tuy nhiên tốc độ kết nối phụ thuộc nhiều vào tốc độ baudrate của VDK.
b. Sử dụng Ethernet
Với mô hình kết nối này, LWIP sẽ sử dụng Ethernet để kết nối lên server, tốc độ cao hơn Serial. Tuy nhiên VDK cần phải hỗ trợ Phy Ethernet hoặc module chuyển đổi SPI-Ethernet. Bài viết sẽ tập trung vào cách porting LWIP qua chuẩn giao tiếp serial UART.
1.2 So sánh cách giao tiếp AT command và LWIP stack
Về cơ bản có 2 cách kết nối chính từ vi điều khiển lên cloud qua serial với module 4G như sau:
- Ở cách thứ 1 : vi điều khiển dựa hoàn toàn vào tập lệnh của nhà sản xuất module 4G để thực hiện kết nối internet.
- Ở cách thứ 2 : vi điều khiển dựa vào tập lênh của nhà sản xuất module 4G để khởi tạo, sau đó kết nối internet qua LWIP.
- So sánh ưu – nhược điểm của 2 phương pháp
Phương pháp | Network stack | Ưu điểm | Nhược điểm |
AT command (cách 1)
|
· Phần xử lí các application như TCP, UDP, HTTP… đều nằm trên module 4G.
· Chip vi điều khiển chỉ nhận lấy các kết quả cuối cùng của các giao thức bằng các tập lệnh AT-Command |
· Tiết kiệm tài nguyên cho vi điều khiển
· Các ứng dụng mạng phụ thuộc vào firmware của nhà sản xuất module |
· Khi thay đổi module thì cần phải thay đổi tập lệnh của các ứng dụng.
· Tiêu tốn ít tài nguyên của vi điều khiển.· Độ ổn định của firmware module 4G cần test lại với mỗi module, mỗi nhà sản xuất khác nhau. · Nhiều giao thức mạng bị hạn chế bởi module của nhà sản xuất, và phụ thuộc và tính năng firmware module của nhà sản xuất. |
LWIP stack (cách 2)
|
· Phần xử lí tầng application các giao thức mạng đều nằm hết trên vi điều khiển, module 4G không xử lí các giao thức mạng.
|
· Khả năng port code giữa các nền tảng tương đối dễ dàng.· Kế thừa được nhiều framework base trên LWIP.
· Khả năng “portable, reuseable” cao· Dễ dàng chuyển đổi module 4G giữa các nhà cung cấp khác nhau. |
· Tiêu tốn tài nguyên của vi điều khiển nhiều hơn so với dùng AT command và network stack của nhà sản xuất.
· Một số ứng dụng cần nhiều tài nguyên phần cứng (SSL..) thì đòi hỏi cấu hình RAM của vi điều khiển lớn. · Không chạy được đồng thời data mode và command mode. Phải chuyển qua lại giữa data mode và command mode. |
2. Porting
Trong bài viết này, mình sử dụng kit có sẵn cho thuận tiện, với tài nguyên như sau:
- Vi điều khiển STM32L083RZ, cấu hình CPU 32Mhz, 20KB RAM, 192KB Flash. Lưu ý các bạn có thể dùng MCU có flash size nhỏ hơn, nhưng theo khuyến nghị của mình là 64KB Flash.
- IDE : KeilC (MDK-ARM), optimize code level 0.
- Tool gen code STM32CubeMX.
Mình sẽ hướng dẫn các bạn khởi một Project và cách thêm thư viện LWIP vào Project.
2.1 Khởi tạo project
- Trong bài viết này mình sẽ sử dụng bộ giao động nội 32Mhz clock, USART với DMA. Tuy nhiên mình sẽ không đi quá chi tiết vào cách thức khởi tạo và code driver.
- Pinout cơ bản.
- Clock cơ bản.
2.2 Thêm thư viện LWIP vào project
- Download source code của LWIP ở đường dẫn http://download.savannah.nongnu.org/releases/lwip/, phiên bản mình sử dụng là 2.1.2
- Giải nén vào project.
- Cấu trúc folder cần import như sau.
- Folder “porting” là phần lập trình viên cần code cho nền tảng Vi điều khiển STM32.
- Folder “app” chứa các thư viện liên quan đến tầng ứng dụng của LWIP như HTTP, MQTT, MDNS…
- Folder “core” và “ipv4” chứa thư viện lõi của LWIP.
- Đường dẫn đến header file cần include vào project.
- Chi tiết các file cần import vào project.
2.3 Port các file cần thiết cho nền tảng STM32
a. lwipopts.h
lwipopts.h chứa toàn bộ các cấu hình của LWIP stack.
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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
#ifndef __LWIPOPTS_H__ #define __LWIPOPTS_H__ #include <stdint.h> #include "app_debug.h" #define NO_SYS 1 #define NO_SYS_NO_TIMERS 0 //#define MEM_LIBC_MALLOC 1 //#define MEMP_MEM_MALLOC 1 #define MEM_ALIGNMENT 4 #define MEM_SIZE (1024) #define PBUF_POOL_SIZE 6 #define LWIP_ARP 0 #define IP_REASS_MAX_PBUFS 0 #define IP_FRAG_USES_STATIC_BUF 0 #define MEMP_NUM_RAW_PCB 0 #define MEMP_NUM_UDP_PCB 2 #define MEMP_NUM_TCP_PCB 1 #define MEMP_NUM_TCP_PCB_LISTEN 1 #define MEMP_NUM_TCP_SEG 8 #define MEMP_NUM_FRAG_PBUF 0 #define MEMP_NUM_TCPIP_MSG_API 0 #define MEMP_NUM_TCPIP_MSG_INPKT 0 #define MEMP_NUM_SNMP_NODE 0 #define MEMP_NUM_SNMP_ROOTNODE 0 #define MEMP_NUM_SNMP_VARBIND 0 #define MEMP_NUM_SNMP_VALUE 0 #define IP_DEFAULT_TTL 255 #define IP_SOF_BROADCAST 0 #define IP_SOF_BROADCAST_RECV 0 #define LWIP_ICMP 1 #define LWIP_BROADCAST_PING 0 #define LWIP_MULTICAST_PING 0 #define LWIP_RAW 0 #define TCP_LISTEN_BACKLOG 0 #define LWIP_NETIF_STATUS_CALLBACK 1 #define LWIP_NETIF_LINK_CALLBACK 1 #define LWIP_NETIF_HWADDRHINT 1 #define LWIP_NETCONN !NO_SYS #define LWIP_SOCKET !NO_SYS #define LWIP_STATS_DISPLAY 0 #define MEM_STATS 0 #define SYS_STATS 0 #define MEMP_STATS 0 #define LINK_STATS 0 #define ETHARP_TRUST_IP_MAC 0 #define ETH_PAD_SIZE 2 #define LWIP_CHKSUM_ALGORITHM 2 #define LWIP_CHECKSUM_ON_COPY 1 #define LWIP_ND6_MAX_MULTICAST_SOLICIT 1 #define LWIP_ND6_MAX_NEIGHBOR_ADVERTISEMENT 1 #define LWIP_ND6_RETRANS_TIMER 20000 #define LWIP_ND6_QUEUEING 0 #define LWIP_ND6_NUM_ROUTERS 0 #define LWIP_ND6_DELAY_FIRST_PROBE_TIME 10000 #define LWIP_TCP_KEEPALIVE 1 #define LWIP_ARP 0 #define ARP_TABLE_SIZE 0 #define IP_REASSEMBLY 0 #define IP_FRAG 0 #define LWIP_TCP 1 #define LWIP_IPV6 0 #define LWIP_IPV4 1 #define LWIP_ICMP6 0 #define LWIP_IPV6_REASS 0 #define LWIP_ND6_TCP_REACHABILITY_HINTS 0 #define LWIP_IPV6_MLD 0 #define LWIP_STATS 0 #define PPP_IPV6_SUPPORT 0 #define LWIP_PPP_API 0 #define PPP_SUPPORT 1 #define LWIP_DNS 1 #define LWIP_SUPPORT_CUSTOM_PBUF 1 #define LWIP_BTLE_6LOWPAN 0 #define PPP_NOTIFY_PHASE 1 #define TCP_TMR_INTERVAL 500 #define LWIP_CALLBACK_API 1 // Keepalive values, compliant with RFC 1122. Don't change this unless you know what you're doing #define TCP_KEEPIDLE_DEFAULT 10000UL // Default KEEPALIVE timer in milliseconds #define TCP_KEEPINTVL_DEFAULT 2000UL // Default Time between KEEPALIVE probes in milliseconds #define TCP_KEEPCNT_DEFAULT 9U // Default Counter for KEEPALIVE probes #define TCP_MSS 536 #define TCP_WND (2 * TCP_MSS) #define TCP_SND_BUF (2 * TCP_MSS) #define DNS_TABLE_SIZE 2 #define DNS_MAX_NAME_LENGTH 128 #define SO_REUSE 1 //#define LWIP_NOASSERT 1 //#define mem_init(...) //#define mem_free(p) nrf_free((p)) //#define mem_malloc(sz) nrf_malloc((sz)) //#define mem_trim(p,sz) nrf_realloc((p),(sz)) #define SNTP_SERVER_DNS 1 #define LWIP_DEBUG LWIP_DBG_OFF #define ETHARP_DEBUG LWIP_DBG_OFF #define NETIF_DEBUG LWIP_DBG_OFF #define PBUF_DEBUG LWIP_DBG_OFF #define API_LIB_DEBUG LWIP_DBG_OFF #define API_MSG_DEBUG LWIP_DBG_OFF #define SOCKETS_DEBUG LWIP_DBG_OFF #define ICMP_DEBUG LWIP_DBG_OFF #define INET_DEBUG LWIP_DBG_OFF #define IP_DEBUG LWIP_DBG_OFF #define IP_REASS_DEBUG LWIP_DBG_OFF #define RAW_DEBUG LWIP_DBG_OFF #define MEM_DEBUG LWIP_DBG_OFF #define MEMP_DEBUG LWIP_DBG_OFF #define SYS_DEBUG LWIP_DBG_OFF #define TCP_DEBUG LWIP_DBG_OFF #define TCP_INPUT_DEBUG LWIP_DBG_OFF #define TCP_OUTPUT_DEBUG LWIP_DBG_OFF #define TCP_RTO_DEBUG LWIP_DBG_OFF #define TCP_CWND_DEBUG LWIP_DBG_OFF #define TCP_WND_DEBUG LWIP_DBG_OFF #define TCP_FR_DEBUG LWIP_DBG_OFF #define TCP_QLEN_DEBUG LWIP_DBG_OFF #define TCP_RST_DEBUG LWIP_DBG_OFF #define UDP_DEBUG LWIP_DBG_OFF #define TCPIP_DEBUG LWIP_DBG_OFF #define PPP_DEBUG LWIP_DBG_OFF #define SLIP_DEBUG LWIP_DBG_OFF #define DHCP_DEBUG LWIP_DBG_OFF #define SYS_LIGHTWEIGHT_PROT 0 #define HTTPC_DEBUG LWIP_DBG_OFF #define HTTPC_DEBUG_REQUEST LWIP_DBG_OFF #define HTTPC_CLIENT_AGENT "huytv" extern void lwip_sntp_recv_callback(uint32_t sec); #define SNTP_SET_SYSTEM_TIME lwip_sntp_recv_callback #endif /* __LWIPOPTS_H__ */ |
b. sys_timer.c
Có nhiệm vụ cung cấp các timer để định thời, timeout cho internal timer của LWIP. Trong đó bắt buộc cần có 2 hàm trả về thời gian hiện tại của hệ thống là “sys_now” và “sys_jiffies” với đơn vị millisecond, các bạn có thể thay thế hàm “sys_get_tick_ms” bằng hàm “HAL_GetTick” của STM32.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/** * @brief Override LWIP weak function */ uint32_t sys_now(void) { return sys_get_tick_ms(); } /** * @brief Override LWIP weak function */ uint32_t sys_jiffies(void) { return sys_get_tick_ms(); } uint32_t sys_get_tick_ms(void) { return HAL_GetTick(); } |
c. cc.h và cpu.h
Chứa các định nghĩa về kiến trúc MCU đang sử dụng.
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 |
#ifndef __CC_H__ #define __CC_H__ #include "cpu.h" #include <stdlib.h> #include <stdio.h> #include "app_debug.h" typedef int32_t sys_prot_t; #define LWIP_PROVIDE_ERRNO #if defined (__GNUC__) & !defined (__CC_ARM) #define LWIP_TIMEVAL_PRIVATE 0 #include <sys/time.h> #endif /* define compiler specific symbols */ #if defined (__ICCARM__) #define PACK_STRUCT_BEGIN #define PACK_STRUCT_STRUCT #define PACK_STRUCT_END #define PACK_STRUCT_FIELD(x) x #define PACK_STRUCT_USE_INCLUDES #elif defined (__GNUC__) #define PACK_STRUCT_BEGIN #define PACK_STRUCT_STRUCT __attribute__ ((__packed__)) #define PACK_STRUCT_END #define PACK_STRUCT_FIELD(x) x #elif defined (__CC_ARM) #define PACK_STRUCT_BEGIN __packed #define PACK_STRUCT_STRUCT #define PACK_STRUCT_END #define PACK_STRUCT_FIELD(x) x #elif defined (__TASKING__) #define PACK_STRUCT_BEGIN #define PACK_STRUCT_STRUCT #define PACK_STRUCT_END #define PACK_STRUCT_FIELD(x) x #endif #ifndef LWIP_PLATFORM_ASSERT #define LWIP_PLATFORM_ASSERT(x) do {DEBUG_RAW("Assertion \"%s\" failed at line %d in %s\r\n", \ x, __LINE__, __FILE__); } while(0) #define LWIP_PLATFORM_DIAG(x) do {DEBUG_RAW x;} while(0) #endif /* LWIP_PLATFORM_ASSERT */ /* Define random number generator function */ extern uint32_t sys_rand(void); #define LWIP_RAND() ((u32_t)sys_rand()) #endif /* __CC_H__ */ |
2.4 Porting các function cần thiết
a. sys_rand
Dùng để ramdom 1 số uint32_t, để cho đơn giản mình sẽ lấy systick value của vi điều khiển lỗi ARM
1 2 3 4 |
uint32_t sys_rand() { return SysTick->VAL; } |
b. Các hàm liên quan đến việc log dữ liệu debug
Để phục vụ in dữ liệu qua màn hình, các bạn có thể retarget printf qua UART, tuy nhiên ở ví dụ này, mình sử dụng bộ thư việc “Segger_RTT”, ưu điểm của bộ thư viện này là chỉ cần 2 dây debug “SWD” để in dữ liệu. Các bạn có thể tìm hiểu thêm về bộ thư viện này ở đây “https://www.segger.com/products/debug-probes/j-link/technology/about-real-time-transfer/”, tuy nhiên ở bài viết này mình sẽ không đề cập chi tiết.
c. Hàm gửi dữ liệu qua cổng UART từ LWIP đến module 4G
“ppp_output_callback” gửi data qua cổng “USART1”, với 2 tham số cơ bản là “data” và “len”, lần lượt là dữ liệu và độ dài dữ liệu LWIP muốn đẩy tới module 4G.
d. gsm_hw_pppos_polling và sio_read
- Định kì nhận dữ liệu từ cổng UART của module sim, truyền vào LWIP stack. Đồng thời polling các timer của LWIP, hàm này cần gọi liên tục vô hạn trong vòng lặp while(1) của hệ thống
3 |
return gsm_hardware_layer_copy_ppp_buffer(data, len); |
- Khi chuyển sang chế độ PPP mode, mọi dữ liệu từ cổng UART sẽ chuyển vào PPP buffer, chúng ta cần phân biệt uart rx data đó dành cho AT command hay PPP stack, ý tưởng của mình đơn giản như sau:
e. Hàm ngắt nhận dữ liệu UART
Hàm nhận dữ lịêu ngắt uart rồi lưu vào PPP buffer, hoặc AT command buffer. Đây là 1 cách implement đơn giản của mình.
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 |
uint32_t prev_index = 0; // Ham ngat nhan du lieu tu cong UART void gsm_hw_layer_uart_fill_rx(uint8_t *data, uint32_t length) { if (length) { m_new_uart_data = true; // If device in is data mode =>> bypass data into PPP stack if (gsm_is_in_ppp_mode()) { for (uint32_t i = 0; i < length; i++) { m_gsm_modem_buffer.buffer[m_gsm_modem_buffer.idx_in++] = data[i]; if (m_gsm_modem_buffer.idx_in >= GSM_PPP_MODEM_BUFFER_SIZE) { m_gsm_modem_buffer.idx_in = 0; DEBUG_ERROR("GSM PPP RX overflow\r\n"); } m_gsm_modem_buffer.buffer[m_gsm_modem_buffer.idx_in] = 0; } } Else // Command mode, bypass data into AT command buffer { prev_index = m_gsm_atc.atc.recv_buff.index; for (uint32_t i = 0; i < length; i++) { m_gsm_atc.atc.recv_buff.buffer[m_gsm_atc.atc.recv_buff.index++] = data[i]; if (m_gsm_atc.atc.recv_buff.index >= sizeof(((gsm_atc_buffer_t*)0)->buffer)) { DEBUG_ERROR("GSM ATC RX overflow\r\n"); m_gsm_atc.atc.recv_buff.index = 0; m_gsm_atc.atc.recv_buff.buffer[0] = 0; return; } } } } } |
Trong đó “uint32_t sio_read(sio_fd_t fd, u8_t *data, u32_t len)” trả về số byte UART-RX mà vi điều khiển nhận về từ module 4G, cần truyền lượng data này vào lwip stack, với tham số truyền vào là buffer data và độ dài dữ liệu mong muốn của LWIP. Dữ liệu được đưa tiếp vào LWIP stack bằng hàm “pppos_input”.
Vậy là tại phần này, mình đã giới thiệu đến các bạn về LWIP và các hàm driver cần thiết cho LWIP. Các nội dung tiếp theo của chuỗi bài viết:
Phần 2: Thiết kế chương trình và porting ứng dụng cho LWIP
Phần 3: Kết nối MQTT trên nền tảng LWIP
Phần 4: Kết nối HTTP trên nền tảng LWIP
Phần 5: Tối ưu các tham số cho vi điều khiển – Optimize memory (ram, flash, speed)
Cố vấn tại Cộng đồng Kỹ thuật TAPIT
Trần Văn Huy