共计 6385 个字符,预计需要花费 16 分钟才能阅读完成。
在现代嵌入式系统开发中,数据交换和通信变得日益重要尤其是在单片机设备接入云端的过程中经常会用到构建和解析 JSON 格式。并且 JSON(JavaScript Object Notation)作为一种轻量级的数据交换格式,因其简洁、易于阅读和编写,以及跨平台兼容性的优势,在嵌入式系统中得到了广泛应用。本文将详细介绍如何在 STM32 微控制器上使用 cJSON 库来生成和解析 JSON 数据,为开发者们提供一个实用的指南。
一、准备工作
1. cJSON 库简介
cJSON 是一个轻量级的、易于使用的 C 语言库,用于解析和生成 JSON 数据。它只包含两个文件:cJSON.c
和cJSON.h
,非常适合资源受限的嵌入式系统。cJSON 的设计目标是简单、快速和高效,因此它非常适合在 STM32 这样的微控制器上运行。
2. 硬件环境
- STM32 微控制器(本次采用 STM32F103RCT6)
- 串口通信模块
3. 软件环境
- Keil uVision IDE(或其他支持 STM32 的 IDE)
- STM32CubeMX(可选,用于配置硬件)
- cJSON 库文件(
cJSON.c
和cJSON.h
) - 工程采用之前制作的串口模版 STM32-HAL 库串口空闲中断 DMA 接收数据 Demo-CSDN 博客
二、实现步骤
1. 导入添加 cJSON 库
添加 cjson.c 和 cjson.h 文件到工程中,工程中相关文件放在工程目录下 User 文件夹下。
在 json 文件中可以看到已经封装好大量函数,在使用过程中只需要包含 cjson.h 文件便可以调用构建和解析 json 的函数
2. 创建 JSON 字符串
2.1. 基本用法
在 STM32 上创建一个 JSON 对象并添加数据非常简单。首先,需要创建一个 cJSON
对象,然后使用 cJSON 提供的 API 函数向其中添加数据。如下代码所示能够在 json 对象中分别添加字符串 string 类型、数字以及 bool 类型:
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "name", "STM32");
cJSON_AddNumberToObject(root, "age",5);
cJSON_AddBoolToObject(root, "is_running", cJSON_True);
创建好 JSON 对象后,需要将其转换为字符串以便于传输或存储。cJSON 提供了 cJSON_Print
函数来完成这一任务。
char *json_string = cJSON_Print(root);
printf("%sn", json_string);
现在就可以输出一个完整的 json 字符串:
{
"name": "stm32",
"age": 5,
"is_running": true
}
在实际使用过程中需要考虑到单片机内存空间问题,如果创建失败可能会导致内存泄漏等问题,所以在单片使用时需要增加相关判断和错误处理
2.2. 单片机中 cJSON 的用法
下方 json 字符串为华为云 IoTDA 中设备上报属性的格式,在 json 字符串中嵌套 json 字符串
{
"name": "environment",
"id": 1,
"params": {
"temp": 23.5,
"humi": 50,
"o2": 21
}
}
在实现前需要确保 stm32 单片机的堆空间足够大,保证后续 json 构建能够成功。
如下代码能够实现了 cJSON 对象的创建、填充、转换为字符串、打印以及最终的内存释放。
void getJsonData(void)
{cJSON *pOrder = cJSON_CreateObject();
cJSON *params = cJSON_CreateObject();
char *json_body = NULL;
if (pOrder == NULL || params == NULL)
{printf("Creat ENV JSONobj Errn");
cJSON_Delete(pOrder);
cJSON_Delete(params);
return;
}
// 添加参数到 params 对象,并检查每个调用的返回值
if (!cJSON_AddNumberToObject(params,"temp",23.5)||
!cJSON_AddNumberToObject(params,"humi",50) ||
!cJSON_AddNumberToObject(params,"o2",21))
{printf("Add ENV data to JSONobj Errn");
cJSON_Delete(pOrder);
cJSON_Delete(params);
return;
}
// 添加 params 到 pOrder 对象
cJSON_AddStringToObject(pOrder, "name", "environment");
cJSON_AddNumberToObject(pOrder, "id", 1);
cJSON_AddItemToObject(pOrder, "params", params);
// 转换 JSON 对象为字符串
json_body = cJSON_PrintUnformatted(pOrder);
if (json_body == NULL)
{printf("Print ENV JSON Errn");
cJSON_Delete(pOrder);
return;
}
// 打印并发送 JSON 字符串
printf("ENV json: %s rn", json_body);
cJSON_free(json_body);
cJSON_Delete(pOrder);
}
- 错误检查:在创建 cJSON 对象后,检查
pOrder
和params
是否为NULL
。因为内存分配可能会失败,尤其是在资源受限的环境中。 - 参数添加:使用
cJSON_AddNumberToObject
添加了数字类型的参数到params
对象中,并检查了每个调用的返回值,能够确保数据正确添加到 JSON 对象中的好方法。 - 对象嵌套:使用
cJSON_AddItemToObject
将params
对象作为子项添加到了pOrder
对象中。params
的所有权转移给了pOrder
,因此在后续调用 cJSON_Delete 删除时不需要在之后单独删除params
。 - 字符串转换:使用
cJSON_PrintUnformatted
将pOrder
对象转换为了一个未格式化的字符串,并检查了转换是否成功。 - 内存释放:最后释放了转换后的字符串
json_body
所占用的内存,以及整个pOrder
对象及其所有子对象所占用的内存。
3. 解析 JSON 字符串
3.1. 基本用法
接收到的 JSON 字符串可以使用 cJSON_Parse
函数进行解析。解析后的结果是一个 cJSON
对象,你可以通过 cJSON_GetObjectItem
等函数来访问其中的数据。
cJSON *parsed_json = cJSON_Parse(json_string);
if (parsed_json == NULL) {
// 解析失败处理
printf("Parse errorn");
} else {cJSON *name = cJSON_GetObjectItem(parsed_json, "name");
if (name != NULL) {printf("Name: %sn", name->valuestring);
}
// 继续解析其他数据...
cJSON_Delete(parsed_json);
}
3.2. 单片机中的用法
在 stm32 单片机中需要先判断接收到的字符串是否完整,并对一些错误进行防范。解析将以华为云 IoTDA 中应用侧下发的控制命令为例,其命令格式如下:
{
"object_device_id": 123,
"command_name": "LED",
"service_id": "WaterMeter",
"paras": {
"sw": true,
"val": 50
}
}
其中 paras 中的 sw 代表开关,val 代表亮度值,解析代码如下,第一部分先查找接收到的数据中完整的 json 字符串,第二部分分配内存空间复制保存 json 数据,第三部分解析 json 数据
int8_t Parse_MqttCmd(uint8_t *data)
{
// 寻找 JSON 数据的开始位置
const char *json_start = strstr((char *)data, "{");
if (json_start == NULL)
{printf("JSON data not found in the received string.n");
return -1;
}
size_t json_length = strlen(json_start);
// 分配内存并复制 JSON 数据
char *json_data = (char *)malloc(json_length + 1);
if (json_data == NULL) {printf("Memory allocation failed.n");
return -1;
}
strncpy(json_data, json_start, json_length);
json_data[json_length] = ' '; // 添加 null 终止符
// 解析 JSON 数据
cJSON *root = cJSON_Parse(json_data);
if (root == NULL)
{printf("Failed to parse JSON data.n");
cJSON_free(json_data);
return -1;
}
// ... 在这里处理 JSON 数据 ...
// 获取并打印 "command_name" 字段的值
cJSON *name = cJSON_GetObjectItemCaseSensitive(root, "command_name");
// 判断 "command_name" 字段的值选择控制类型
if (name && cJSON_IsString(name) && (name->valuestring != NULL))
{char * command_name = name->valuestring;
printf("Name: %s rn", command_name);
// 灯光控制命令
if(strstr(name->valuestring,"LED"))
{cJSON *paras = cJSON_GetObjectItemCaseSensitive(root, "paras");/* 获取 obj 中的 paras 的 json*/
if (paras && cJSON_IsObject(paras))
{cJSON *sw_item = cJSON_GetObjectItemCaseSensitive(paras, "sw");/* 开关状态 */
if (sw_item != NULL && cJSON_IsBool(sw_item))
{int sw_value = sw_item->valueint; // cJSON 使用 int 来表示 bool 值
printf("sw: %d rn", sw_value); // 输出 sw 的值,1 代表 true,0 代表 false
}else
{printf("Failed to get'sw'value or it's not a boolean.rn");
}
cJSON *val_item = cJSON_GetObjectItemCaseSensitive(paras, "val");
if (val_item != NULL && cJSON_IsNumber(val_item))
{int val = val_item->valueint; // cJSON 使用 int 来表示 bool 值
printf("val: %d rn", val); // 输出 sw 的值,1 代表 true,0 代表 false
}else
{printf("Failed to get'val'value or it's not a number. rn");
}
}
}
}
// 释放资源
cJSON_Delete(root);
cJSON_free(json_data);
return 1;
}
- 字段存在性和类型检查:通过
cJSON_GetObjectItemCaseSensitive
和相应的类型检查函数来确保字段存在且类型正确。 - 内存分配和释放:分配内存来存储 JSON 字符串,并在处理完 JSON 数据后释放了这块内存也释放了 cJSON 对象。
- 错误处理:在 JSON 解析失败和内存分配失败添加了错误提示。
4. 注意事项
- 内存管理:STM32 的内存资源有限,特别是在使用 cJSON 库时,需要确保有足够的堆内存来存储 JSON 数据,需要调整 STM32 的堆栈大小。
- 错误处理:在解析 JSON 字符串时,一定要检查返回值,确保解析没有失败。
三、cJSON 库各类函数解析
1. 解析和创建函数
- cJSON_Parse(): 解析一个 JSON 格式的字符串,并返回一个
cJSON
指针,指向解析后的 JSON 结构的根。如果解析失败,返回NULL
。 - cJSON_Create 系列函数(如
cJSON_CreateObject()
,cJSON_CreateArray()
,cJSON_CreateString()
,cJSON_CreateNumber()
,cJSON_CreateBool()
,cJSON_CreateNull()
): 这些函数用于创建不同类型的cJSON
对象,分别对应于 JSON 中的对象、数组、字符串、数字、布尔值和 null。
2. 访问和修改函数
- cJSON_GetObjectItemCaseSensitive() 和 cJSON_GetObjectItem(): 根据键名查找 JSON 对象中的项。第一个函数对大小写敏感,第二个函数则不敏感。
- cJSON_GetArrayItem(): 根据索引获取 JSON 数组中的项。
- cJSON_AddItemToObject(), cJSON_AddItemToArray(), cJSON_AddItemReferenceToArray(), cJSON_AddItemToObjectCS(): 这些函数用于向 JSON 对象或数组中添加项。其中,带有
CS
后缀的函数对键名大小写敏感。 - cJSON_ReplaceItemInObjectCaseSensitive() 和 cJSON_ReplaceItemInObject(): 根据键名替换 JSON 对象中的项。与
GetObjectItem
函数类似,第一个函数对大小写敏感。 - cJSON_DetachItemFromObject() 和 cJSON_DetachItemFromArray(): 从 JSON 对象或数组中移除项,但不释放其内存。
- cJSON_DeleteItemFromObject() 和 cJSON_DeleteItemFromArray(): 从 JSON 对象或数组中移除项,并释放其内存。
3. 辅助函数
- cJSON_Print() 和 cJSON_PrintUnformatted(): 将
cJSON
结构转换回 JSON 格式的字符串。第一个函数格式化输出,第二个函数不格式化。 - cJSON_PrintBuffered(): 类似于
cJSON_Print
,但允许使用自定义的缓冲区。 - cJSON_Duplicate(): 创建一个
cJSON
项的深拷贝。 - cJSON_NumStrings 和相关宏(如
cJSON_IsArray
,cJSON_IsObject
,cJSON_IsString
,cJSON_IsNumber
,cJSON_IsBool
,cJSON_IsNull
): 这些函数和宏用于检查cJSON
项的类型。
4. 错误处理
- cJSON_GetErrorPtr(): 返回一个指向全局变量的指针,该变量包含解析错误时的错误信息(如果有的话)。
5. 内存管理
- cJSON_Delete(): 释放一个
cJSON
项及其所有子项占用的内存。
四、源码链接
【免费】stm32+cjson 库实现 json 格式创建与解析资源 -CSDN 文库
原文地址: STM32 单片机与 cJSON:构建并解析 JSON 数据