# STM32F4-ETH-OTA **Repository Path**: lewque/stm32-f4-eth-ota ## Basic Information - **Project Name**: STM32F4-ETH-OTA - **Description**: STM32F407使用以太网进行OTA的程序 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2024-07-19 - **Last Updated**: 2024-07-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 通过以太网实现OTA程序的步骤 - bootloader程序 bootloader程序需要实现 - 以太网驱动 - 外部FLASH驱动 - 应用程序跳转 - app应用程序 # BootLoader程序 ## 1. 以太网驱动 单片机通过以太网能够正常访问外部网络后也就能够从网络获取固件,下一步就是将固件保存到flash中,因为STM32内部flash只有1MB,而rtthread+以太网之后固件也176KB,还是不太够用的,所以需要使用外部flash,因此需要借助rtthread的FAL,文件抽象层来驱动板子上的外部flash w25q128来存储固件 ## 2. FLASH驱动 ## 3. 获取HTTPServer上的固件包 使用httpserver的方式提供固件下载,在服务器上使用命令,即可将指定目录通过httpserver的方式通过8080端口映射到公网 ```shell python -m http.server ``` 使用socket访问httpserver获取固件包,先用Linux下 C实现 ```c #include #include #include #include #include #include int main() { int socket_desc; struct sockaddr_in server; char server_ip[] = "124.222.71.199"; int server_port = 8070; char file_path[] = "/stm32/rtthread.bin"; char *request; uint16_t request_size; uint8_t *response; // 创建套接字 socket_desc = socket(AF_INET, SOCK_STREAM, 0); if (socket_desc == -1) { printf("无法创建套接字\n"); return 1; } server.sin_addr.s_addr = inet_addr(server_ip); server.sin_family = AF_INET; server.sin_port = htons(server_port); // 连接服务器 if (connect(socket_desc, (struct sockaddr *)&server, sizeof(server)) < 0) { printf("连接失败\n"); return 1; } request_size = snprintf(NULL, 0, "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", file_path, server_ip) + 1; // 动态申请内存 request = (char *)malloc(request_size); if (request == NULL) { printf("内存分配失败\n"); return 1; } // 构建HTTP请求 snprintf(request, request_size, "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", file_path, server_ip); // 发送HTTP请求 if (send(socket_desc, request, strlen(request), 0) < 0) { printf("发送失败\n"); return 1; } response = (uint8_t *)malloc(1024 * 256); uint16_t bytes_received = 0; // 读取并保存响应数据 bytes_received = recv(socket_desc, response, 1024 * 256, 0); if (bytes_received <= 0) { printf("无法接收响应数据\n"); return 1; } // 截取bin文件数据 char *bin_data = strstr((char *)response, "\r\n\r\n"); if (bin_data == NULL) { printf("无法找到bin文件数据\n"); free(response); free(request); return 1; } bin_data += 4; // 跳过 \r\n\r\n // 创建本地文件 FILE *local_file = fopen("smart_agriculture_data.bin", "wb"); if (local_file == NULL) { printf("无法创建本地文件\n"); free(response); free(request); return 1; } fwrite(bin_data, 1, bytes_received - (bin_data - (char *)response), local_file); fclose(local_file); free(response); free(request); close(socket_desc); printf("数据保存完成\n"); return 0; } ``` 响应的数据包 ![image-20230831101304169](https://zhangguosheng.oss-cn-beijing.aliyuncs.com/image-20230831101304169.png) 固件的数据包 ![image-20230831101321140](https://zhangguosheng.oss-cn-beijing.aliyuncs.com/image-20230831101321140.png) 通过对比,真正的bin文件在在GMT以及0D0A0D0A后,前面的是http的响应头。 可以看到在接收的数据包GMT后面有字符0D0A0D0A,查了一下是ascii中的\r\n\r\n,因此通过\r\n\r\n即可找到真正的bin文件 移植到单片机程序如下: ```c /* * Copyright (c) 2006-2021, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2023-08-31 zmake the first version */ #include #include #define DBG_TAG "ota" #define DBG_LVL DBG_LOG #include #define FIRMWARE_MAX_SIZE 1024*256 void ota_thread_func(void *arg) { int socket_desc; struct sockaddr_in server; char server_ip[] = "124.222.71.199"; int server_port = 8070; char file_path[] = "/stm32/rtthread.bin"; char *request; uint16_t request_size; uint8_t *response; response = (uint8_t *)rt_malloc(FIRMWARE_MAX_SIZE); if(response == NULL) { rt_kprintf("malloc failed\r\n"); rt_thread_delete(rt_thread_self()); } // 创建套接字 socket_desc = lwip_socket(AF_INET, SOCK_STREAM, 0); if (socket_desc == -1) { rt_kprintf("无法创建套接字\n"); rt_thread_delete(rt_thread_self()); } server.sin_addr.s_addr = inet_addr(server_ip); server.sin_family = AF_INET; server.sin_port = htons(server_port); // 连接服务器 if (lwip_connect(socket_desc, (struct sockaddr *)&server, sizeof(server)) < 0) { rt_kprintf("连接失败\n"); rt_thread_delete(rt_thread_self()); } request_size = rt_snprintf(NULL, 0, "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", file_path, server_ip) + 1; // 动态申请内存 request = (char *)rt_malloc(request_size); if (request == NULL) { rt_kprintf("内存分配失败\n"); rt_thread_delete(rt_thread_self()); } // 构建HTTP请求 rt_snprintf(request, request_size, "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", file_path, server_ip); // 发送HTTP请求 if (lwip_send(socket_desc, request, rt_strlen(request), 0) < 0) { rt_kprintf("发送失败\n"); rt_thread_delete(rt_thread_self()); } uint16_t bytes_received = 0; // 读取并保存响应数据 bytes_received = lwip_recv(socket_desc, response, FIRMWARE_MAX_SIZE, 0); if (bytes_received <= 0) { rt_kprintf("无法接收响应数据\n"); rt_thread_delete(rt_thread_self()); } rt_kprintf("%s", response); // // 截取bin文件数据 // char *bin_data = strstr((char *)response, "\r\n\r\n"); // if (bin_data == NULL) { // rt_kprintf("无法找到bin文件数据\n"); // free(response); // free(request); // rt_thread_delete(rt_thread_self()); // } // // bin_data += 4; // 跳过 \r\n\r\n // 写入flash // 创建本地文件 // FILE *local_file = fopen("smart_agriculture_data.bin", "wb"); // if (local_file == NULL) { // rt_kprintf("无法创建本地文件\n"); // rt_free(response); // rt_free(request); // return 1; // } // fwrite(bin_data, 1, bytes_received - (bin_data - (char *)response), local_file); // fclose(local_file); rt_free(response); rt_free(request); lwip_close(socket_desc); rt_kprintf("数据保存完成\n"); } void ota(void) { rt_thread_t ota_thread; ota_thread = rt_thread_create("ota", ota_thread_func, RT_NULL, 1024, 5, 20); if(ota_thread != RT_NULL) { rt_thread_startup(ota_thread); }else{ LOG_E("ota thread create failed\r\n"); } } MSH_CMD_EXPORT(ota, start ota); ``` 现在通过命令ota即可创建一个线程并从http服务器获取固件并打印出响应头 ![image-20230902130931318](https://zhangguosheng.oss-cn-beijing.aliyuncs.com/image-20230902130931318.png) 但是目前遇到的问题是通过线程的方式获取http响应并没有足够的内存空间去存储http响应,线程需要占用栈空间,rt_malloc分配的内存空间在堆空间中,栈空间和堆空间都在内存中,F407的内存为128K,所以不能存放http响应。 所以不能使用动态分配的内存来存放http响应, 获取完整的bin包 ```c /* * Copyright (c) 2006-2021, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2023-08-31 zmake the first version */ #include #include #define DBG_TAG "ota" #define DBG_LVL DBG_LOG #include #define FIRMWARE_MAX_SIZE 1024*256 uint32_t str2int(uint8_t *str) { uint8_t *str_end, *str_start; uint32_t val=0; str_start = str; while(*str!='\r') { str++; } str_end = str; val = strtoll((const char *)str_start, (char **)&str_end, 10); if(val == 0) { rt_kprintf("str转int失败\r\n"); } return val; } void get_bin_start(uint8_t *response,uint32_t *bin_size) { uint8_t *data_ptr=NULL; data_ptr = (uint8_t*)rt_strstr((const char*)response,"Content-Length: "); data_ptr += 16; *bin_size = str2int(data_ptr); rt_kprintf("[bin size:%ld Bytes]\r\n",*bin_size); } void ota_thread_func(void *arg) { uint16_t socket_desc; struct sockaddr_in server; char server_ip[] = "124.222.71.199"; int server_port = 8070; char file_path[] = "/stm32/rtthread.bin"; char *request; uint16_t request_size; uint8_t *response; // 创建套接字 socket_desc = lwip_socket(AF_INET, SOCK_STREAM, 0); if (socket_desc == -1) { rt_kprintf("无法创建套接字\n"); rt_thread_delete(rt_thread_self()); } server.sin_addr.s_addr = inet_addr(server_ip); server.sin_family = AF_INET; server.sin_port = htons(server_port); // 连接服务器 if (lwip_connect(socket_desc, (struct sockaddr *)&server, sizeof(server)) < 0) { rt_kprintf("连接失败\n"); goto ota_failed; } request_size = rt_snprintf(NULL, 0, "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", file_path, server_ip) + 1; // 动态申请内存 request = (char *)rt_malloc(request_size); if (request == NULL) { rt_kprintf("内存分配失败\n"); goto ota_failed; } // 构建HTTP请求 rt_snprintf(request, request_size, "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", file_path, server_ip); // 发送HTTP请求 if (lwip_send(socket_desc, request, rt_strlen(request), 0) < 0) { rt_kprintf("发送失败\n"); goto ota_failed; } rt_free(request); response = (uint8_t *)rt_malloc(1024); if(response == NULL) { rt_kprintf("malloc failed\r\n"); goto ota_failed; } uint32_t bytes_received = 0; // 读取并保存响应数据 bytes_received = lwip_recv(socket_desc, response, 1024, 0); if (bytes_received <= 0) { rt_kprintf("无法接收响应数据\n"); goto ota_failed; } rt_kprintf("%s\r\n", response); uint32_t bin_size = 0; uint32_t *p_bin_size = &bin_size; uint32_t total_size=0; get_bin_start(response, p_bin_size); (void)p_bin_start; uint16_t bin_frame_count=0; uint16_t bin_frame_total = bytes_received - total_size / 1024; rt_kprintf("共有%d帧\r\n",bin_frame_total); while(total_size < bin_size) { uint8_t *bin_data_ptr; if(bin_frame_count==bin_frame_total) bytes_received = lwip_recv(socket_desc, response, bin_size-total_size, 0); else{ bytes_received = lwip_recv(socket_desc, response, 1024, 0); } if (bytes_received <= 0) { rt_kprintf("无法接收响应数据\n"); goto ota_failed; } bin_data_ptr = response; for(int i=0;i