首页/新闻资讯/正文详情

STM32移植cJSON解析JSON:从串口收包到结构体实战

发布时间:2026/11/4 13:13:52 来源:云帆数科 栏目:资讯中心
STM32移植cJSON解析JSON:从串口收包到结构体实战
简介这份资源面向在STM32平台上需要处理JSON数据的嵌入式开发者提供基于cjson库的解析与生成示例工程可直接用于项目中解决单片机与上位机或云端进行轻量级数据交换时的格式处理问题。压缩包共133个文件约1.76MB以31个c源文件与31个h头文件为核心涵盖STM32标准外设库与cjson实现另含Keil工程文件、编译中间产物及readme说明工程结构完整、层次清晰。已有3250人学习下载说明其在嵌入式JSON应用场景中具有较高的参考价值。读者可从中获得一套经过验证的cjson移植与调用范例理解JSON字符串的解析流程、内存配置方式及数据生成方法并借助现成工程快速搭建自己的通信数据处理模块减少从零调试库文件的时间成本。1. 串口收到一包 JSONSTM32 该怎么把它拆开做嵌入式联网项目最常遇到的场景就是模组吐回来一串 JSON{temp:26.5,hum:61,ok:true}。字符串是拿到了可接下来要取值、要判断、要存进结构体靠strstr加sscanf硬抠字段一多、嵌套一层代码立刻变成一坨谁都不敢动的玄学。这份资源就是一个把 cJSON 移植进 STM32 工程、用来解析 JSON 字符串的完整示例包适合正在做 4G 模组通信、MQTT 上报、上位机协议对接的嵌入式开发者。它解决的不是“JSON 是什么”而是“在只有几十 KB RAM 的单片机上怎么安全地把这包字符串变成能用的变量”。2. 工程结构与移植思路cJSON 是怎么塞进 STM32 的拿到压缩包先别急着编译先看清楚它凭什么能在单片机上跑。cJSON 本身是纯 C 写的不依赖操作系统、不依赖标准库的重型部分核心就两个文件这正是它能被塞进 STM32 的根本原因。整个示例工程的目录组织大致如下不同 IDE 建工程时文件名可能略有差异但结构是一致的。stm32_cjson_demo/ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ ├── cJSON.h // cJSON 头文件 │ │ └── usart.h │ └── Src/ │ ├── main.c // 解析入口业务代码在这 │ ├── cJSON.c // cJSON 实现约 3000 行 │ └── usart.c // 串口收发负责把字节拼成字符串 ├── Drivers/ // HAL 库CubeMX 生成 └── MDK-ARM/ 或 STM32CubeIDE/ // 工程文件2.1 为什么选 cJSON 而不是自己写解析器自己写解析器听起来很酷实际做起来是血泪经验转义字符、浮点指数、嵌套数组、空值随便一个没处理对现场就翻车。cJSON 的价值在于它把 JSON 抽象成一棵 DOM 树解析完你拿到的是一串cJSON节点取值就是按名字找孩子节点逻辑清晰、可维护。它的内存模型是“解析时按需 malloc用完统一 delete”对 STM32 来说这意味着必须配好堆否则解析到一半返回 NULL你还以为是数据格式错了。移植时只需要把cJSON.c和cJSON.h加进工程确保cJSON.c里用到的malloc/free/strlen/memcpy这些函数在你的工程里可用。HAL 工程默认带标准 C 库一般直接能编过。如果用的是精简的 newlib-nano注意把printf浮点支持打开否则打印浮点字段会输出异常。2.2 堆空间配置cJSON 的命门cJSON 每解析一个对象、一个键值对都会申请内存。一个中等复杂度的报文解析峰值可能吃掉几 KB。STM32 的默认堆Heap Size在启动文件startup_stm32xxxx.s里配置CubeMX 生成的工程通常在sysmem.c或链接脚本里定义。常见做法是把 Heap 调到 0x10004KB以上具体看你的报文大小。/* 在 main.c 里可以先做个堆可用性自检避免上线才发现不够 */ extern int __io_putchar(int ch); void cjson_heap_check(void) { /* 连续申请再释放粗略探测当前可用堆 */ void *p malloc(2048); if (p NULL) { printf(heap not enough for 2KB\r\n); return; } free(p); printf(heap ok\r\n); }这段自检不是万能的但能在调试阶段快速暴露“堆配小了”这个最常见的问题。参数 2048 按你实际报文规模调整报文越大这个探测值越要贴近真实峰值。注意malloc失败返回 NULL 时不要继续解析直接丢弃这包数据重来比解析出半棵树再崩掉强。2.3 串口收包到字符串的衔接JSON 解析的前提是拿到一个完整的、以\0结尾的字符串。串口是字节流必须自己拼包。示例里通常用“收到换行符或固定长度”作为一帧结束标志。下面是一个中断收包加空闲判断的典型写法思路是收到字节先塞缓冲区主循环里检测到结束符再交给解析函数。#define RX_BUF_SIZE 256 uint8_t rx_byte; // 单字节中断接收 char json_buf[RX_BUF_SIZE]; // 拼包缓冲 volatile uint16_t json_len 0; volatile uint8_t json_ready 0; /* 串口中断回调HAL 库风格 */ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { if (rx_byte \n || json_len RX_BUF_SIZE - 1) { json_buf[json_len] \0; // 补字符串结束符 json_ready 1; // 通知主循环 json_len 0; } else { json_buf[json_len] rx_byte; } HAL_UART_Receive_IT(huart, rx_byte, 1); // 重新挂接收 } }这里的关键参数是RX_BUF_SIZE它必须大于你最长的一帧报文否则会被截断解析必然失败。json_buf[json_len] \0这一句不能省cJSON 靠它判断字符串结尾。json_ready用volatile修饰因为它在中断和主循环之间共享编译器优化时可能把它缓存进寄存器导致主循环永远看不到变化这是新手最容易踩的坑之一。3. 核心解析代码拆解从字符串到结构体的完整链路移植通了接下来是真正干活的部分。cJSON 的 API 不多但用错顺序就会内存泄漏或者拿到野指针。这一章按“解析 → 取值 → 释放”的顺序把链路走一遍每段都能直接抄进你的工程。3.1 解析入口与返回值判断cJSON_Parse是整个流程的起点它把字符串变成树失败返回 NULL。很多人拿到 NULL 就懵其实 cJSON 提供了cJSON_GetErrorPtr()告诉你解析到哪个位置出错调试时非常有用。#include cJSON.h void parse_json_frame(const char *frame) { cJSON *root cJSON_Parse(frame); if (root NULL) { /* 打印出错位置定位是数据问题还是拼包问题 */ printf(json parse error near: %s\r\n, cJSON_GetErrorPtr()); return; } /* 后续取值逻辑 */ handle_json_root(root); cJSON_Delete(root); // 必须释放否则每帧泄漏一棵树 }cJSON_Parse内部会跳过空白、校验括号配对、处理转义。返回的root是整棵树的根可能是对象也可能是数组。cJSON_GetErrorPtr()返回的是出错位置附近的指针打印出来能直接看到是哪个字符让解析器卡住了比盲猜高效得多。cJSON_Delete一定要在取值完成后调用它递归释放所有子节点漏掉就是慢性内存泄漏跑几小时就 OOM。3.2 按字段取值类型判断不能省拿到 root 之后取值前必须先确认节点类型。JSON 里数字可能是整数也可能是浮点布尔是true/false字符串带引号直接按错误类型取会得到 0 或者空指针。void handle_json_root(cJSON *root) { cJSON *item; /* 取温度浮点 */ item cJSON_GetObjectItemCaseSensitive(root, temp); if (cJSON_IsNumber(item)) { float temp (float)item-valuedouble; printf(temp%.1f\r\n, temp); } /* 取湿度整数 */ item cJSON_GetObjectItemCaseSensitive(root, hum); if (cJSON_IsNumber(item)) { int hum item-valueint; printf(hum%d\r\n, hum); } /* 取状态布尔 */ item cJSON_GetObjectItemCaseSensitive(root, ok); if (cJSON_IsBool(item)) { printf(ok%d\r\n, cJSON_IsTrue(item)); } /* 取字符串注意用 valuestring */ item cJSON_GetObjectItemCaseSensitive(root, dev); if (cJSON_IsString(item) item-valuestring ! NULL) { printf(dev%s\r\n, item-valuestring); } }cJSON_GetObjectItemCaseSensitive是大小写敏感的查找如果你的协议字段大小写不固定用cJSON_GetObjectItem。valuedouble和valueint是 cJSON 内部同时维护的两个值浮点字段用前者整数用后者。cJSON_IsBool判断后还要用cJSON_IsTrue取实际布尔值不能直接读valueint因为布尔节点的内部表示和数字不同。字符串字段一定要判valuestring ! NULL空字符串和缺失字段在 cJSON 里表现不同不判空可能拿到野指针。3.3 嵌套对象与数组的遍历真实报文很少是平铺的常见的是{sensor:{temp:26.5},list:[1,2,3]}这种结构。嵌套对象就是再往下找一层数组则要用cJSON_GetArraySize配合cJSON_GetArrayItem遍历。void handle_nested(cJSON *root) { /* 嵌套对象先取子对象再在子对象里取字段 */ cJSON *sensor cJSON_GetObjectItemCaseSensitive(root, sensor); if (cJSON_IsObject(sensor)) { cJSON *t cJSON_GetObjectItemCaseSensitive(sensor, temp); if (cJSON_IsNumber(t)) { printf(nested temp%.1f\r\n, t-valuedouble); } } /* 数组先取长度再按下标取 */ cJSON *list cJSON_GetObjectItemCaseSensitive(root, list); if (cJSON_IsArray(list)) { int n cJSON_GetArraySize(list); for (int i 0; i n; i) { cJSON *elem cJSON_GetArrayItem(list, i); if (cJSON_IsNumber(elem)) { printf(list[%d]%d\r\n, i, elem-valueint); } } } }cJSON_GetArraySize返回数组元素个数cJSON_GetArrayItem按下标取越界会返回 NULL所以循环里仍然要判类型。嵌套对象的关键是每一层都先判cJSON_IsObject因为如果上游字段缺失sensor就是 NULL直接往下取会崩。这套“逐层判空判类型”的写法看着啰嗦但它是嵌入式环境里最稳的姿势比事后查 hardfault 省事得多。3.4 反向操作用 cJSON 拼装上报报文解析只是半条链路很多项目还要把采集到的数据拼成 JSON 发出去。cJSON 的构造 API 是cJSON_CreateObject加cJSON_AddXxxToObject用完同样要cJSON_Delete但打印前要用cJSON_PrintUnformatted拿到紧凑字符串。char *build_report(float temp, int hum) { cJSON *root cJSON_CreateObject(); if (root NULL) return NULL; cJSON_AddNumberToObject(root, temp, temp); cJSON_AddNumberToObject(root, hum, hum); cJSON_AddBoolToObject(root, ok, 1); /* Unformatted 不带换行缩进省流量适合串口/模组发送 */ char *out cJSON_PrintUnformatted(root); cJSON_Delete(root); // 树释放了out 还在需要调用者 free return out; }cJSON_PrintUnformatted返回的字符串是malloc出来的调用者用完必须free(out)否则又是一处泄漏。如果报文不大、发送频率不高也可以直接用cJSON_PrintPreallocated打印到你自己的静态缓冲区彻底避开堆分配这在 RAM 紧张的型号上是很实用的技巧。参数上注意cJSON_AddNumberToObject对浮点默认保留精度有限需要更高精度时用cJSON_CreateNumber配合手动设置。4. 避坑与排查那些让解析莫名失败的细节代码能编过不代表能跑对。下面这几条是实际项目里反复出现的翻车点每条都按“现象 → 原因 → 解决”说清楚遇到问题时对着查。4.1 解析一直返回 NULL但报文肉眼看着没问题现象串口打印出来的 JSON 字符串完全正确cJSON_Parse却始终返回 NULL。原因多半是字符串里混进了不可见字符比如\r、模组自带的IPD前缀、或者拼包时残留的上一帧尾巴。解决在解析前打印字符串的十六进制逐字节确认拼包逻辑里在帧头做同步收到{才开始记录遇到}再结束而不是简单按换行切。4.2 跑一段时间后死机或返回 NULL现象刚上电正常跑几十分钟后解析开始失败最终 hardfault。原因几乎都是内存泄漏——某条分支里cJSON_Parse成功后提前 return没走到cJSON_Delete。解决把解析和释放用统一出口管理或者用goto cleanup模式保证任何路径都释放同时把 Heap 调大并定期打印剩余堆做监控。4.3 浮点字段取出来是 0 或者乱值现象temp字段明明是 26.5取出来是 0。原因有两种一是用了valueint去读浮点字段二是工程链接了精简 C 库strtod被裁掉导致 cJSON 解析浮点失败。解决浮点统一用valuedouble检查链接选项确保浮点解析函数没被优化掉必要时在 cJSON 配置里关掉不需要的特性减小依赖。4.4 中断里调用解析导致卡死现象把cJSON_Parse直接写进串口中断回调系统响应变慢甚至看门狗复位。原因cJSON 内部有malloc和较长的循环中断里做重活是大忌。解决中断只负责收字节和置标志解析放到主循环或独立任务里做这是嵌入式的基本纪律别图省事。4.5 中文或特殊字符解析后乱码现象JSON 里带中文取出来是乱码。原因cJSON 按 UTF-8 处理字符串如果你的源文件或串口数据是 GBK字节层面就对不上。解决统一全链路用 UTF-8发送端和接收端编码一致如果必须兼容 GBK在解析后自己做一次编码转换别指望 cJSON 帮你转。5. 二次开发改造把它变成你自己的协议解析层示例能跑通只是起点真正落地时要把它改造成适配自己协议的解析层。我的习惯是把“解析”和“业务”彻底分开解析层只负责把 JSON 转成一个固定的结构体业务层只认结构体这样协议字段变了只改一处。/* 定义业务结构体和 JSON 字段解耦 */ typedef struct { float temp; int hum; bool ok; char dev[32]; } sensor_data_t; /* 解析层JSON - 结构体返回 0 成功-1 失败 */ int json_to_sensor(const char *frame, sensor_data_t *out) { cJSON *root cJSON_Parse(frame); if (root NULL) return -1; cJSON *t cJSON_GetObjectItemCaseSensitive(root, temp); cJSON *h cJSON_GetObjectItemCaseSensitive(root, hum); cJSON *o cJSON_GetObjectItemCaseSensitive(root, ok); cJSON *d cJSON_GetObjectItemCaseSensitive(root, dev); int ret -1; if (cJSON_IsNumber(t) cJSON_IsNumber(h) cJSON_IsBool(o)) { out-temp (float)t-valuedouble; out-hum h-valueint; out-ok cJSON_IsTrue(o); if (cJSON_IsString(d) d-valuestring) { strncpy(out-dev, d-valuestring, sizeof(out-dev) - 1); out-dev[sizeof(out-dev) - 1] \0; } ret 0; } cJSON_Delete(root); return ret; }这样改造的好处是以后协议加字段只动json_to_sensor业务逻辑里全是sensor_data_t测试时可以直接构造结构体不用拼 JSON 字符串。strncpy后面手动补\0是必须的防止源字符串刚好填满缓冲区导致没有结束符。再进一步可以把字段名做成表驱动用一张{字段名, 类型, 偏移量}的表来批量解析字段再多也不用写一长串 if。验证方法很简单准备一组正常报文、一组缺字段报文、一组类型错误报文跑一遍看返回值是否符合预期尤其是缺字段时结构体里不能残留上一帧的旧值解析前先memset清零。还有一个实用技巧是给解析加超时和长度上限保护cJSON_ParseWithLength可以限定最大解析长度防止恶意或异常的超长报文把堆吃光。从那以后我每次移植 cJSON 到新板子都强制先跑一遍堆自检和异常报文测试再谈业务逻辑这个习惯帮我省掉了太多现场排查的时间。希望帮到你。本文还有配套的精品资源点击获取

