ESP8266-01S学习笔记

这里写目录标题

  • [传模式 vs 非透传模式解析JSON的差异](#传模式 vs 非透传模式解析JSON的差异)

传模式 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"
    }
  }]
}

解析步骤

  1. 接收数据包含+IPD前缀
  2. 需要先提取长度信息486
  3. 然后跳过冒号:
  4. 再接收486字节的HTTP响应
  5. 再从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"
    }
  }]
}

解析步骤

  1. 直接接收原始HTTP响应
  2. 不需要处理+IPD前缀
  3. 直接查找JSON开始位置
  4. 解析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 为什么需要调整堆栈大小?

标准库开发时,如果使用以下功能需要调整堆栈:

  1. cJSON库 :使用malloc()动态分配内存
  2. printf重定向 :使用malloc()(如果启用浮点数支持)
  3. 网络缓冲:较大的数据缓冲区
  4. 递归函数:多层递归调用
  5. 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分钟更新一次
    }
}

三、实际建议

针对您的需求:

  1. 模式选择 :建议使用非透传模式,因为:

    • 有明确的数据长度信息
    • 连接状态可控
    • 更适合HTTP这种短连接场景
  2. 堆栈设置

    • 如果只使用标准库,不修改startup也能工作
    • 但建议将Heap_Size增加到0x800(2KB)
    • 这是最小的安全修改
  3. 内存策略

    • 使用静态缓冲区避免malloc
    • 限制JSON解析深度
    • 合理设置缓冲区大小
  4. 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响应解析

这样既能保持代码的简洁性,又能确保系统的稳定性。

相关推荐
jimmyleeee2 小时前
人工智能基础知识笔记三十四:提升RAG效果的几种技术
人工智能·笔记
keep_moving_20233 小时前
uvm_mem_mam介绍
学习
电子阿板3 小时前
STM32G0B1 NRST复位和其它IO复用了,如何设置成专用复位引脚,
stm32·单片机·嵌入式硬件
兆龙电子单片机设计3 小时前
【STM32项目开源】STM32单片机智慧农业大棚控制系统
stm32·单片机·物联网·开源·毕业设计
再卷还是菜3 小时前
网安渗透学习小结--PHP源码调试方法,文件包含漏洞,PHP伪协议以及ctf题目解答
学习
Lonely 净土3 小时前
渗透学习笔记-前四天
笔记·学习
蓝桉~MLGT3 小时前
中级软考(软件工程师)第四章知识点——操作系统
学习
TensorFlowGAN3 小时前
渗测随堂总结笔记 1(未完,一二章+三章 01)
笔记
bai5459363 小时前
STM32 CubeIDE 串口通信
stm32·单片机·嵌入式硬件