1. 为什么STM32需要JSON解析能力
在物联网和嵌入式设备爆发式增长的今天,JSON已经成为设备间通信的事实标准格式。我刚开始接触STM32与云平台对接时,发现大多数API接口都采用JSON格式传输数据。比如一个简单的温湿度传感器数据,云端通常要求这样的格式:
{ "device_id": "STM32F407_001", "sensor_data": { "temperature": 25.6, "humidity": 60.2 }, "timestamp": 1625097600 }传统嵌入式开发中,我们习惯用自定义的二进制协议或简单的字符串分隔方式。但实际项目中我深刻体会到,当需要与Web服务对接时,JSON的通用性能大幅降低开发门槛。有次项目紧急对接第三方平台,对方只提供JSON API,幸亏提前集成了cJSON库,两天就完成了协议对接。
cJSON作为轻量级解析库,在STM32F103这类Cortex-M3内核芯片上,完整解析一个20层嵌套的JSON文件仅需4KB RAM。相比之下,我测试过Jansson库虽然内存占用更小,但在处理复杂JSON时稳定性不如cJSON。特别是在资源受限环境下,cJSON的零拷贝设计能有效减少内存碎片问题。
2. cJSON库移植实战指南
2.1 获取与工程集成
直接从GitHub克隆最新源码:
git clone https://github.com/DaveGamble/cJSON.git关键文件只需要两个:
cJSON.c(约28KB)cJSON.h(约18KB)
我习惯在工程目录下新建Middlewares/cJSON文件夹存放这些文件。在Keil中添加源文件时,遇到过路径包含中文导致编译失败的情况,建议使用全英文路径。有个实用技巧:将cJSON文件属性设置为"Exclude from Build"先不编译,等配置完成再取消排除。
2.2 内存管理优化
默认的malloc/free在STM32上可能引发内存碎片。我的解决方案是使用静态内存池:
#define CJSON_POOL_SIZE 2048 static uint8_t cjson_mem_pool[CJSON_POOL_SIZE]; static size_t cjson_mem_offset = 0; void* cJSON_malloc(size_t size) { if(cjson_mem_offset + size > CJSON_POOL_SIZE) { printf("[ERROR] cJSON内存不足!\n"); return NULL; } void* ptr = &cjson_mem_pool[cjson_mem_offset]; cjson_mem_offset += size; return ptr; } void cJSON_free(void* ptr) { // 静态池无需释放 }在cJSON.h开头添加:
#define cJSON_malloc my_malloc #define cJSON_free my_free2.3 常见编译问题解决
遇到__use_no_semihosting错误时,在usart.c添加:
// 解决半主机模式错误 void _ttywrch(int ch) { ch = ch; }如果出现堆栈溢出,修改启动文件startup_stm32fxxx.s中的堆栈大小:
Stack_Size EQU 0x00001000 → 0x000020003. JSON数据解析实战技巧
3.1 基础解析示例
解析这个气象站数据:
char* json_str = "{\"device\":\"WS-100\",\"readings\":[{\"type\":\"temp\",\"value\":23.5},{\"type\":\"humi\",\"value\":65}]}"; cJSON* root = cJSON_Parse(json_str); if(!root) { printf("解析错误: %s\n", cJSON_GetErrorPtr()); return; } // 获取设备名 cJSON* device = cJSON_GetObjectItem(root, "device"); printf("设备: %s\n", device->valuestring); // 遍历传感器数组 cJSON* readings = cJSON_GetObjectItem(root, "readings"); int array_size = cJSON_GetArraySize(readings); for(int i=0; i<array_size; i++) { cJSON* item = cJSON_GetArrayItem(readings, i); cJSON* type = cJSON_GetObjectItem(item, "type"); cJSON* value = cJSON_GetObjectItem(item, "value"); printf("%s: %.1f\n", type->valuestring, value->valuedouble); } cJSON_Delete(root); // 必须释放内存3.2 高效解析技巧
- 错误处理增强版:
cJSON* root = cJSON_ParseWithLength(json_str, strlen(json_str)); if(!root) { const char* error_ptr = cJSON_GetErrorPtr(); if(error_ptr) { printf("错误位置: %.*s\n", 20, error_ptr); } return; }- 批量提取数据:
typedef struct { float temp; float humi; } SensorData; void parse_sensor_data(cJSON* root, SensorData* out) { cJSON* readings = cJSON_GetObjectItem(root, "readings"); cJSON* item; cJSON_ArrayForEach(item, readings) { cJSON* type = cJSON_GetObjectItem(item, "type"); cJSON* value = cJSON_GetObjectItem(item, "value"); if(strcmp(type->valuestring, "temp") == 0) { out->temp = value->valuedouble; } else if(strcmp(type->valuestring, "humi") == 0) { out->humi = value->valuedouble; } } }4. JSON数据生成高级应用
4.1 动态生成复杂JSON
构建一个设备状态报告:
cJSON* root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "device_id", "STM32F407_001"); // 添加嵌套对象 cJSON* status = cJSON_CreateObject(); cJSON_AddNumberToObject(status, "battery", 85); cJSON_AddBoolToObject(status, "connected", true); cJSON_AddItemToObject(root, "status", status); // 添加数组 cJSON* sensors = cJSON_CreateArray(); cJSON* sensor1 = cJSON_CreateObject(); cJSON_AddStringToObject(sensor1, "type", "temperature"); cJSON_AddNumberToObject(sensor1, "value", 25.6); cJSON_AddItemToArray(sensors, sensor1); cJSON* sensor2 = cJSON_CreateObject(); cJSON_AddStringToObject(sensor2, "type", "humidity"); cJSON_AddNumberToObject(sensor2, "value", 60.2); cJSON_AddItemToArray(sensors, sensor2); cJSON_AddItemToObject(root, "sensors", sensors); // 输出紧凑型JSON char* json_str = cJSON_PrintUnformatted(root); printf("%s\n", json_str); // 释放资源 cJSON_free(json_str); cJSON_Delete(root);4.2 内存优化技巧
- 使用静态缓冲区:
char buffer[512]; cJSON* root = cJSON_CreateObject(); // ...构建JSON对象... cJSON_PrintPreallocated(root, buffer, sizeof(buffer), true);- 复用JSON对象:
cJSON* root = cJSON_CreateObject(); for(int i=0; i<10; i++) { cJSON_DeleteItemFromObject(root, "data"); // 删除旧数据 cJSON_AddNumberToObject(root, "data", i); // 使用数据... }5. 真实项目中的经验分享
在工业传感器项目中,我们遇到JSON解析性能瓶颈。通过以下优化将解析时间从15ms降到3ms:
- 预解析关键路径:
// 提前获取常用路径 cJSON* GetNestedItem(cJSON* root, const char* path) { char* token = strtok((char*)path, "."); cJSON* curr = root; while(token && curr) { curr = cJSON_GetObjectItem(curr, token); token = strtok(NULL, "."); } return curr; } // 使用示例 cJSON* temp = GetNestedItem(root, "payload.sensors.temperature");- 选择性解析:
// 只解析需要的字段 void FastParse(const char* json, float* out_temp, float* out_humi) { cJSON* root = cJSON_Parse(json); *out_temp = cJSON_GetObjectItem(root, "temp")->valuedouble; *out_humi = cJSON_GetObjectItem(root, "humi")->valuedouble; cJSON_Delete(root); }- 内存泄漏检测:
#ifdef DEBUG #define CJSON_TRACK_MEMORY size_t cjson_mem_used = 0; void* debug_malloc(size_t size) { cjson_mem_used += size; printf("[MEM] Alloc %zu bytes (total: %zu)\n", size, cjson_mem_used); return malloc(size); } void debug_free(void* ptr) { // 注意: 实际项目中需要记录释放大小 printf("[MEM] Freed\n"); free(ptr); } #endif