【STC32G应用】单片机如何实现JSON解析

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

SON字符串在应用层面,如安卓、云平台,应用的十分广泛。对于高级语言的开发者来说比底层的十六进制开发要简单的多。因此很多安卓与单片机的协议也是基于JSON做的。对于ARM系列单片机来说做JSON的解析并非难事。只要使用CJSON库即可。通过一些接口的调用就可以实现JSON的解析和封装。不过需要注意的一点是这个库还是比较吃资源的,而且需要malloc来动态申请内存。相较于ARM动辄十几K,几十K的RAM来说,问题并不大。

但是随着这两年,芯片价格的变动。例如这几年的ST价格的剧烈波动。虽然带动了国产替代方案的迅速崛起。如:GD、APM等PINTOPIN替代方案。但是很多用户的目光又回到了51系的单片机。

虽然开发环境不友好、架构与ARM差别大、资源少但是架不住价格便宜的诱惑。这个年代做到几块钱的MCU还是吃香呀,并且现在51单片机,例如STC,速率、模拟性能、Flash容量也不差了,还有片上EEPROM,支持串口自定义命令的ISP升级,定时器资源等等。唯一的缺点就是内存太小。并且对于一些基于指针操作的C代码和函数也不友好。
那在系统整体降成本的过程中,如果想要保持协议不变。例如JSON的话呢,怎么办呢?


一、JSON解析库

引用现成的CJSON不合适,一个是并不需要全协议的支持。第二个再根据应用进行裁剪也费时费力。反倒不如直接重新简单做个小库来的方便。

整体小库大体可以分为三个部分:

1、串口协议的解析(大部分还是用的串口);

2、JSON的解析和封装;

3、循环队列QUEUE。

来实现一个资源占用较少的JSON协议解析。当然有利有弊,资源少了,带来的限制就多了,例如协议相对固定,然后队列的存储采用定长缓冲区为单位,没法做到内存使用的高效率。

二、具体实现

1.串口解析

代码如下(示例):

void vACLRcvDataHandle(uint8_t ucData, uint8_t ucOrder)

{
    
    

    static uint16_t usLengthFlag    = 0;

    static uint8_t  *pCmdLine       = NULL;

    static uint8_t  ucACLHead[3]    = {
    
    0};

    static uint16_t usCounter       = 0;

    static ACLRcvSta_e eACLRcvSta   = eACLRcvStaIdle;

    static uint8_t ucCheck          = 0;



    uint8_t ucTemp                  = ucData;



    if(ucOrder == ACL_RCV_STA_CLR) {
    
    



        eACLRcvSta  = eACLRcvStaIdle;

        return;

    }

    switch(eACLRcvSta)

    {
    
    

    case eACLRcvStaIdle:            //serch  head

        ucACLHead[0] = ucACLHead[1];

        ucACLHead[1] = ucACLHead[2];

        ucACLHead[2] = ucTemp;

        // 寻找帧头

        if(memcmp(ucACLHead, GucACLHeadBuf, 3) == 0)

        {
    
    

            eACLRcvSta  = eACLRcvStaType;

            pCmdLine    = GucACLRcvBuf;

            ucACLHead[2] = 0x00;



        }



        break;

    case eACLRcvStaType:        //serch  TYPE

        // 数据类型判断

        if((ucTemp == eACLRcvTypeNormal) || (ucTemp == eACLRcvTypeUpdate))

        {
    
    

            eACLRcvSta = eACLRcvStaLength;

            // 长度标识

            usLengthFlag = 0;

            ucCheck = 0;

            *pCmdLine = ucTemp;

            pCmdLine++;

        }

        else

        {
    
    

            eACLRcvSta = eACLRcvStaIdle;

        }

        break;

    case eACLRcvStaLength:      //serch  Length



        usLengthFlag++;

        *pCmdLine = ucTemp;

        pCmdLine++;

        ucCheck += ucTemp;

        // 数据长度计算

        if(usLengthFlag >= 2)

        {
    
    

            usLengthFlag = (GucACLRcvBuf[1] << 8) + GucACLRcvBuf[2];//计算数据长度,高位移位加低位

            if( usLengthFlag >= 3) {
    
    



                // 数据长度越界检测

                if(usLengthFlag > ACL_COMM_MAX_SIZE ) {
    
    

                    eACLRcvSta = eACLRcvStaIdle;

                } else {
    
    

                    usCounter = 0;

                    eACLRcvSta = eACLRcvStaRecving;

                }

            } else {
    
    

                eACLRcvSta = eACLRcvStaIdle;

            }

        }



        break;

    case eACLRcvStaRecving: //push receive data to cmd line

        //存数据

        *pCmdLine = ucTemp;

        pCmdLine++;

        usCounter++;

        ucCheck += ucTemp;



        if(usCounter >= (usLengthFlag - 2)) {
    
    

            eACLRcvSta = eACLRcvStaCheck;

        }



        break;

    case eACLRcvStaCheck:

        if(ACL_CHECK) {
    
    

            if(ucCheck != ucTemp ) {
    
    

                eACLRcvSta = eACLRcvStaIdle;

            } else {
    
    

                eACLRcvSta = eACLRcvStaEnd;

            }

        } else  {
    
    

            eACLRcvSta = eACLRcvStaEnd;

        }

        break;

    case eACLRcvStaEnd:

        *pCmdLine = '0';        // add string end flag

        if(ucTemp == 0xFF)

        {
    
    

            eACLRcvSta = eACLRcvStaIdle;

            vACLPortQueueSendJson(GucACLRcvBuf);

        } else {
    
    

            eACLRcvSta = eACLRcvStaIdle;

        }



        break;



    default:

        eACLRcvSta = eACLRcvStaIdle;

        break;

    }

}