相关推荐

奔驰CLA自动驾驶安全系统DRIVE AV核心技术解析
奔驰CLA自动驾驶安全系统DRIVE AV核心技术解析

1. 奔驰CLA安全性能突破背后的技术支撑 当奔驰CLA在2023年欧洲NCAP测试中获得历史最高分时,行业内的目光不约而同聚焦到了其搭载的DRIVE AV自动驾驶系统。这套由英伟达提供的解决方案,在主动安全领域实现了多项突破性创新。作为从业十余年的汽车电子工程… · 2026/11/4 13:13:52

Vocoflex测评:十秒干声建音色模型,AI歌声转换新思路
Vocoflex测评:十秒干声建音色模型,AI歌声转换新思路

做音乐制作这几年,AI 人声工具我换了一茬又一茬。最早玩拼接式引擎的时候,一个音色要抠半天参数;后来 Synthesizer V 的神经网络声库一路更新,说实话已经到能直接进混音的程度了。所以当开发 Synthesizer V 的 Dreamtonics 发布 V… · 2026/11/4 13:13:52

零训练激活引导:用残差流方向向量控制大模型行为
零训练激活引导:用残差流方向向量控制大模型行为

1. 先想清楚动手的地方:为什么是激活值,而不是权重 第一次接触 Activation Steering 是在一个很具体的需求上:团队要把一个中等规模开源基座的输出风格压得更克制、更正式,同时还要降低它在某些话题上过度发挥的倾向。走常规路线&… · 2026/11/4 13:13:52

