这里写目录标题
- [传模式 vs 非透传模式解析JSON的差异](#传模式 vs 非透传模式解析JSON的差异)
-
- [一、透传模式 vs 非透传模式的JSON解析](#一、透传模式 vs 非透传模式的JSON解析)
-
- [1.1 两种模式下的串口数据格式对比](#1.1 两种模式下的串口数据格式对比)
- [1.2 两种模式对比表](#1.2 两种模式对比表)
- [1.3 透传模式设置代码](#1.3 透传模式设置代码)
- 二、堆栈设置问题详解
-
- [2.1 为什么需要调整堆栈大小?](#2.1 为什么需要调整堆栈大小?)
- [2.2 startup文件中的堆栈设置](#2.2 startup文件中的堆栈设置)
- [2.3 如何不修改startup文件也能工作?](#2.3 如何不修改startup文件也能工作?)
- [2.4 完整的标准库开发代码(不修改startup)](#2.4 完整的标准库开发代码(不修改startup))
- 三、实际建议
传模式 vs 非透传模式解析JSON的差异
一、透传模式 vs 非透传模式的JSON解析
1.1 两种模式下的串口数据格式对比
非透传模式(默认模式)
c
// ESP8266接收到的HTTP响应格式
+IPD,<length>:<data>
// 实际示例:
+IPD,486:HTTP/1.1 200 OK
Date: Mon, 17 Jan 2026 08:00:00 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 385
Connection: close
{
"results": [{
"location": {"name": "北京"},
"now": {
"text": "晴",
"temperature": "15",
"humidity": "45"
}
}]
}
解析步骤:
- 接收数据包含
+IPD前缀 - 需要先提取长度信息
486 - 然后跳过冒号
: - 再接收486字节的HTTP响应
- 再从HTTP响应中提取JSON部分
c
// 非透传模式数据解析示例
int parse_non_transparent_data(uint8_t* data, int len) {
char* ptr = (char*)data;
// 1. 检查是否以+IPD开头
if (strncmp(ptr, "+IPD,", 5) != 0) {
return -1;
}
// 2. 解析数据长度
ptr += 5; // 跳过"+IPD,"
int data_len = atoi(ptr);
// 3. 找到冒号位置
while (*ptr != ':' && *ptr != '\0') ptr++;
if (*ptr != ':') return -2;
ptr++; // 跳过冒号
// 4. ptr现在指向HTTP响应开始位置
char* json_start = strstr(ptr, "\r\n\r\n");
if (json_start) {
json_start += 4; // 跳过空行
cJSON* json = cJSON_Parse(json_start);
// 解析JSON...
}
return 0;
}
透传模式
c
// ESP8266接收到的HTTP响应格式(无+IPD前缀)
HTTP/1.1 200 OK
Date: Mon, 17 Jan 2026 08:00:00 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 385
Connection: close
{
"results": [{
"location": {"name": "北京"},
"now": {
"text": "晴",
"temperature": "15",
"humidity": "45"
}
}]
}
解析步骤:
- 直接接收原始HTTP响应
- 不需要处理+IPD前缀
- 直接查找JSON开始位置
- 解析JSON
c
// 透传模式数据解析示例
int parse_transparent_data(uint8_t* data, int len) {
char* ptr = (char*)data;
// 直接查找HTTP头结束位置
char* json_start = strstr(ptr, "\r\n\r\n");
if (json_start) {
json_start += 4; // 跳过空行
cJSON* json = cJSON_Parse(json_start);
// 解析JSON...
}
return 0;
}
1.2 两种模式对比表
| 特性 | 非透传模式 | 透传模式 |
|---|---|---|
| 数据前缀 | 有+IPD,长度:前缀 |
无前缀,原始数据 |
| 解析难度 | 较复杂,需解析长度 | 较简单,直接处理 |
| 数据完整性 | 自动分包,带长度信息 | 纯流式,需自己处理边界 |
| 内存管理 | 知道数据长度,易于缓冲 | 不知道长度,需动态缓冲 |
| 错误处理 | AT指令有明确响应 | 无明确响应,需超时判断 |
| 连接控制 | 可随时关闭/查询连接 | 需发送+++退出透传 |
| 多连接 | 支持最多4个连接 | 只支持单连接 |
| 稳定性 | 较高,有ACK确认 | 较低,无确认机制 |
| 推荐场景 | HTTP请求等短连接 | 长连接、MQTT、WebSocket |
1.3 透传模式设置代码
c
// 设置ESP8266为透传模式
void esp8266_set_transparent_mode(void) {
char cmd[64];
// 1. 设置工作模式
esp8266_send_command("AT+CWMODE=1", 2000);
// 2. 连接WiFi
sprintf(cmd, "AT+CWJAP=\"%s\",\"%s\"", WIFI_SSID, WIFI_PASSWORD);
esp8266_send_command(cmd, 10000);
// 3. 设置单连接模式
esp8266_send_command("AT+CIPMUX=0", 2000);
// 4. 开启透传模式
esp8266_send_command("AT+CIPMODE=1", 2000);
// 5. 建立TCP连接
sprintf(cmd, "AT+CIPSTART=\"TCP\",\"%s\",%d", SERVER_IP, SERVER_PORT);
esp8266_send_command(cmd, 5000);
// 6. 开始透传
esp8266_send_command("AT+CIPSEND", 2000);
// 现在进入透传模式,所有串口数据直接发送到服务器
// 要退出透传,需发送"+++"(不带引号)
}
// 退出透传模式
void esp8266_exit_transparent_mode(void) {
// 发送退出透传序列
HAL_Delay(1000);
HAL_UART_Transmit(&huart3, (uint8_t*)"+++", 3, 1000);
HAL_Delay(1000);
// 等待ESP8266响应
char response[64];
if (esp8266_wait_response("OK", 2000)) {
printf("已退出透传模式\n");
}
}
二、堆栈设置问题详解
2.1 为什么需要调整堆栈大小?
标准库开发时,如果使用以下功能需要调整堆栈:
- cJSON库 :使用
malloc()动态分配内存 - printf重定向 :使用
malloc()(如果启用浮点数支持) - 网络缓冲:较大的数据缓冲区
- 递归函数:多层递归调用
- RTOS任务:每个任务有自己的栈
2.2 startup文件中的堆栈设置
assembly
; startup_stm32f103xe.s 中的关键配置
Stack_Size EQU 0x400 ; 默认1KB栈
Heap_Size EQU 0x200 ; 默认512B堆
建议调整为:
assembly
Stack_Size EQU 0x800 ; 2KB栈(增大)
Heap_Size EQU 0x800 ; 2KB堆(增大)
2.3 如何不修改startup文件也能工作?
如果您想保持startup文件不变,可以采用以下策略:
方案1:使用静态分配替代动态分配
c
// 静态分配缓冲区,避免使用malloc
#define JSON_BUFFER_SIZE 1024
static char json_buffer[JSON_BUFFER_SIZE];
static cJSON static_cjson_nodes[20];
// 自定义malloc/free函数
void* my_malloc(size_t size) {
static uint8_t memory_pool[2048];
static size_t pool_index = 0;
if (pool_index + size > sizeof(memory_pool)) {
return NULL;
}
void* ptr = &memory_pool[pool_index];
pool_index += (size + 3) & ~3; // 4字节对齐
return ptr;
}
void my_free(void* ptr) {
// 静态内存池,不实际释放
}
// 在main中设置cJSON的内存函数
int main(void) {
cJSON_Hooks hooks = {
.malloc_fn = my_malloc,
.free_fn = my_free
};
cJSON_InitHooks(&hooks);
// ... 其他初始化代码
}
方案2:使用自定义cJSON版本
c
// 修改cJSON.h,使用静态内存池
#define CJSON_STATIC_POOL_SIZE 4096
static char cjson_memory_pool[CJSON_STATIC_POOL_SIZE];
static size_t cjson_pool_offset = 0;
void* cJSON_malloc(size_t size) {
if (cjson_pool_offset + size > CJSON_STATIC_POOL_SIZE) {
return NULL;
}
void* ptr = &cjson_memory_pool[cjson_pool_offset];
cjson_pool_offset += size;
return ptr;
}
void cJSON_free(void* ptr) {
// 静态内存池,不释放
}
2.4 完整的标准库开发代码(不修改startup)
c
/* main.c - 标准库风格,不依赖大堆 */
#include "main.h"
#include <string.h>
#include <stdio.h>
#include "cJSON.h"
// 自定义内存管理
#define STATIC_POOL_SIZE 2048
static uint8_t memory_pool[STATIC_POOL_SIZE];
static size_t pool_index = 0;
void* static_malloc(size_t size) {
if (pool_index + size > STATIC_POOL_SIZE) {
return NULL;
}
void* ptr = &memory_pool[pool_index];
pool_index += (size + 3) & ~3; // 4字节对齐
return ptr;
}
void static_free(void* ptr) {
// 静态内存池,不释放
}
// HTTP响应缓冲区(静态分配)
#define HTTP_BUFFER_SIZE 1024
static char http_buffer[HTTP_BUFFER_SIZE];
// ESP8266 AT指令发送
void esp8266_send_at(const char* cmd, int timeout_ms) {
HAL_UART_Transmit(&huart3, (uint8_t*)cmd, strlen(cmd), timeout_ms);
HAL_UART_Transmit(&huart3, (uint8_t*)"\r\n", 2, timeout_ms);
}
// 等待特定响应
int esp8266_wait_for(const char* expected, int timeout_ms) {
uint32_t start = HAL_GetTick();
int index = 0;
char response[128];
while (HAL_GetTick() - start < timeout_ms) {
if (HAL_UART_Receive(&huart3, (uint8_t*)&response[index], 1, 10) == HAL_OK) {
if (response[index] == '\n' || index >= 127) {
response[index] = '\0';
if (strstr(response, expected) != NULL) {
return 1;
}
index = 0;
} else {
index++;
}
}
}
return 0;
}
// 获取天气数据(非透传模式)
int get_weather_non_transparent(void) {
char cmd[128];
// 1. 建立TCP连接
esp8266_send_at("AT+CIPSTART=\"TCP\",\"api.seniverse.com\",80", 5000);
if (!esp8266_wait_for("CONNECT", 5000)) return -1;
// 2. 构建HTTP请求
const char* request =
"GET /v3/weather/now.json?key=YOUR_KEY&location=beijing HTTP/1.1\r\n"
"Host: api.seniverse.com\r\n"
"Connection: close\r\n\r\n";
sprintf(cmd, "AT+CIPSEND=%d", strlen(request));
esp8266_send_at(cmd, 2000);
if (esp8266_wait_for(">", 1000)) {
esp8266_send_at(request, 2000);
}
// 3. 接收响应
HAL_Delay(2000);
int total_len = 0;
// 持续接收直到没有数据
while (1) {
if (HAL_UART_Receive(&huart3, (uint8_t*)http_buffer + total_len, 1, 100) != HAL_OK) {
break;
}
if (total_len < HTTP_BUFFER_SIZE - 1) {
total_len++;
} else {
break; // 缓冲区满
}
}
http_buffer[total_len] = '\0';
// 4. 解析响应
return parse_http_response(http_buffer);
}
// 解析HTTP响应
int parse_http_response(const char* response) {
// 查找JSON开始位置
const char* json_start = strstr(response, "\r\n\r\n");
if (!json_start) json_start = strstr(response, "\n\n");
if (!json_start) return -1;
json_start += 2; // 跳过空行
// 使用cJSON解析
cJSON_Hooks hooks = {
.malloc_fn = static_malloc,
.free_fn = static_free
};
cJSON_InitHooks(&hooks);
cJSON* root = cJSON_Parse(json_start);
if (!root) return -2;
// 提取数据...
cJSON* results = cJSON_GetObjectItem(root, "results");
if (results && cJSON_IsArray(results)) {
cJSON* first = cJSON_GetArrayItem(results, 0);
if (first) {
cJSON* location = cJSON_GetObjectItem(first, "location");
cJSON* now = cJSON_GetObjectItem(first, "now");
if (location && now) {
printf("City: %s\n", cJSON_GetObjectItem(location, "name")->valuestring);
printf("Temp: %s°C\n", cJSON_GetObjectItem(now, "temperature")->valuestring);
}
}
}
cJSON_Delete(root);
return 0;
}
int main(void) {
HAL_Init();
SystemClock_Config();
// 初始化串口
MX_USART1_UART_Init(); // 调试串口
MX_USART3_UART_Init(); // ESP8266串口
printf("Weather Station Starting...\n");
// 初始化ESP8266
esp8266_send_at("AT", 1000);
esp8266_wait_for("OK", 2000);
esp8266_send_at("ATE0", 1000); // 关闭回显
while (1) {
if (get_weather_non_transparent() == 0) {
printf("Weather data updated successfully\n");
} else {
printf("Failed to get weather data\n");
}
HAL_Delay(300000); // 5分钟更新一次
}
}
三、实际建议
针对您的需求:
-
模式选择 :建议使用非透传模式,因为:
- 有明确的数据长度信息
- 连接状态可控
- 更适合HTTP这种短连接场景
-
堆栈设置:
- 如果只使用标准库,不修改startup也能工作
- 但建议将Heap_Size增加到0x800(2KB)
- 这是最小的安全修改
-
内存策略:
- 使用静态缓冲区避免malloc
- 限制JSON解析深度
- 合理设置缓冲区大小
-
ESP8266型号:
- 正点原子ATK-ESP8266推荐使用
- 如果是ESP-01S,注意GPIO较少
- ESP-12F更适合,有更多GPIO
最简单实用的方案:
c
// 1. startup.s中修改:
// Heap_Size EQU 0x800 (2KB足够)
// Stack_Size EQU 0x800
// 2. 使用非透传模式
// 3. 使用标准库+CJSON
// 4. 注意HTTP响应解析
这样既能保持代码的简洁性,又能确保系统的稳定性。