这个根据各自的协议进行设计就行。这里的DEMO除了JSON串以外,增加了帧头帧尾 帧长度 校验位 帧类型。

解析方式基本类似于常用的AT指令的解析方式,相对来说简单可靠。解析完成后通过循环队列进行存储。队列的长度可以自己定义,资源多的可以定义的长一点。资源少的就定义的短一点。

最小的资源包含一个单位的串口接收缓冲区。一个单位的接收队列和一个单位的发送队列。如果单位长度为100字节的话,整个库最低消耗可能不到500个字节。对于51来说还是很友好的。

2.JSON的解析和封装

代码如下(示例):

static ACLBaseType prxJsonParse(uint8_t *pBuf,ACLOrder_s *psACLOrders)

{
    
    

    uint8_t *cmd_json = NULL;

    uint8_t *cmd_type = NULL;

    uint8_t *cmd_var1 = NULL;

    uint8_t *cmd_var2 = NULL;



    // Init

    psACLOrders->ucType = 0xFF;

    psACLOrders->ulVar1 = 0xFF;

    psACLOrders->ulVar2 = 0xFF;

   

    cmd_json = pBuf;



    // 判断是否存在JSON串

    if(cmd_json[0] != '{')

    {
    
    

        return ACL_FALSE;

    }

    else

    {
    
    

        cmd_type = strstr(cmd_json, "type");

        cmd_var1 = strstr(cmd_json, "var1");

        cmd_var2 = strstr(cmd_json, "var2");

        

        // Carefully !! atoi return 0 even if str has no number

        if( !cmd_type)

        {
    
    

            return ACL_FALSE;

        }

        else

        {
    
    

            psACLOrders->ucType = atoi(cmd_type+6);

        }



        if(cmd_var1)

        {
    
    

            psACLOrders->ulVar1 = atoi(cmd_var1+6);

        }



        if(cmd_var2)

        {
    
    

            psACLOrders->ulVar2 = atoi(cmd_var2+6);

        }



    }



    return ACL_TRUE;

}