RH2288HV3 BIOS升级实战:从拆包到刷写与排错
RH2288HV3 BIOS升级实战:从拆包到刷写与排错

简介:面向企业运维人员与数据中心管理员,这份资源提供华为RH2288HV3机架服务器的BIOS固件更新包,用于修复底层缺陷、提升系统稳定性与硬件兼容性,适合需要定期维护企业级服务器、通过iBMC远程完成固件升级的读者。压缩包共4个文件… · 2026/11/4 14:59:39

主机折腾前的安全底线:数据备份、系统稳定与账号防护全指南
主机折腾前的安全底线:数据备份、系统稳定与账号防护全指南

身边不少朋友在主机上栽过跟头,最惨的一次是某位朋友把攒了两年的存档丢干净了,前一天还在打最终章,第二天开机发现存档列表空了一半,人直接懵在沙发上。这类事听起来像小概率,实际上每年都在重复发生:一次… · 2026/11/4 14:59:39

从基础到高级:排序算法学习路线与快速排序实践
从基础到高级:排序算法学习路线与快速排序实践

1. 算法学习日记:从基础排序到快速排序的跨越 作为一名从零开始学习算法的小白,我深刻理解那种面对复杂算法时的困惑和挫败感。前两天还在轻松搞定选择、冒泡、插入这些基础排序算法,突然就跳到了计数排序、基数排序和快速排序这些"大块… · 2026/11/4 14:59:39

