Trong các bài học trước, chúng ta đã quen với việc sử dụng ip tĩnh được gán sẵn trong chương trình. Tiếp tục bài này, chúng ta sẽ làm quen với giao thức DHCP . DHCP – Dynamic Host Configuration Protocol giúp cấp ip một cách tự động và lấy các thông tin cấu hình khác như gateway
Quá trình đạt được địa chỉ của giao thức dhcp gồm các bước sau:
- Bước 1: Thiết bị phát tán 1 thông điệp DHCP Discover có chứa địa chỉ MAC, tên thiết bị …
- Bước 2: Máy chủ nhận thông điệp và chuẩn bị địa chỉ IP cho thiết bị và phát tán bản tin offer lên mạng
- Bước 3: Thiệt bị nhận thông điệp, lấy địa chỉ IP và gửi bản tin DHCP Request
- Bước 4: Máy chủ nhận bản tin và trả lời lại bằng bản tin ACK
Cấu trúc gói tin
Gói tin Discovery
Gói tin Offer
Gói tin Request
Gói tin Acknowledgement
Các bạn tìm hiểu chi tiết hơn tại đây
- https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol
- https://medium.com/@bromiley/full-packet-friday-dhcp-abbc6b7b3c77
Tải file chụp gói tin bằng wireshark tại đây
Dùng bộ lọc để lọc ra các gói tin dhcp nhé
Lập trình
Do phần mềm mô phỏng không hỗ trợ giao thức dhcp và các giao thức về sau nên bắt đầu từ bài này mình sẽ chuyển sang dùng module thật. Phần cứng mình cũng sẽ chuyển luôn qua kit stm32f103c8t6 vì mình đang có kit này :v
Chuẩn bị đồ nghề
- Với stm32f103c8t6 thì mình sử dụng thử viện HAL, nên các bạn chuẩn bị HAL CUBEMX để sinh code nhé
- Module USB-TTL để debug dữ liệu, phần mềm Hercules
- Kit STM32, mạch nạp
- Moudle ENC28J60
- Modem mạng, dây nối …
Kết nối phần cứng
ENC28J60 | STM32F103C6 |
MOSI | A7 |
MISO | A6 |
SCK | A5 |
CS | B1 |
INT | B0 |
GND | GND |
5V | 5V |
PL2303 (USB – TTL) | STM32F103C8 |
RX | A9 |
GND | GND |
Nói chúng, mính sẽ dùng bộ SPI1 và USART1 để debug
Khởi tạo project với CUBEMX
Trong phần Clock Configuration, chọn thạch anh 8M, tốc độ 72Mhz
Sinh code tùy theo trình dịch của bạn, sau đó đó tải thư viện cho các bài trước mà mình đã “remake” lại cho phù hợp với thư viện HAL. Còn ở đây, mình sử dụng trình dịch KeilC
Các bạn tải thư viện tại đây
Hướng dẫn add thư viện vào KeilC
Các bạn sử dụng trình dịch khác thì tham khảo cách add thư viện trên google nhé
Sau khi sinh code và mở project, các bạn giải nén và copy thư mục thư viện tên là “IOT47_lib” vào thư mục Drivers
Trong KeilC, click vào Option for Target
Chọn thẻ C/C++ -> Include Paths
Add dòng này vào dưới cùng
../Drivers/IOT47_lib/ENC28J60
Để dẽ quản lí, chúng ta sẽ đưa tất cả các file thư viện .c và trong cây Project và nhóm riêng vào trong group tên là ENC28J60 nhé
Các bạn ấn chuột phải vào project trong cây Project và chọn Add group, group New Folder được tạo ,click và đổi tên group thành tên gì các bạn muốn, ở đây mình sẽ đổi tên là ENC28J60
Click đúp vào group vừa tạo và tiến hành add tất cả các file .c vào cây quản lí project
Bây giờ add các thư viện cần thiết vào file main và biên dịch thử nhé
1 2 3 |
#include "enc28j60.h" #include "net.h" #include "uart.h" |
Thêm chương trình ngắt timer và ngắt ngoài
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM2) { ARP_clear_table(); } } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin == GPIO_PIN_0 && net_readEnable()) { NET_loop(); } } |
Và thêm hàm khởi tạo vào đầu chương trình (trong hàm main, trước while(1) )
1 2 3 |
UART_init(&huart1); ENC29J600_ini(&hspi1); HAL_TIM_Base_Start_IT(&htim2); |
Các bạn có thể test lại các giao thức xem thư viện hoạt động ok chưa nhé, bây giờ sẽ đi vào phần chính là viết thư viện dhcp ( trong file thư viện mình để đã có sẵn thư viện dhcp rồi đó)
Thư viện DCHP
File dhcp.h
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 |
#ifndef DHCP_H_ #define DHCP_H_ //-------------------------------------------------- //Include cac thu vien can thiet #include "stm32f1xx_hal.h" #include <string.h> #include <stdlib.h> #include <stdint.h> #include <stdio.h> #include "udp.h" #include "uart.h" //------------------------------------------------- #define Port_src 68 #define Port_dst 67 #define Boardcast {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF} #define ip_src {0,0,0,0} #define ip_dst {0xFF,0xFF,0xFF,0xFF} //------------------------------------------------- typedef struct { uint8_t op; //1 uint8_t htype; //1 uint8_t hlen; //6 uint8_t hops; //0 uint32_t id; uint16_t seconds; uint16_t flags; uint8_t client_ip[4]; uint8_t your_id[4]; uint8_t server_id[4]; uint8_t gateway_id[4]; uint8_t client_mac[6]; uint8_t client_padding[10]; uint8_t server_name[64]; uint8_t boot_file_name[128]; uint32_t magic_cookie; //DHCP OPTION uint16_t option53; //0x3501 // Option: (53) DHCP Message Type (Discover) uint8_t option53_dhcp; //0x01 ;//discover uint16_t option61; //0x3D07 //Option: (61) Client identifier uint8_t option61_hard_type; // Hardware type: Ethernet (0x01) uint8_t option61_mac[6]; //Client MAC address uint16_t option12; //0x0C05 Option: (12) Host Name (IOT47) uint8_t option12_hostname[5]; uint16_t option55;//0x3705 //Option: (55) Parameter Request List uint8_t option55_subnet_mask; //Parameter Request List Item: (1) Subnet Mask uint8_t option55_router; //Parameter Request List Item: (3) Router uint8_t option55_domain_name_sv;//Parameter Request List Item: (6) Domain Name Server uint8_t optionEND;//0xFF }__attribute__ ((packed)) DHCP_discover_segment; typedef struct { uint8_t opcode; uint8_t dump1[15]; uint8_t offfer_ip[4]; uint8_t *option; }__attribute__ ((packed)) DHCP_return_segment; //----------------------------------------------------------------------------- uint8_t DHCP_get_ip(uint32_t timeout); void dhcp_discover(void); void dhcp_getGateway(uint8_t *dhcp_mess,uint8_t *gate_mac,uint8_t *gate_ip); //------------------------------------------------- #endif /* ARP_H_ */ |
File dhcp.c
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 |
#include "dhcp.h" extern const uint8_t macaddr[6]; extern uint8_t ip[4]; extern char debug_string[60]; extern uint8_t gateway_ip[4]; extern uint8_t gateway_mac[6]; uint8_t boar_cast[6]=Boardcast; uint8_t ip_nguon[4]=ip_src; uint8_t ip_dich[ 4]=ip_dst; uint8_t getip_ok=0; uint8_t DHCP_get_ip(uint32_t timeout) { uint32_t t = HAL_GetTick(); while(1) { if( (HAL_GetTick() - t) > timeout) return 0; //time out dhcp_discover(); HAL_Delay(1000); if(getip_ok)return 1; } } void dhcp_discover(void) { uint16_t dhcp_discover_length,i; DHCP_discover_segment discover; discover.op = 1; discover.htype=1; discover.hlen=6; discover.hops=0; discover.id = swap32((uint32_t)0x20071999); discover.seconds = 0; discover.flags=0; memcpy(discover.client_ip,ip_nguon,4); memcpy(discover.your_id,ip_nguon,4); memcpy(discover.server_id,ip_nguon,4); memcpy(discover.gateway_id,ip_nguon,4); memcpy(discover.client_mac,macaddr,6); for(i=0;i<10;i++)discover.client_padding[i]=0x00; for(i=0;i<64;i++)discover.server_name[i]=0x00; for(i=0;i<128;i++)discover.boot_file_name[i]=0x00; discover.magic_cookie=swap32((uint32_t)0x63825363); discover.option53= 0x0135; discover.option53_dhcp=0x01; discover.option61=0x073D; discover.option61_hard_type=0x01; memcpy(discover.option61_mac,macaddr,6); discover.option12 = 0x050C; discover.option12_hostname[0]= 'I'; discover.option12_hostname[1]= 'O'; discover.option12_hostname[2]= 'T'; discover.option12_hostname[3]= '4'; discover.option12_hostname[4]= '7'; discover.option55=0x0337; discover.option55_subnet_mask=1; discover.option55_router=3; discover.option55_domain_name_sv=6; discover.optionEND=0xFF; //end dhcp_discover_length=sizeof(discover); UDP_send_with_mac(boar_cast,ip_dich,Port_dst,(uint8_t *)macaddr,ip_nguon,Port_src,(uint8_t *)&discover,dhcp_discover_length); } void dhcp_getGateway(uint8_t *dhcp_mess,uint8_t *gate_mac,uint8_t *gate_ip) { DHCP_return_segment *DHCP_return = (DHCP_return_segment *)dhcp_mess; if(getip_ok == 0) { getip_ok=1; memcpy(ip,DHCP_return->offfer_ip,4); memcpy(gateway_ip,gate_ip,4); memcpy(gateway_mac,gate_mac,6); sprintf(debug_string,"Your IP:%i.%i.%i.%i\r\n",ip[0],ip[1],ip[2],ip[3]); UART_putString(debug_string); sprintf(debug_string,"GateWay IP:%i.%i.%i.%i\r\n",gateway_ip[0],gateway_ip[1],gateway_ip[2],gateway_ip[3]); UART_putString(debug_string); sprintf(debug_string,"GateWay MAC:%02X:%02X:%02X:%02X:%02X:%02X\r\n",gateway_mac[0],gateway_mac[1],gateway_mac[2],gateway_mac[3],gateway_mac[4],gateway_mac[5]); UART_putString(debug_string); dhcp_discover(); } } //----------------END FILE---------------------------------// |
Trong hàm main gọi hàm DHCP_get_ip với tham số timeout là 10000, tức 10 giây
1 2 |
UART_putString("Get IP ...\r\n"); if(!DHCP_get_ip(10000))UART_putString("DHCP error !\r\n");//get dynamic ip, timeout 10s |
Hàm DHCP_get_ip trả về 0 tức là timeout, 1 là OK và sẽ in ra màn hình Serial ip mà ENC28J60 được mode wifi phát
Chúng ta đã lấy được IP cá nhân, IP của Gateway và Mac của Gate, đây sẽ là các giá trị quan trọng được sử dụng cho các giao thức tiếp theo
Tải full project với cubemx và keilc tại đây
Xem toàn bộ các bài viết về ENC28J60
http://iot47.com/category/iot-tutorial/ethernetgiao-tiep-enc28j60/
Lưu ý: Thư viện dhcp chưa hoàn chỉnh, các bạn cũng phải thêm phần đọc thời gian sống của ip để yêu cầu gateway cấp phát lại ip, thông thường các ip sẽ có thời gian dùng khoảng 24 giờ