static ACLBaseType vACLSendJsonConcatenation( ACLSendType_e eACLSendType, ACLOrder_s *pACLOrder,  char *pcBuffer,  uint16_t *pusLen)

{
    
    

    uint8_t *pPoint = pcBuffer;

    BUFFER_LOCAL uint8_t ucTempBuf[ACL_VAR2_BUF_SIZE+7] = {
    
    0};

    uint8_t ucTemp = 0;



    char *pcString = NULL;

    if(((POINTE_CAST)pPoint == NULL)||((POINTE_CAST)pACLOrder == NULL)) {
    
    

        return ACL_FALSE;

    }

    /* 创建JSON根节点 */

    pPoint[0] = '{';

    pPoint++;

    // Add type

    ucTemp = sprintf(ucTempBuf,"\"type\":%d,",(uint16_t)pACLOrder->ucType);



    memcpy(pPoint,ucTempBuf,ucTemp);

    pPoint += ucTemp;



    switch(eACLSendType)

    {
    
    

    case eACLSendVar2None:

        pACLOrder->ulVar2 = 0;

    case eACLSendVar2Int:

    {
    
    

        ucTemp = sprintf(ucTempBuf,"\"var1\":%d,",(uint16_t)pACLOrder->ulVar1);

        memcpy(pPoint,ucTempBuf,ucTemp);

        pPoint += ucTemp;

        ucTemp = sprintf(ucTempBuf,"\"var2\":%d",(uint16_t)pACLOrder->ulVar2);

        memcpy(pPoint,ucTempBuf,ucTemp);

        pPoint += ucTemp;



        break;

    }

    case eACLSendVar2Str:

    {
    
    

        ucTemp = sprintf(ucTempBuf,"\"var1\":%d,",(uint16_t)pACLOrder->ulVar1);

        memcpy(pPoint,ucTempBuf,ucTemp);

        pPoint += ucTemp;

        ucTemp = sprintf(ucTempBuf,"\"var2\":");

        memcpy(pPoint,ucTempBuf,ucTemp);

        pPoint += ucTemp;

        memcpy(pPoint,pACLOrder->ucVar2Buf,strlen(pACLOrder->ucVar2Buf)%(ACL_VAR2_BUF_SIZE+1));

        pPoint += strlen(pACLOrder->ucVar2Buf)%(ACL_VAR2_BUF_SIZE+1);

        break;

    }

    default:

    {
    
    

        break;

    }

    }

    *(pPoint++) = '}';



    *pusLen = (uint16_t)(pPoint - pcBuffer);



    return ACL_TRUE;

}

解析和封装也是要根据自己的协议进行定义。基本的就使用了标准库函数里面的接口。相对也比较简单 strstr atoi memcpy sprintf。没有什么难度,虽然引用库函数,但是效率上也是比较快的。一般的应用没什么大的差别。

3.循环队列QUEUE

extern INT8U queueCreate(  DATAQUEUE *Queue,

                               void *pBuf,

                               INT32U ulSizeOfBuf,

                               INT8U (* pfuncReadEmpty)(),

                               INT8U (* pfuncWriteFull)()

                            );

 extern INT8U queueReadNData(DATAQUEUE *Queue, uint8_t *pucDestBuf, uint8_t ucNumber)  ;

 extern INT8U queueWriteNData(DATAQUEUE *Queue, uint8_t *pucSrcBuf, uint8_t ucNumber);

queue的部分各自都有自己喜欢的或者常用的queue库。只要实现好queue初始化、入队出队就可以了。

提示一下临界区保护还是一定要有的。


总结

这样子就简单实现了一个简单的JSON解析。整个库也非常透明,基本没有封装的很深的部分。也比较简单,根据自己的协议搞搞,1天左右也就弄完了。

并且由于循环队列的存在,也支持了命令的缓冲,减少命令的丢失。

注:文章是作者原创,同时发布于:Z站 https://z.zlg.cn/articleinfo?id=852836

猜你喜欢

转载自blog.csdn.net/lunzilx/article/details/131912509