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.
Phần 1, mình đã 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. Tại bài viết này, mình tiếp tục hướng dẫn các bạn thiết kế chương trình hàm main, các hàm chức năng và thực hiện Demo:
- Port thành công LWIP cho vi điều khiển qua giao tiếp PPP overserial
- Lấy được IPV4 của thiết bị
- Thực hiện 1 lệnh đồng bộ thời gian qua Internet
1. Thiết kế chương trình
Về cơ bản chu trình sẽ như sau
- Khởi tạo phần cứng
- Khởi tạo GSM, đăng kí cho module SIM register được vào nhà mạng Viettel
- Active kết nối
1.1 Main
Mình sẽ khởi tạo LWIP và DNS qua hàm “dns_initialize()” và “lwip_init()”. Trong đó dns_initialize dành để phân giả tên miền ra thành địa chỉ IP, lwip_init() dùng để khởi tạo core của LWIP.
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 |
int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_DMA_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ dns_initialize(); lwip_init(); DEBUG_INFO("Application started\r\n"); gsm_init_hw(); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ gsm_mnr_task(NULL); } /* USER CODE END 3 */ } |
1 2 3 4 5 6 7 8 9 |
static void dns_initialize(void) { // 8.8.8.8 và 1.1.1.1 ip_addr_t dns_server_0 = IPADDR4_INIT_BYTES(8, 8, 8, 8); ip_addr_t dns_server_1 = IPADDR4_INIT_BYTES(1, 1, 1, 1); dns_setserver(0, &dns_server_0); dns_setserver(1, &dns_server_1); dns_init(); } |
- Vòng lặp quét các trạng thái GSM
1 2 3 4 5 6 |
void gsm_mnr_task(void *arg) { gsm_hw_layer_run(); // Hỏi vòng phần hardward uart, at command, rx data… gsm_state_machine_polling(); // Hỏi vòng các state machine của GSM gsm_hw_pppos_polling(); // Hỏi vòng các công việc liên quan đến timer và serial data của LWIP } |
1.2 Các hàm ứng dụng
Sau khi khởi tạo xong module 4G chúng ta cần chuyển chế độ hoạt động của module từ “command mode” sang “data mode” bằng cách gọi hàm “open_ppp_stack”, lệnh cần quan tâm chính để đưa vào chế độ data mode là “ATD*99***1#\r\n” và chờ phản hồi “CONNECT” từ module SIM. Về cơ bản tập lệnh này giống như giữ các nhà sản xuất module SIM.
a. open_ppp_stack
1 |
gsm_hw_send_at_cmd("ATV1\r\n", "OK\r\n", "", 1000, 3, open_ppp_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 |
static void open_ppp_stack(gsm_response_event_t event, void *response_buffer, void *data) { DEBUG_INFO("Open PPP stack\r\n"); static uint8_t retry_count = 0; switch (m_gsm_manager.step) { case 1: { gsm_hw_send_at_cmd(AT_CSQ, AT_OK, AT_NULL, 1000, 2, open_ppp_stack); m_gsm_manager.step = 2; } break; case 2: { //Check SIM inserted, if removed -> RESET module NOW! gsm_hw_send_at_cmd(AT_CPIN, AT_OK, AT_NULL, 1000, 5, open_ppp_stack); m_gsm_manager.step = 3; } break; case 3: { if (event == GSM_EVENT_OK) { if (strstr(response_buffer, GSM_SIM_NOT_INSERT)) { DEBUG_INFO("Sim card not inserted\r\n"); gsm_change_state(GSM_STATE_RESET); return; } // Chuyển từ command mode sang data mode gsm_hw_send_at_cmd("ATD*99***1#\r\n", "CONNECT", AT_NULL, 1000, 10, open_ppp_stack); m_gsm_manager.step = 4; } else { DEBUG_INFO("Open ppp stack failed\r\n"); gsm_change_state(GSM_STATE_RESET); return; } } break; case 4: { DEBUG_INFO("PPP state: %s\r\n", (event == GSM_EVENT_OK) ? "[OK]" : "[FAIL]"); // Đã chuyển sang ppp mode thành công, từ đây mọi data nhận từ module 4G sẽ truyền thằng vào LWIP //stack m_gsm_manager.mode = GSM_PPP_MODE; if (event != GSM_EVENT_OK) { retry_count++; if (retry_count > 4) { retry_count = 0; ppp_close(m_ppp_control_block, 0); /* Reset GSM */ gsm_change_state(GSM_STATE_RESET); } else { m_gsm_manager.step = 3; ppp_close(m_ppp_control_block, 0); gsm_hw_send_at_cmd(ATV1, AT_OK, AT_NULL, 1000, 5, open_ppp_stack); } } else { gsm_change_state(GSM_OK); //Create PPP connection m_ppp_control_block = pppos_create(&m_ppp_netif, ppp_output_callback, ppp_link_status_cb, NULL); if (m_ppp_control_block == NULL) { DEBUG_ERROR("Create PPP interface ERR!\r\n"); //assert(0); NVIC_SystemReset(); } /* Set this interface as default route */ ppp_set_default(m_ppp_control_block); //ppp_set_auth(m_ppp_control_block, PPPAUTHTYPE_CHAP, "", ""); ppp_set_notify_phase_callback(m_ppp_control_block, ppp_notify_phase_cb); ppp_connect(m_ppp_control_block, 0); } } break; default: break; } } |
- Khi khởi tạo PPP connection, cần lưu ý cấp phát PPP control block
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Create PPP connection m_ppp_control_block = pppos_create(&m_ppp_netif, ppp_output_callback, ppp_link_status_cb, NULL); if (m_ppp_control_block == NULL) { DEBUG_ERROR("Create PPP interface ERR!\r\n"); assert(0); } /* Set this interface as default route */ ppp_set_default(m_ppp_control_block); //ppp_set_auth(m_ppp_control_block, PPPAUTHTYPE_CHAP, "", ""); ppp_set_notify_phase_callback(m_ppp_control_block, ppp_notify_phase_cb); ppp_connect(m_ppp_control_block, 0); |
b. ppp_link_status_cb
Dùng để nhận các trạng thái status, pha của PPP
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 139 140 141 142 143 144 |
/** * PPP status callback * =================== * * PPP status callback is called on PPP status change (up, down, ...) from lwIP core thread */ static void ppp_link_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { struct netif *pppif = ppp_netif(pcb); LWIP_UNUSED_ARG(ctx); switch (err_code) { case PPPERR_NONE: { #if LWIP_DNS const ip_addr_t *ns; #endif /* LWIP_DNS */ DEBUG_INFO("PPP Connected\r\n"); #if PPP_IPV4_SUPPORT DEBUG_INFO("our_ipaddr = %s\r\n", ipaddr_ntoa(&pppif->ip_addr)); DEBUG_INFO("his_ipaddr = %s\r\n", ipaddr_ntoa(&pppif->gw)); DEBUG_INFO("netmask = %s\r\n", ipaddr_ntoa(&pppif->netmask)); #if LWIP_DNS ns = dns_getserver(0); DEBUG_INFO("\tdns1 = %s\r\n", ipaddr_ntoa(ns)); ns = dns_getserver(1); DEBUG_INFO("\tdns2 = %s\r\n", ipaddr_ntoa(ns)); #endif /* LWIP_DNS */ #endif /* PPP_IPV4_SUPPORT */ #if PPP_IPV6_SUPPORT DEBUG_INFO("\r our6_ipaddr = %s\n", ip6addr_ntoa(netif_ip6_addr(pppif, 0))); #endif /* PPP_IPV6_SUPPORT */ break; } case PPPERR_PARAM: { DEBUG_INFO("status_cb: Invalid parameter\r\n"); break; } case PPPERR_OPEN: { DEBUG_INFO("status_cb: Unable to open PPP session\r\n"); break; } case PPPERR_DEVICE: { DEBUG_INFO("status_cb: Invalid I/O device for PPP\r\n"); break; } case PPPERR_ALLOC: { DEBUG_INFO("status_cb: Unable to allocate resources\r\n"); break; } case PPPERR_USER: /* 5 */ { /* ppp_close() was previously called, reconnect */ DEBUG_INFO("status_cb: ppp is closed by user OK! Try to re-open...\r\n"); /* ppp_free(); -- can be called here */ ppp_free(m_ppp_control_block); gsm_change_state(GSM_REOPEN_PPP); break; } case PPPERR_CONNECT: /* 6 */ { DEBUG_INFO("status_cb: Connection lost\r\n"); m_ppp_connected = false; ppp_close(m_ppp_control_block, 1); break; } case PPPERR_AUTHFAIL: { DEBUG_INFO("status_cb: Failed authentication challenge\r\n"); break; } case PPPERR_PROTOCOL: { DEBUG_INFO("status_cb: Failed to meet protocol\n"); break; } case PPPERR_PEERDEAD: { DEBUG_INFO("status_cb: Connection timeout\r\n"); break; } case PPPERR_IDLETIMEOUT: { DEBUG_INFO("status_cb: Idle Timeout\r\n"); break; } case PPPERR_CONNECTTIME: { DEBUG_INFO("status_cb: Max connect time reached\r\n"); break; } case PPPERR_LOOPBACK: { DEBUG_INFO("status_cb: Loopback detected\r\n"); break; } default: { DEBUG_INFO("status_cb: Unknown error code %d\r\n", err_code); break; } } /* * This should be in the switch case, this is put outside of the switch * case for example readability. */ if (err_code == PPPERR_NONE) { DEBUG_INFO("PPP is opened OK\r\n!"); return; } // /* ppp_close() was previously called, don't reconnect */ // if (err_code == PPPERR_USER) { // /* ppp_free(); -- can be called here */ // m_ppp_connected = false; // ppp_free(m_ppp_control_block); // DEBUG_INFO("\r PPP opened ERR!"); // return; // } /* * Try to reconnect in 30 seconds, if you need a modem chatscript you have * to do a much better signaling here ;-) */ // ppp_connect(pcb, 30); /* OR ppp_listen(pcb); */ } |
Trong trường hợp kết nối thành công, status của PPP callback sẽ nhảy vào trạng thái “PPPERR_NONE”, đến bước này bạn đã kết nối thành công và sẵn sàng kết nối internet.
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 |
case PPPERR_NONE: { #if LWIP_DNS const ip_addr_t *ns; #endif /* LWIP_DNS */ DEBUG_INFO("PPP Connected\r\n"); #if PPP_IPV4_SUPPORT // In địa chỉ IP của thiết bị DEBUG_INFO("our_ipaddr = %s\r\n", ipaddr_ntoa(&pppif->ip_addr)); DEBUG_INFO("his_ipaddr = %s\r\n", ipaddr_ntoa(&pppif->gw)); DEBUG_INFO("netmask = %s\r\n", ipaddr_ntoa(&pppif->netmask)); #if LWIP_DNS ns = dns_getserver(0); DEBUG_INFO("\tdns1 = %s\r\n", ipaddr_ntoa(ns)); ns = dns_getserver(1); DEBUG_INFO("\tdns2 = %s\r\n", ipaddr_ntoa(ns)); #endif /* LWIP_DNS */ #endif /* PPP_IPV4_SUPPORT */ #if PPP_IPV6_SUPPORT DEBUG_INFO("\r our6_ipaddr = %s\n", ip6addr_ntoa(netif_ip6_addr(pppif, 0))); #endif /* PPP_IPV6_SUPPORT */ break; } |
c. ppp_phase_callback
Nhận về pha của PPP và xử lí các trạng thái reconnect
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 |
static void ppp_notify_phase_cb(ppp_pcb *pcb, u8_t phase, void *ctx) { switch (phase) { /* Session is down (either permanently or briefly) */ case PPP_PHASE_DEAD: DEBUG_INFO("PPP_PHASE_DEAD\r\n"); m_gsm_manager.ppp_phase = PPP_PHASE_DEAD; break; /* We are between two sessions */ case PPP_PHASE_HOLDOFF: DEBUG_INFO("PPP_PHASE_HOLDOFF\r\n"); m_gsm_manager.ppp_phase = PPP_PHASE_HOLDOFF; break; /* Session just started */ case PPP_PHASE_INITIALIZE: DEBUG_INFO("PPP_PHASE_INITIALIZE\r\n"); m_gsm_manager.ppp_phase = PPP_PHASE_INITIALIZE; break; case PPP_PHASE_NETWORK: DEBUG_INFO("PPP_PHASE_NETWORK\r\n"); break; case PPP_PHASE_ESTABLISH: DEBUG_INFO("PPP_PHASE_ESTABLISH\r\n"); break; /* Session is running */ case PPP_PHASE_RUNNING: DEBUG_INFO("PPP_PHASE_RUNNING\r\n"); m_gsm_manager.ppp_phase = PPP_PHASE_RUNNING; m_ppp_connected = true; break; case PPP_PHASE_TERMINATE: DEBUG_INFO("PPP_PHASE_TERMINATE\r\n"); break; case PPP_PHASE_DISCONNECT: DEBUG_INFO("PPP_PHASE_DISCONNECT\r\n"); break; default: DEBUG_INFO("Unknown PPP phase %d\r\n", phase); break; } } |
2. Thử nghiệm và ứng dụng
2.1 Lấy IPv4 của thiết bị
- Kết quả thử nghiệm, thiết bị đã kết nối thành công vào internet và lấy được IPv4
2.2 Thử nghiệm đồng bộ thời gian từ internet
Khởi tạo dịch vụ đồng bộ thời gian, ở đây mình sẽ lấy internet từ NTP server “pool.ntp.org“
1 2 3 4 5 6 7 8 9 10 11 12 |
static void initialize_stnp(void) { static bool sntp_start = false; if (sntp_start == false) { sntp_start = true; DEBUG_INFO("Initialize stnp\r\n"); sntp_setoperatingmode(SNTP_OPMODE_POLL); sntp_setservername(0, "pool.ntp.org"); sntp_init(); } } |
Khai báo hàm callback đồng bộ thời gian trong file “lwipopts.h”
- Xử lí callback data của dịch vụ SNTP
1 2 3 4 5 6 7 8 9 10 11 |
void lwip_sntp_recv_callback(uint32_t time) { if (time == 0) { DEBUG_WARN("NTP: Error, server not responding or bad response\r\n"); } else { DEBUG_INFO("NTP: %u seconds elapsed since 1.1.1970\r\n", time); } } |
- Kết quả:
3. Phần tiếp theo
Vậy là tại phần này, mình đã giới thiệu đến các bạn thiết kế chương trình hàm main, các hàm chức năng và thực hiện Demo:
- Port LWIP cho vi điều khiển qua giao tiếp PPP overserial
- Lấy IPV4 của thiết bị
- Thực hiện lệnh đồng bộ thời gian qua Internet
Các bạn cùng đón chờ các nội dung tiếp theo của chuỗi bài viết:
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)
—
Chúc các bạn thành công!
Tài liệu tham khảo: https://github.com/huybk213/lwip_porting
Cố vấn tại Cộng đồng Kỹ thuật TAPIT
Trần Văn Huy