AgentScope 多智能体框架实战:从单机 Demo 到企业级应用
AgentScope 多智能体框架实战:从单机 Demo 到企业级应用

多智能体系统这两年从论文里的概念快速变成了工程团队手里的真实工具,但真正落到代码层面,很多人第一反应还是"自己撸一个调度循环"。我一开始也这么干过,用几十行 Python 拼了个消息转发器,跑两个 Agent 对话还行&… · 2026/11/4 14:59:39

后见之明:从认知偏差到AI反思机制,别再让复盘沦为马后炮
后见之明:从认知偏差到AI反思机制,别再让复盘沦为马后炮

1. 为什么"早知道"这三个字,几乎贯穿所有失败复盘"早知道当时就多拿两块地""早知道那轮融资就不该犹豫""早知道这个用户场景根本不该做"——你敢说你没说过类似的话吗?我自己就有过太多次这种瞬间,事… · 2026/11/4 14:59:39

AI Agent知识获取管道:RAG系统设计与工程落地全解析
AI Agent知识获取管道:RAG系统设计与工程落地全解析

1. 这不是“加个RAG就完事”的技术补丁,而是AI Agent知识生命力的血管系统你有没有遇到过这样的场景:一个精心设计的AI Agent,在处理用户关于公司内部报销政策的问题时,自信满满地引用了三年前已废止的旧版流程;或者在… · 2026/11/4 14:58:34

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38

AI辅助技术栈评估:提升研发决策效率60%
AI辅助技术栈评估:提升研发决策效率60%

1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17

从生成Token到直接决策:Jev如何重塑AI Agent工程实践
从生成Token到直接决策:Jev如何重塑AI Agent工程实践

1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17

32G统一内存跑大模型:内存账、TPS实测与端云分流
32G统一内存跑大模型:内存账、TPS实测与端云分流

想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码