[STC32G アプリケーション] MCU はどのように JSON 解析を実装しますか

ヒント: 記事を作成した後、目次を自動的に生成できます。生成方法は、右側のヘルプドキュメントを参照してください。


序文

SON 文字列は、Android やクラウド プラットフォームなどのアプリケーション レベルで広く使用されています。高レベル言語の開発者にとって、低レベルの 16 進数の開発よりもはるかに簡単です。したがって、Android と MCU 間のプロトコルの多くも JSON に基づいています。ARM シリーズのマイクロコントローラーが JSON を解析するのは難しくありません。CJSON ライブラリを使用するだけです。JSON の解析とカプセル化は、いくつかのインターフェイス呼び出しを通じて実現できます。ただし、注意すべき点が 1 つあります。このライブラリは依然としてリソースを消費し、メモリを動的に適用するには malloc が必要であるということです。ARM の 10 K、数十 K を超える RAM と比較すると、問題は大きくありません。

しかし、過去 2 年間でチップの価格は変化しました。例えば、STの価格は近年激しく変動しています。それが国内の代替品の急速な台頭につながりましたが。例: GD、APM、その他の PINTOPIN の代替手段。しかし、多くのユーザーの目は 51 シリーズ MCU に戻っています。

開発環境は優しくなく、アーキテクチャはARMとは大きく異なり、リソースも少ないため、安価な価格の誘惑には耐えられません。この時代ではまだ数ドルのマイコンが主流で、現在ではSTCなどの51シングルチップマイコンは速度、アナログ性能、フラッシュ容量の点で悪くはなく、オンチップEEPROMやISPのアップグレードもあり、シリアルポートのカスタムコマンドやタイマーリソースなどをサポートします。唯一の欠点はメモリが少なすぎることです。また、ポインター操作に基づく一部の C コードや関数には適していません。
システム全体のコストを削減する過程で、契約を変更せずに維持したい場合。たとえば、JSON はどうでしょうか?


1. JSON解析ライブラリ

既製の CJSON を引用するのは不適切です。1 つは、プロトコル全体のサポートが必要ないということです。2番目の方法も、用途に応じてトリミングするのに時間と労力がかかります。逆に、単に小さなライブラリを再作成するほど便利ではありません。

小さなライブラリ全体は、大きく 3 つの部分に分けることができます。

1. シリアル ポート プロトコルの分析 (ほとんどのプロトコルは依然としてシリアル ポートを使用しています)。

2. JSON の解析とカプセル化。

3. 循環キュー QUEUE。

リソース消費量の少ない JSON プロトコル解析を実現します。もちろん一長一短があり、プロトコルが比較的固定的であったり、キューの格納単位が固定長のバッファであったりするため、高いメモリ使用効率が得られないなど、リソースが少ないほど制約が多くなります。

2. 実現

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;

    }

}

これは、それぞれのプロトコルに従って設計できます。ここのデモでは、JSON 文字列に加えて、フレーム ヘッダー、フレーム テール、フレーム長、チェック ディジット フレーム タイプが追加されています。

解析方法は基本的に一般的に使用されているATコマンドの解析方法と同様であり、比較的簡単で確実です。解析が完了すると、解析は循環キューに保存されます。キューの長さは自分で定義でき、リソースが多い場合はさらに長く定義できます。リソースが少ない場合は、定義を短くする必要があります。

最小のリソースはシリアル ポート受信バッファのユニットで構成されます。受信キュー 1 ユニットと送信キュー 1 ユニット。ユニット長が 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);

キューの各部分には、独自のお気に入りまたはよく使用されるキュー ライブラリがあります。キューが初期化され、キューの出入りが行われている限りは問題ありません。

クリティカルセクションの保護は依然として必要であることに注意してください。


要約する

このようにして、単純な JSON 解析が簡単に実装されます。ライブラリ全体も非常に透明性が高く、パッケージには基本的に深い部分がありません。こちらも比較的簡単で、ご本人の同意に応じて1日程度で完了します。

また、循環キューの存在により、コマンドの損失を減らすためにコマンドのバッファリングもサポートされます。

注: この記事は著者によるオリジナルであり、Z ステーションで公開されています https://z.zlg.cn/articleinfo?id=852836

おすすめ

転載: blog.csdn.net/lunzilx/article/details/131912509