嵌入式開源項目精選專欄
本專欄由Mculover666創建,主要內容爲尋找嵌入式領域內的優質開源項目,一是幫助開發者使用開源項目實現更多的功能,二是通過這些開源項目,學習大佬的代碼及背後的實現思想,提升自己的代碼水平,和其它專欄相比,本專欄的優勢在於:
不會單純的介紹分享項目,還會包含作者親自實踐的過程分享,甚至還會有對它背後的設計思想解讀。
目前本專欄包含的開源項目有:
- cJSON | 一個輕量級C語言JSON解析器
- paho | 支持10種語言編寫mqtt客戶端,總有一款適合你!
- MultiButton | 一個小巧簡單易用的事件驅動型按鍵驅動模塊
- letter-shell | 一個功能強大的嵌入式shell
- EasyLogger | 一款輕量級且高性能的日誌庫
- SFUD | 一款串行 Flash 通用驅動庫
- EasyFlash | 讓 Flash 成爲小型 KV 數據庫
- MultiTimer | 一款可無限擴展的軟件定時器
- cmd-parser | 一個基於哈希匹配的超快命令解析器
如果您自己編寫或者發現的開源項目不錯,歡迎留言或者私信投稿到本專欄,分享獲得雙倍的快樂!
1. jsmn
本期給大家帶來的開源項目是 jsmn,一個資源佔用極小的json解析器,號稱世界上最快,作者zserge,目前收穫 2.1K 個 star,遵循 MIT 開源許可協議。
2. 移植jsmn
2.1. 移植思路
開源項目在移植過程中主要參考項目的readme文檔,一般只需兩步:
- ① 添加源碼到裸機工程中;
- ② 實現需要的接口;
2.2. 準備裸機工程
本文中我使用的是小熊派IoT開發套件,主控芯片爲STM32L431RCT6:
移植之前需要準備一份裸機工程,我使用STM32CubeMX生成,需要初始化以下配置:
- 配置一個串口用於發送數據;
- printf重定向
具體過程可以參考:
2.3. 添加jsmn到工程中
① 複製jsmn源碼到工程中:
② 將 jsmn.h 文件添加到keil中(沒有實質作用,方便編輯):
③ 添加jsmn頭文件路徑:
3. 使用jsmn解析json數據
3.1. 準備工作
① 包含jsmn頭文件
使用時包含頭文件:
/* USER CODE BEGIN Includes */
#include "jsmn.h"
#include <stdio.h> //用於printf打印
/* USER CODE END Includes */
② 設置一段原始json數據
在main.c中設置原始的json數據,用於後續解析:
/* USER CODE BEGIN PV */
static const char *JSON_STRING =
"{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000,\n"
"\"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}";
/* USER CODE END PV */
③ 開闢一塊存放token的數組(token池)
jsmn中,每個數據段解析出來之後是一個token,關於token的詳細解釋,請參考下文第4.1小節。
/* USER CODE BEGIN PV */
jsmntok_t t[128];
/* USER CODE END PV */
④ 編寫在原始JSON數據中的字符串比較函數:
static int jsoneq(const char *json, jsmntok_t *tok, const char *s) {
if (tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start &&
strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
return 0;
}
return -1;
}
3.2. 創建並初始化解析器
在main函數的開始創建解析器:
/* USER CODE BEGIN 1 */
int r;
int i;
jsmn_parser p;//jsmn解析器
/* USER CODE END 1 */
在隨後外設初始化完成之後的代碼中初始化解析器:
/* USER CODE BEGIN 2 */
jsmn_init(&p);
/* USER CODE END 2 */
3.3. 解析數據,獲取token
r = jsmn_parse(&p, JSON_STRING, strlen(JSON_STRING), t,sizeof(t) / sizeof(t[0]));
if (r < 0) {
printf("Failed to parse JSON: %d\n", r);
return 1;
}
/* Assume the top-level element is an object */
if (r < 1 || t[0].type != JSMN_OBJECT) {
printf("Object expected\n");
return 1;
}
3.4. 逐個解析token
/* Loop over all keys of the root object */
for (i = 1; i < r; i++)
{
if (jsoneq(JSON_STRING, &t[i], "user") == 0)
{
/* We may use strndup() to fetch string value */
printf("- user: %.*s\n", t[i + 1].end - t[i + 1].start,
JSON_STRING + t[i + 1].start);
i++;
}
else if (jsoneq(JSON_STRING, &t[i], "admin") == 0)
{
/* We may additionally check if the value is either "true" or "false" */
printf("- Admin: %.*s\n", t[i + 1].end - t[i + 1].start,
JSON_STRING + t[i + 1].start);
i++;
}
else if (jsoneq(JSON_STRING, &t[i], "uid") == 0)
{
/* We may want to do strtol() here to get numeric value */
printf("- UID: %.*s\n", t[i + 1].end - t[i + 1].start,
JSON_STRING + t[i + 1].start);
i++;
}
else if (jsoneq(JSON_STRING, &t[i], "groups") == 0)
{
int j;
printf("- Groups:\n");
if (t[i + 1].type != JSMN_ARRAY)
{
continue; /* We expect groups to be an array of strings */
}
for (j = 0; j < t[i + 1].size; j++)
{
jsmntok_t *g = &t[i + j + 2];
printf(" * %.*s\n", g->end - g->start, JSON_STRING + g->start);
}
i += t[i + 1].size + 1;
}
else
{
printf("Unexpected key: %.*s\n", t[i].end - t[i].start,
JSON_STRING + t[i].start);
}
}
3.5. 解析結果
編譯、下載到開發板,使用串口助手進行測試:
3.6. 內存對比
4. jsmn設計思想解讀
4.1. jsmn對json數據項的抽象
jsmn對json數據中的每一個數據段都會抽象爲一個結構體,稱之爲token,此結構體非常簡潔:
/**
* JSON token description.
* type type (object, array, string etc.)
* start start position in JSON data string
* end end position in JSON data string
*/
typedef struct jsmntok {
jsmntype_t type;
int start;
int end;
int size;
#ifdef JSMN_PARENT_LINKS
int parent;
#endif
} jsmntok_t;
在本實驗中未開啓JSMN_PARENT_LINKS
,所以此結構體佔用16Byte大小。
從結構體中的數據成員可以看出,jsmn並不對數據進行任何操作,僅僅記錄:
- 數據項的類型
- 數據項數據段在原始json數據中的起始位置
- 數據項數據段在原始json數據中的結束位置
其中,數據項的類型支持4種:
/**
* JSON type identifier. Basic types are:
* o Object
* o Array
* o String
* o Other primitive: number, boolean (true/false) or null
*/
typedef enum {
JSMN_UNDEFINED = 0,
JSMN_OBJECT = 1,
JSMN_ARRAY = 2,
JSMN_STRING = 3,
JSMN_PRIMITIVE = 4
} jsmntype_t;
4.2. jsmn如何解析出每個token
上述說到jsmn將每一個json數據段都抽象爲一個token,那麼jsmn是如何對整段json數據進行解析,得到每一個數據項的token呢?
jsmn解析器也是非常簡潔的一個結構體:
/**
* JSON parser. Contains an array of token blocks available. Also stores
* the string being parsed now and current position in that string.
*/
typedef struct jsmn_parser {
unsigned int pos; /* offset in the JSON string */
unsigned int toknext; /* next token to allocate */
int toksuper; /* superior token node, e.g. parent object or array */
} jsmn_parser;
jsmn解析就是將json數據逐個字符進行解析,用pos數據成員來記錄解析器當前的位置,當尋找到特殊字符時,就去之前我們定義的token數組(t)中申請一個空的token成員,將該token在數組中的位置記錄在數據成員toknext中。
源碼在下面的函數中,代碼過多,暫且先不放:
JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
jsmntok_t *tokens, const unsigned int num_tokens);
下面用一個實例來看看token是怎麼分配的。
縮短json原始數據:
static const char *JSON_STRING =
"{\"name\":\"mculover666\",\"admin\":false,\"uid\":1000}";
在解析之後將每個token打印出來:
printf("[type][start][end][size]\n");
for(i = 0;i < r; i++)
{
printf("[%4d][%5d][%3d][%4d]\n", t[i].type, t[i].start, t[i].end, t[i].size);
}
結果如下:
5. 項目工程源碼獲取和問題交流
目前我將jsmn源碼、我移植到小熊派STM32L431RCT6開發板的工程源碼上傳到了QQ羣裏(包含好幾份HAL庫,QQ相對速度快點),可以在QQ羣裏下載,有問題也可以在羣裏交流,當然也歡迎大家分享出來自己移植的工程到QQ羣裏:
放上QQ羣二維碼:
接收更多精彩文章及資源推送,歡迎訂閱我的微信公衆號:『mculover666』。