简介:本资源是一套面向嵌入式初学者与物联网项目开发者的STM32与ESP8266串口通信驱动程序,聚焦解决MCU通过AT指令控制Wi-Fi模块实现STA/AP双模联网的核心问题。适用于基于HAL库的STM32F1/F4系列开发,涵盖初始化配置、AT命令收发、响应解析、模式切换及基础错误处理等关键环节,帮助开发者快速构建无线数据传输能力。压缩包为RAR格式,共2个文件(1个C源文件实现核心逻辑,1个H头文件定义宏、结构体与函数接口),总大小仅4KB,轻量易集成,代码结构清晰,所有硬件相关宏(如UARTx、收发函数指针)均预留可配置接口,便于适配不同开发板与RTOS环境。目前已有1382人学习下载,读者可直接复用该驱动框架,结合ESP8266官方AT指令集快速完成Wi-Fi连接、TCP透传或热点搭建等典型IoT功能验证。
1. STM32 ESP8266驱动程序:不是“AT指令封装”,而是把Wi-Fi模块真正变成STM32的外设
你手头有一块STM32F103C8T6最小系统板,还有一颗ESP8266-01S模块,想让它连上家里路由器、发HTTP请求、上传传感器数据——但翻遍GitHub,90%的所谓“驱动”只是把AT+CWMODE=1、AT+CWJAP这些字符串拼一拼、发一发、再strstr()找OK或ERROR。这种写法在实验室点灯阶段能跑通,一旦遇到信号弱、AP重连、DNS超时、TCP粘包,就会变成黑匣子:串口打印一堆Recv: +IPD,xxx:却收不到完整JSON,或者AT+CWJAP返回FAIL后死等30秒才重试,主循环全卡住。这份「STM32 ESP8266驱动程序」不是AT指令胶水层,而是一套带状态机、可抢占、支持超时重试、自动重连、缓冲区管理、错误码分级的嵌入式外设驱动框架。它把ESP8266抽象成esp_wifi_t结构体,暴露esp_wifi_init()、esp_wifi_connect()、esp_tcp_send()、esp_udp_recv()等类HAL接口,底层用环形缓冲区+DMA+空闲中断收发数据,上层可无缝接入FreeRTOS任务或裸机定时器调度。适合正在做物联网终端原型、需要稳定联网能力又不想被AT指令细节拖垮开发节奏的嵌入式开发者。
2. 工程结构与核心依赖:从源码包看清楚它到底“长什么样”
拿到压缩包解压后,你会看到一个典型的STM32标准外设库(StdPeriph)风格工程,但关键在于它对硬件抽象的粒度和对ESP8266通信生命周期的建模方式。整个驱动不依赖HAL库,也不绑定特定IDE,所有硬件操作都通过esp_hal.h统一收口,这意味着你只要改4个函数(串口初始化、发送字节、接收字节、获取毫秒滴答),就能把它移植到任何基于Cortex-M的MCU平台。
2.1 目录结构解析:为什么说它是“可移植驱动”而非“Demo工程”
stm32_esp8266_driver/ ├── Core/ # 驱动核心逻辑(状态机、协议解析、API) │ ├── esp_core.c # 主状态机:IDLE → INIT → CONNECTING → CONNECTED → DISCONNECTED │ ├── esp_at_parser.c # AT响应解析器:支持多行响应(如AT+CWLAP)、带校验的+IPD数据提取 │ ├── esp_buffer.c # 双缓冲机制:RX环形缓冲区(1KB)+ TX线性缓冲区(512B),支持阻塞/非阻塞模式 │ └── esp_api.c # 对外API:esp_wifi_connect() / esp_http_post() / esp_mqtt_publish() ├── HAL/ # 硬件抽象层(你唯一需要改的地方) │ ├── esp_hal.c # 实现esp_hal_uart_init() / esp_hal_uart_send() / esp_hal_uart_recv() │ └── esp_hal.h # 定义HAL函数指针类型,含超时参数(单位ms) ├── Config/ # 配置项集中管理 │ ├── esp_config.h # 可裁剪:是否启用自动重连、最大重试次数、DNS缓存条目数、HTTP超时阈值 │ └── esp_user_config.h # 用户自定义:SSID/PSK、服务器域名、MQTT Broker地址等 ├── Examples/ # 三个典型用例(非main.c,是独立.c文件) │ ├── example_http_get.c # 带重试的GET请求,解析HTTP状态码+Content-Length │ ├── example_mqtt_pub.c # MQTT PUBLISH流程:CONNECT → SUBSCRIBE → PUBLISH → DISCONNECT │ └── example_udp_echo.c # UDP双向通信:自动处理分片、校验和、心跳保活 └── Inc/ ├── esp_types.h # 关键类型定义:esp_err_t(分ESP_OK/ESP_TIMEOUT/ESP_NO_AP_FOUND/ESP_TCP_CONN_FAIL等) └── esp_wifi.h # 主头文件:包含所有API声明和结构体(esp_wifi_t, esp_tcp_conn_t)提示:这个目录结构刻意避开“bsp”“driver”等模糊命名,用
Core/HAL/Config直指设计意图——Core是不可变逻辑,HAL是可替换硬件适配层,Config是业务开关。你不需要动Core里的任何一行代码,就能让驱动跑在STM32F407或GD32F303上。
2.2 关键头文件设计:如何用C语言实现“面向对象”的驱动接口
esp_wifi.h是整个驱动的门面,它没有暴露任何AT指令字符串或状态枚举,而是用结构体封装设备上下文:
// Inc/esp_wifi.h typedef struct { uint8_t mac[6]; // 模块MAC地址(AT+CIFSR获取) uint8_t ip[4]; // 获取到的IPv4地址 uint8_t gateway[4]; uint8_t netmask[4]; uint8_t status; // ESP_WIFI_STATUS_CONNECTED等 uint32_t last_activity_ms; // 最后一次成功通信时间戳(用于心跳检测) } esp_wifi_t; typedef struct { const char* host; // 目标域名或IP uint16_t port; // 端口 uint32_t timeout_ms; // 连接超时(默认5000ms) uint8_t keep_alive; // 是否启用TCP保活(默认0) } esp_tcp_conn_t; esp_err_t esp_wifi_init(esp_wifi_t* wifi); esp_err_t esp_wifi_connect(const char* ssid, const char* password); esp_err_t esp_tcp_connect(esp_tcp_conn_t* conn, esp_tcp_handle_t* handle); esp_err_t esp_tcp_send(esp_tcp_handle_t handle, const uint8_t* data, uint16_t len, uint32_t timeout_ms);这段代码背后是严格的职责分离:esp_wifi_t只存运行时状态,不存缓冲区;esp_tcp_conn_t只管连接参数,不碰socket句柄;esp_tcp_handle_t是opaque类型(在.c里定义为uint16_t),对外完全隐藏实现细节。这种设计让你在调试时能快速定位问题层级——如果esp_tcp_send()返回ESP_TIMEOUT,说明网络层已建立但远端无响应;如果是ESP_TCP_CONN_FAIL,那一定是esp_tcp_connect()阶段出错,不用去翻AT日志。
2.3 状态机核心逻辑:esp_core.c中的一次完整连接流程
驱动的灵魂在esp_core.c的esp_core_task()函数,它是一个可被FreeRTOS任务或裸机SysTick调用的非阻塞轮询函数。我们来看esp_wifi_connect()触发后的关键路径:
// Core/esp_core.c static esp_err_t esp_core_connect_step(esp_wifi_t* wifi) { switch (wifi->status) { case ESP_WIFI_STATUS_IDLE: // Step 1: 发送AT复位指令,等待模块就绪 return esp_at_send_cmd("AT+RST", "ready", 3000); // 超时3s,匹配"ready" case ESP_WIFI_STATUS_RESET_OK: // Step 2: 设置Station模式 return esp_at_send_cmd("AT+CWMODE=1", "OK", 1000); case ESP_WIFI_STATUS_SET_MODE_OK: // Step 3: 连接AP(注意:这里传入的是用户配置的SSID/PSK) char cmd[64]; snprintf(cmd, sizeof(cmd), "AT+CWJAP=\"%s\",\"%s\"", esp_config.ssid, esp_config.password); return esp_at_send_cmd(cmd, "WIFI CONNECTED", 10000); // 给足10秒连网时间 case ESP_WIFI_STATUS_CONNECTED: // Step 4: 获取IP地址(关键!很多驱动漏掉这步导致后续HTTP失败) return esp_at_send_cmd("AT+CIFSR", "OK", 2000); default: return ESP_FAIL; } }这个状态机的精妙之处在于:每一步都带独立超时、独立成功标志、独立失败回退。比如AT+CWJAP失败时,它不会直接返回错误,而是进入ESP_WIFI_STATUS_CONNECT_FAILED状态,记录失败次数,3秒后自动重试(由esp_config.max_retry_count控制)。而AT+CIFSR返回的IP字符串格式是+CIFSR:STAIP,"192.168.1.105",解析逻辑在esp_at_parser.c里用有限状态机完成,避免sscanf()在资源受限MCU上的栈溢出风险。
3. 编译与烧录:从Keil到STM32CubeIDE的零修改适配
这份驱动不绑定任何IDE,但实际落地时,你最常面对的是Keil MDK-ARM或STM32CubeIDE。下面以STM32F103C8T6 + Keil uVision5为例,说明如何3分钟内让驱动跑起来——重点不是“怎么加文件”,而是“哪些地方必须改、哪些绝对不能动”。
3.1 添加源码到工程的正确姿势
不要把整个stm32_esp8266_driver/文件夹拖进Keil。正确做法是:
- 在Keil工程根目录下新建
Drivers/ESP8266/文件夹; - 将
Core/下的.c文件(esp_core.c,esp_at_parser.c,esp_buffer.c,esp_api.c)复制进去; - 将
HAL/下的esp_hal.c复制进去,并立即打开它; - 在
esp_hal.c顶部找到#include "stm32f10x_usart.h",确认你的工程已包含标准外设库头文件路径; - 修改
esp_hal_uart_init()函数中的串口实例(默认是USART2)和引脚(PA2/PA3),匹配你硬件接线; - 将
Inc/下所有.h文件添加到Keil的Options for Target → C/C++ → Include Paths中。
注意:
esp_hal.c是唯一需要你动手改的文件。其他所有.c文件保持原样——这是驱动可移植性的基石。如果你发现要改esp_core.c才能编译通过,说明你的HAL层没对齐,应该回头检查esp_hal.h里的函数声明是否与esp_hal.c实现一致。
3.2 串口硬件配置要点:为什么921600波特率在STM32F103上可行
ESP8266-01S官方支持最高921600bps,但很多开发者用115200凑合,结果在HTTP POST大JSON时频繁丢包。驱动默认配置为UART_BAUDRATE_921600,在STM32F103上需满足两个条件:
- 系统时钟必须为72MHz(HSE+PLL);
- USARTDIV计算值需精确到小数点后三位(标准库函数
USART_Init()内部会四舍五入,但误差<0.5%即可)。
验证方法:在esp_hal_uart_init()中加入实测代码:
// HAL/esp_hal.c void esp_hal_uart_init(void) { USART_InitTypeDef USART_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_USART2, ENABLE); USART_InitStructure.USART_BaudRate = 921600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, &USART_InitStructure); // 关键:开启空闲中断(用于检测一帧数据结束) USART_ITConfig(USART2, USART_IT_IDLE, ENABLE); NVIC_EnableIRQ(USART2_IRQn); }提示:空闲中断(IDLE interrupt)是驱动高可靠性的核心。它不依赖固定长度或特殊结束符,只要串口线上连续出现1个字符时间的空闲,就触发中断,此时DMA接收缓冲区中的数据即为完整一帧。这比
while(USART_GetFlagStatus(USART2, USART_FLAG_RXNE))轮询方式节省90% CPU占用。
3.3 FreeRTOS集成:如何让ESP驱动不阻塞其他任务
如果你的工程已跑FreeRTOS,千万别把esp_core_task()放在while(1)里死循环调用。正确做法是创建一个专用任务:
// main.c void ESP_Task(void const * argument) { esp_wifi_t wifi; esp_wifi_init(&wifi); for(;;) { esp_core_task(&wifi); // 非阻塞轮询,耗时<50us osDelay(10); // 每10ms检查一次状态机 } } int main(void) { // ... 其他初始化 osThreadDef(ESP_TASK, ESP_Task, osPriorityBelowNormal, 0, 256); osThreadCreate(osThread(ESP_TASK), NULL); osKernelStart(); }这个任务的堆栈设为256字节足够——因为驱动所有AT指令发送都用snprintf()到栈上临时缓冲区,不malloc;所有接收数据都存于预分配的环形缓冲区;状态机变量全在esp_wifi_t结构体内。实测在STM32F103上,该任务CPU占用率<0.3%。
4. 常见问题排查:那些让你熬夜到凌晨三点的玄学故障
即使按上述步骤操作,你仍可能遇到“明明接线正确、AT指令手动发能通,但驱动就是连不上”的情况。以下是我在多个项目中踩过的坑,按现象→原因→解决三段式整理,每一条都对应真实复现场景。
4.1 现象:esp_wifi_connect()始终返回ESP_TIMEOUT,串口助手中能看到AT+CWJAP发出,但无任何响应
原因:ESP8266-01S模块的CH_PD引脚未拉高。该引脚是芯片使能端,低电平强制休眠,此时模块不响应任何AT指令。很多开发者只接了VCC/GND/TX/RX,忘了CH_PD。
解决:用万用表测CH_PD对地电压,应为3.3V。若为0V,将CH_PD接到STM32的任意GPIO(如PC13),并在esp_hal_uart_init()后添加:
RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOC, ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &GPIO_InitStruct); GPIO_SetBits(GPIOC, GPIO_Pin_13); // 拉高使能4.2 现象:AT+CWLAP能扫描到AP,但AT+CWJAP返回NO AP FOUND,而手机能正常连同一WiFi
原因:ESP8266固件版本过旧,不支持WPA2-Enterprise或某些信道带宽组合。常见于出厂固件(v0.9.2.2)。
解决:刷写最新AT固件(推荐ESP8266_NONOS_SDK_V2.2.1中的at_v130.0099.0020.bin)。刷写工具用ESP8266Flasher.exe,接线时GPIO0必须接地(下载模式),VCC接3.3V,TX/RX交叉,GND共地。刷完后断电重启,再发AT+GMR确认版本号。
4.3 现象:HTTP GET能成功,但POST JSON数据时,服务器返回400 Bad Request,抓包发现数据末尾多出乱码
原因:驱动默认使用\r\n作为HTTP头结束符,但某些服务器严格要求\r\n\r\n(空行)后才是body。而esp_http_post()函数中snprintf()拼接header时少了一个\r\n。
解决:打开Core/esp_api.c,定位esp_http_post()函数,在snprintf(header, ...)语句后插入:
// 修正HTTP头结束符 strcat(header, "\r\n"); // 确保header以\r\n\r\n结尾并确保Content-Length字段计算准确(strlen(json_payload)而非sizeof(json_payload))。
4.4 现象:模块连上WiFi后,esp_tcp_send()偶尔返回ESP_OK但服务器没收到数据,且esp_core_task()状态卡在ESP_WIFI_STATUS_CONNECTED不动
原因:TCP连接未启用TCP_NODELAY(禁用Nagle算法),小包被内核缓存合并。ESP8266在透传模式下,若发送间隔<200ms,会等待更多数据或超时才发。
解决:在esp_tcp_connect()成功后,立即发送AT+CIPSEND的透传关闭指令:
// 在esp_tcp_connect()返回ESP_OK后追加 esp_at_send_cmd("AT+CIPMODE=0", "OK", 1000); // 切回命令模式这样每次esp_tcp_send()都会走AT+CIPSEND指令,确保数据即时发出。
4.5 现象:FreeRTOS下osDelay(10)后esp_core_task()不再执行,系统卡死
原因:esp_core_task()中调用了osDelay()或vTaskDelay(),导致任务主动挂起,而状态机需要持续轮询。驱动设计原则是“状态机非阻塞”,所有延时必须由上层调度器控制。
解决:检查Core/esp_core.c中是否误用了osDelay()。正确做法是——所有延时操作(如AT指令超时)均由esp_hal_get_tick_count()获取当前毫秒数,与start_time比较实现软延时,绝不调用RTOS延时API。
5. 核心模块代码逐行剖析:esp_at_parser.c如何安全提取+IPD数据
当你用esp_tcp_recv()接收TCP数据时,ESP8266返回的原始AT流类似:
+IPD,123:{"sensor":"temp","value":25.6} OK其中+IPD,123:表示接下来有123字节数据,但实际recv_buf里混着AT前缀、数据、OK响应。很多驱动用strstr(recv_buf, "+IPD,")粗暴截取,结果在高并发场景下因缓冲区覆盖导致+IPD,被截断,解析失败。esp_at_parser.c用状态机解决这个问题,我们来逐行看关键实现。
5.1 解析器状态定义与初始化
// Core/esp_at_parser.c typedef enum { PARSE_STATE_IDLE, // 等待+IPD开头 PARSE_STATE_READ_LEN, // 已读到"+IPD,",等待数字 PARSE_STATE_READ_DATA, // 已知长度,读取指定字节数 PARSE_STATE_READ_OK // 等待OK结束 } parse_state_t; static parse_state_t parser_state = PARSE_STATE_IDLE; static uint16_t expected_len = 0; static uint16_t received_len = 0; static uint8_t* data_dest = NULL; void esp_at_parser_init(uint8_t* buffer, uint16_t len) { parser_state = PARSE_STATE_IDLE; expected_len = 0; received_len = 0; data_dest = buffer; // 指向用户提供的接收缓冲区 }这个设计的关键是:状态和长度变量全局静态,但数据目标地址由用户传入。这意味着你可以为不同TCP连接分配不同buffer,互不干扰。
5.2 核心解析循环:如何用单字节输入驱动状态迁移
esp_err_t esp_at_parser_input(uint8_t byte) { switch (parser_state) { case PARSE_STATE_IDLE: if (byte == '+') { parser_state = PARSE_STATE_READ_LEN; expected_len = 0; received_len = 0; } break; case PARSE_STATE_READ_LEN: if (byte >= '0' && byte <= '9') { expected_len = expected_len * 10 + (byte - '0'); } else if (byte == ':') { if (expected_len > 0 && data_dest != NULL) { parser_state = PARSE_STATE_READ_DATA; received_len = 0; } else { parser_state = PARSE_STATE_IDLE; // 格式错误,重置 } } break; case PARSE_STATE_READ_DATA: if (received_len < expected_len && data_dest != NULL) { data_dest[received_len++] = byte; } if (received_len >= expected_len) { parser_state = PARSE_STATE_READ_OK; } break; case PARSE_STATE_READ_OK: if (byte == 'O' || byte == 'K') { static uint8_t ok_buf[3] = {0}; ok_buf[0] = ok_buf[1]; ok_buf[1] = byte; if (ok_buf[0] == 'O' && ok_buf[1] == 'K') { return ESP_OK; // 成功提取数据 } } break; } return ESP_IN_PROGRESS; }这段代码的精妙在于:
- 不依赖缓冲区大小:
data_dest由用户传入,驱动不分配内存; - 不假设数据完整性:
+IPD,123:可能被拆成两帧到达,状态机自动跨帧续接; - 错误恢复快:若
+IPD,后跟非数字字符,立刻回到IDLE,不污染后续解析。
5.3 在esp_core_task()中如何调用解析器
// Core/esp_core.c static void esp_core_parse_rx_buffer(esp_wifi_t* wifi) { uint8_t byte; while (esp_hal_uart_recv(&byte) == ESP_OK) { // 从HAL层读一个字节 esp_err_t result = esp_at_parser_input(byte); if (result == ESP_OK) { // 数据已存入user_buffer,通知上层 if (wifi->tcp_callback) { wifi->tcp_callback(wifi->tcp_user_data, esp_at_parser_get_data(), esp_at_parser_get_len()); } } } }这里esp_at_parser_get_data()返回data_dest指针,esp_at_parser_get_len()返回received_len——上层无需关心解析过程,只管处理最终数据。这种“输入驱动状态机、输出结构化结果”的设计,正是工业级驱动与玩具Demo的本质区别。
6. 二次开发实战:给驱动增加HTTPS支持的3个关键改造点
很多物联网项目现在强制要求HTTPS,而原驱动只支持HTTP。你可能会想:“直接把AT+CIPSTART="SSL",...塞进去不就行了?”——不行。SSL握手涉及证书验证、密钥协商、加密缓冲区,远比普通TCP复杂。下面是我在一个温湿度上报项目中落地HTTPS的真实改造路径,聚焦最小侵入、最大复用。
6.1 改造点1:扩展TCP连接类型,兼容SSL模式
原esp_tcp_conn_t结构体只支持TCP,需新增SSL标识:
// Inc/esp_types.h typedef enum { ESP_PROTO_TCP, ESP_PROTO_SSL // 新增SSL协议类型 } esp_proto_t; // Inc/esp_wifi.h typedef struct { const char* host; uint16_t port; uint32_t timeout_ms; uint8_t keep_alive; esp_proto_t proto; // 新增字段,默认ESP_PROTO_TCP } esp_tcp_conn_t;然后在esp_tcp_connect()中根据proto分支处理:
// Core/esp_api.c esp_err_t esp_tcp_connect(esp_tcp_conn_t* conn, esp_tcp_handle_t* handle) { if (conn->proto == ESP_PROTO_SSL) { // SSL连接需先发AT+CIPSSLSIZE设置缓冲区(ESP8266-01S仅支持512B) esp_at_send_cmd("AT+CIPSSLSIZE=2", "OK", 1000); // 再发AT+CIPSTART="SSL",... snprintf(cmd, sizeof(cmd), "AT+CIPSTART=\"SSL\",\"%s\",%d", conn->host, conn->port); } else { snprintf(cmd, sizeof(cmd), "AT+CIPSTART=\"TCP\",\"%s\",%d", conn->host, conn->port); } // 后续逻辑复用原有AT+CIPSTATUS轮询 }注意:
AT+CIPSSLSIZE=2表示使用2KB SSL缓冲区(实际值2对应2048B),这是ESP8266-01S的上限。若证书过大,需提前用OpenSSL裁剪。
6.2 改造点2:重载HTTP API,自动处理SSL握手延迟
HTTPS握手比TCP连接慢3~5倍,原HTTP超时(5s)不够。需在esp_http_post()中动态调整:
// Core/esp_api.c esp_err_t esp_http_post(const char* url, const char* json_payload, http_resp_cb_t callback, void* user_data) { // ... 原有URL解析逻辑 // 关键:若目标端口为443,延长超时至15s uint16_t port = (port_str) ? atoi(port_str) : 80; uint32_t http_timeout = (port == 443) ? 15000 : 5000; // 后续发送HTTP请求时,用http_timeout替代硬编码5000 esp_at_send_cmd_with_timeout(http_cmd, "SEND OK", http_timeout); }6.3 改造点3:增加证书预置接口,避免每次连接都加载
ESP8266不支持动态证书加载,必须在固件中预置。驱动提供esp_ssl_set_cert()接口,将证书存入Flash特定扇区:
// Core/esp_api.c #define CERT_FLASH_ADDR 0x0801F000 // STM32F103最后16KB Flash esp_err_t esp_ssl_set_cert(const uint8_t* cert_data, uint16_t len) { if (len > 4096) return ESP_FAIL; // 限制证书大小 FLASH_Unlock(); FLASH_ClearPage(CERT_FLASH_ADDR); for (uint16_t i = 0; i < len; i += 2) { uint16_t halfword = ((uint16_t)cert_data[i+1] << 8) | cert_data[i]; FLASH_ProgramHalfWord(CERT_FLASH_ADDR + i, halfword); } FLASH_Lock(); return ESP_OK; }调用时机在main()初始化后:
// main.c extern const uint8_t server_cert_start[] asm("_binary_server_crt_start"); extern const uint8_t server_cert_end[] asm("_binary_server_crt_end"); esp_ssl_set_cert(server_cert_start, server_cert_end - server_cert_start);提示:证书文件需用
objcopy转为二进制:arm-none-eabi-objcopy -I binary -O elf32-littlearm server.crt server.crt.o
然后链接时加入工程,_binary_*符号由链接器自动生成。
从那以后我每次做物联网终端,都会在项目初期就把esp_ssl_set_cert()和AT+CIPSSLSIZE加进去——哪怕当时还不用HTTPS。因为等产品过认证时再补,往往要重构整个网络栈。希望帮到你。
本文还有配套的精品资源,点击获取