欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

FreeRTOS使用任務(wù)通知實(shí)現(xiàn)命令行解釋器

 更新時(shí)間:2022年04月08日 13:31:58   作者:zhzht19861011  
這篇文章主要為大家介紹了FreeRTOS使用任務(wù)通知實(shí)現(xiàn)命令行解釋器,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪

前言

雖然這是介紹FreeRTOS系列的文章,但這篇文章偏重于命令行解釋器的實(shí)現(xiàn)。這一方面是因?yàn)槿蝿?wù)通知使用起來非常簡(jiǎn)單,另一方面也因?yàn)閷?duì)于嵌入式程序來說,使用命令行解釋器來輔助程序調(diào)試是非常有用的。程序調(diào)試是一門技術(shù),基本上我們需要兩種調(diào)試手段,一種是可以單步仿真的硬件調(diào)試器,另外一種是可以長(zhǎng)期監(jiān)視程序狀態(tài)的狀態(tài)輸出,可以通過串口、顯示屏等等手段輸出異常信息或者某些關(guān)鍵點(diǎn)。這里的命令行解釋器就屬于后者。

本文實(shí)現(xiàn)的命令行解釋器具有以下特性:

支持十進(jìn)制參數(shù),識(shí)別負(fù)號(hào);

支持十六進(jìn)制參數(shù),十六進(jìn)制以‘0x’開始;

命令名長(zhǎng)度可定義,默認(rèn)最大20個(gè)字符;

參數(shù)數(shù)目可定義,默認(rèn)最多8個(gè)參數(shù);

命令名和參數(shù)之間以空格隔開,空格個(gè)數(shù)任意;

整條命令以回車換行符結(jié)束;

整條命令最大長(zhǎng)度可定義,默認(rèn)64字節(jié),包括回車換行符;

如果使用SecureCRT串口工具(推薦),支持該軟件的控制字符,比如退格鍵、左移鍵、右移鍵等。

一個(gè)帶參數(shù)的命令格式如下所示:

參數(shù)名 <參數(shù)1> <參數(shù)2> … <參數(shù)3>[回車換行符]

1.編碼風(fēng)格

FreeRTOS的編碼標(biāo)準(zhǔn)及風(fēng)格見FreeRTOS編碼標(biāo)準(zhǔn)及風(fēng)格指南,但我自己的編碼風(fēng)格跟FreeRTOS并不相同,并且我也不打算改變我當(dāng)前堅(jiān)持使用的編碼風(fēng)格。所以在這篇或者以后的文章中可能會(huì)在一個(gè)程序中看到兩種不同的編碼風(fēng)格,對(duì)于涉及FreeRTOS的代碼,我盡可能使用FreeRTOS建議的編碼風(fēng)格,與FreeRTOS無關(guān)的代碼,我仍然使用自己的編碼風(fēng)格。我可以保證,兩種編碼風(fēng)格決不會(huì)影響程序的可讀性,編寫良好可讀性的代碼,是我一直注重并堅(jiān)持的。

2.一些準(zhǔn)備工作

2.1串口硬件驅(qū)動(dòng)

命令行解釋器使用一個(gè)硬件串口,需要外部提供兩個(gè)串口底層函數(shù):一個(gè)是串口初始化函數(shù)init_cmd_uart(),用于初始化串口波特率、中斷等事件;另一個(gè)是發(fā)送單個(gè)字符函數(shù)my_putc()。此外,命令行為串口接收中斷服務(wù)程序提供函數(shù)fill_rec_buf(),用于保存接收到的字符,當(dāng)收到回車換行符后,該函數(shù)向命令行分析任務(wù)發(fā)送通知。

2.2一個(gè)類printf函數(shù)

類printf函數(shù)用來格式化輸出,我一般用來輔助調(diào)試,為了方便的將調(diào)試代碼從程序中去除,需要將類printf函數(shù)進(jìn)行封裝。我的文章嵌入式C程序優(yōu)質(zhì)編寫教程第5.2節(jié)給出了一個(gè)完整的類printf函數(shù)實(shí)現(xiàn)和封裝代碼,最終我們使用到的類printf函數(shù)是如下形式的宏:

MY_DEBUGF(CMD_LINE_DEBUG,("第%d個(gè)參數(shù):%d\n",i+1,arg[i])); 

3.使用任務(wù)通知

我們將會(huì)創(chuàng)建一個(gè)任務(wù),用來分析接收到的命令,如果命令有效則調(diào)用命令實(shí)現(xiàn)函數(shù)。這個(gè)任務(wù)名字為vTaskCmdAnalyze()。串口接收中斷用于接收命令,如果接收到回車換行符,則向任務(wù)vTaskCmdAnalyze()發(fā)送任務(wù)通知,表明已經(jīng)接收到一條完整命令,任務(wù)可以去處理了。

示意框圖如圖3-1所示。

4.數(shù)據(jù)結(jié)構(gòu)

命令行解釋器程序需要涉及兩個(gè)數(shù)據(jù)結(jié)構(gòu):一個(gè)與命令有關(guān),包括命令的名字、命令的最大參數(shù)數(shù)目、命令的回調(diào)函數(shù)類型、命令幫助信息等;另一個(gè)與分析命令有關(guān),包括接收命令字符緩沖區(qū)、存放參數(shù)緩沖區(qū)等。

4.1與命令有關(guān)的數(shù)據(jù)結(jié)構(gòu)

定義如下:

   typedef struct {
        char const *cmd_name;                        //命令字符串
        int32_t max_args;                            //最大參數(shù)數(shù)目
        void (*handle)(int argc,void * cmd_arg);     //命令回調(diào)函數(shù)
        char  *help;                                 //幫助信息
    }cmd_list_struct;

需要說明一下命令回調(diào)函數(shù)的參數(shù),argc保存接收到的參數(shù)數(shù)目,cmd_arg指向參數(shù)緩沖區(qū),目前只支持32位的整形參數(shù),這在絕大多數(shù)嵌入式場(chǎng)合是足夠的。

4.2與分析命令有關(guān)數(shù)據(jù)結(jié)構(gòu)

定義如下:

#define ARG_NUM     8          //命令中允許的參數(shù)個(gè)數(shù)
#define CMD_LEN     20         //命令名占用的最大字符長(zhǎng)度
#define CMD_BUF_LEN 60         //命令緩存的最大長(zhǎng)度
typedef struct {
    char rec_buf[CMD_BUF_LEN];            //接收命令緩沖區(qū)
    char processed_buf[CMD_BUF_LEN];      //存儲(chǔ)加工后的命令(去除控制字符)
    int32_t cmd_arg[ARG_NUM];             //保存命令的參數(shù)
}cmd_analyze_struct;

緩沖區(qū)的大小使用宏來定義,通過更改相應(yīng)的宏定義,可以設(shè)置整條命令的最大長(zhǎng)度、命令參數(shù)最大數(shù)目等。

5.串口接收中斷處理函數(shù)

本文使用的串口軟件是SecureCRT,在這個(gè)軟件下敲擊的任何鍵盤字符,都會(huì)立刻通過串口硬件發(fā)送出去,這與Telnet類似。所以我們無需使用串口的FIFO,每接收到一個(gè)字符就產(chǎn)生一次中斷。串口中斷與硬件關(guān)系密切,所以命令行解釋器提供了一個(gè)與硬件無關(guān)的函數(shù)fill_rec_buf(),每當(dāng)串口中斷接收到一個(gè)字符,就以收到的字符為參數(shù)調(diào)用這個(gè)函數(shù)。 fill_rec_buf()函數(shù)主要操作變量cmd_analyze,變量的聲明原型為:

cmd_analyze_struct cmd_analyze;

函數(shù)fill_rec_buf()的實(shí)現(xiàn)代碼為:

/*提供給串口中斷服務(wù)程序,保存串口接收到的單個(gè)字符*/
void fill_rec_buf(char data)
{
    //接收數(shù)據(jù) 
    static uint32_t rec_count=0;
    cmd_analyze.rec_buf[rec_count]=data;
    if(0x0A==cmd_analyze.rec_buf[rec_count] && 0x0D==cmd_analyze.rec_buf[rec_count-1])
    {
       BaseType_t xHigherPriorityTaskWoken = pdFALSE;
       rec_count=0;
       /*收到一幀數(shù)據(jù),向命令行解釋器任務(wù)發(fā)送通知*/
       vTaskNotifyGiveFromISR (xCmdAnalyzeHandle,&xHigherPriorityTaskWoken);
       
       /*是否需要強(qiáng)制上下文切換*/
       portYIELD_FROM_ISR(xHigherPriorityTaskWoken );
    }
    else
    {
       rec_count++;
       /*防御性代碼,防止數(shù)組越界*/
       if(rec_count>=CMD_BUF_LEN)
       {
           rec_count=0;
       }
    }    
}

6.命令行分析任務(wù)

命令行分析任務(wù)大部分時(shí)間都會(huì)因?yàn)榈却蝿?wù)通知而處于阻塞狀態(tài)。當(dāng)接收到一個(gè)通知后,任務(wù)首先去除命令行中的無效字符和控制字符,然后找出命令名并分析參數(shù)數(shù)目、將參數(shù)轉(zhuǎn)換成十六進(jìn)制數(shù)并保存到參數(shù)緩沖區(qū)中,最后檢查命令名和參數(shù)是否合法,如果合法則調(diào)用命令回調(diào)函數(shù)處理本條命令。

6.1去除無效字符和控制字符

串口軟件SecureCRT支持控制字符。比如在輸入一串命令的時(shí)候,發(fā)現(xiàn)某個(gè)字符輸入錯(cuò)誤,就要使用退格鍵或者左右移動(dòng)鍵定位到錯(cuò)誤的位置進(jìn)行修改。這里的退格鍵和左右移動(dòng)鍵都屬于控制字符,比如退格鍵的鍵值為0x08、左移鍵的鍵值為0x1B0x5B 0x44。我們之前也說過,在軟件SecureCRT中輸入字符時(shí),每敲擊一個(gè)字符,該字符立刻通過串口發(fā)送給我們的嵌入式設(shè)備,也就是所有鍵值都會(huì)按照敲擊鍵盤的順序存入到接收緩沖區(qū)中,但這里面可能有我們不需要的字符,我們首先需要利用控制字符將不需要的字符刪除掉。這個(gè)工作由函數(shù)get_true_char_stream()實(shí)現(xiàn),代碼如下所示:

/**
* 使用SecureCRT串口收發(fā)工具,在發(fā)送的字符流中可能帶有不需要的字符以及控制字符,
* 比如退格鍵,左右移動(dòng)鍵等等,在使用命令行工具解析字符流之前,需要將這些無用字符以
* 及控制字符去除掉.
* 支持的控制字符有:
*   上移:1B 5B 41
*   下移:1B 5B 42
*   右移:1B 5B 43
*   左移:1B 5B 44
*   回車換行:0D 0A
*  Backspace:08
*  Delete:7F
*/
static uint32_t get_true_char_stream(char *dest,const char *src)
{
   uint32_t dest_count=0;
   uint32_t src_count=0;
    while(src[src_count]!=0x0D && src[src_count+1]!=0x0A)
    {
       if(isprint(src[src_count]))
       {
           dest[dest_count++]=src[src_count++];
       }
       else
       {
           switch(src[src_count])
           {
                case    0x08:                          //退格鍵鍵值
                {
                    if(dest_count>0)
                    {
                        dest_count --;
                    }
                    src_count ++;
                }break;
                case    0x1B:
                {
                    if(src[src_count+1]==0x5B)
                    {
                        if(src[src_count+2]==0x41 || src[src_count+2]==0x42)
                        {
                            src_count +=3;              //上移和下移鍵鍵值
                        }
                        else if(src[src_count+2]==0x43)
                        {
                            dest_count++;               //右移鍵鍵值
                            src_count+=3;
                        }
                        else if(src[src_count+2]==0x44)
                        {
                            if(dest_count >0)           //左移鍵鍵值
                            {
                                dest_count --;
                            }
                           src_count +=3;
                        }
                        else
                        {
                            src_count +=3;
                        }
                    }
                    else
                    {
                        src_count ++;
                    }
                }break;
                default:
                {
                    src_count++;
                }break;
           }
       }
    }
   dest[dest_count++]=src[src_count++];
    dest[dest_count++]=src[src_count++];
    return dest_count;
}

6.2參數(shù)分析

接收到的命令中可能帶有參數(shù),我們需要知道參數(shù)的數(shù)目,還需要把字符型的參數(shù)轉(zhuǎn)換成整形數(shù)并保存到參數(shù)緩沖區(qū)(這是因?yàn)槊罨卣{(diào)函數(shù)需要這兩個(gè)參數(shù))。這個(gè)工作由函數(shù)cmd_arg_analyze()實(shí)現(xiàn),代碼如下所示:

/**
* 命令參數(shù)分析函數(shù),以空格作為一個(gè)參數(shù)結(jié)束,支持輸入十六進(jìn)制數(shù)(如:0x15),支持輸入負(fù)數(shù)(如-15)
* @param rec_buf   命令參數(shù)緩存區(qū)
* @param len       命令的最大可能長(zhǎng)度
* @return -1:       參數(shù)個(gè)數(shù)過多,其它:參數(shù)個(gè)數(shù)
*/
static int32_t cmd_arg_analyze(char *rec_buf,unsigned int len)
{
   uint32_t i;
   uint32_t blank_space_flag=0;    //空格標(biāo)志
   uint32_t arg_num=0;             //參數(shù)數(shù)目
   uint32_t index[ARG_NUM];        //有效參數(shù)首個(gè)數(shù)字的數(shù)組索引
   
    /*先做一遍分析,找出參數(shù)的數(shù)目,以及參數(shù)段的首個(gè)數(shù)字所在rec_buf數(shù)組中的下標(biāo)*/
    for(i=0;i<len;i++)
    {
       if(rec_buf[i]==0x20)        //為空格
       {
           blank_space_flag=1;              
           continue;
       }
        else if(rec_buf[i]==0x0D)   //換行
       {
           break;
       }
       else
       {
           if(blank_space_flag==1)
           {
                blank_space_flag=0; 
                if(arg_num < ARG_NUM)
                {
                   index[arg_num]=i;
                    arg_num++;         
                }
                else
                {
                    return -1;      //參數(shù)個(gè)數(shù)太多
                }
           }
       }
    }
   
    for(i=0;i<arg_num;i++)
    {
        cmd_analyze.cmd_arg[i]=string_to_dec((unsigned char *)(rec_buf+index[i]),len-index[i]);
    }
    return arg_num;
}

在這個(gè)函數(shù)cmd_arg_analyze()中,調(diào)用了字符轉(zhuǎn)整形函數(shù)string_to_dec()。我們只支持整形參數(shù),這里給出一個(gè)字符轉(zhuǎn)整形函數(shù)的簡(jiǎn)單實(shí)現(xiàn),可以識(shí)別負(fù)號(hào)和十六進(jìn)制的前綴’0x’。在這個(gè)函數(shù)中調(diào)用了三個(gè)C庫(kù)函數(shù),分別是isdigit()、isxdigit()和tolower(),因此需要包含頭文件#include <ctype.h>。函數(shù)string_to_dec()實(shí)現(xiàn)代碼如下:

/*字符串轉(zhuǎn)10/16進(jìn)制數(shù)*/
static int32_t string_to_dec(uint8_t *buf,uint32_t len)
{
   uint32_t i=0;
   uint32_t base=10;       //基數(shù)
   int32_t  neg=1;         //表示正負(fù),1=正數(shù)
   int32_t  result=0;
   if((buf[0]=='0')&&(buf[1]=='x'))
    {
       base=16;
       neg=1;
       i=2;
    }
    else if(buf[0]=='-')
    {
       base=10;
       neg=-1;
       i=1;
    }
    for(;i<len;i++)
    {
       if(buf[i]==0x20 || buf[i]==0x0D)    //為空格
       {
           break;
       }
       
       result *= base;
       if(isdigit(buf[i]))                 //是否為0~9
       {
           result += buf[i]-'0';
       }
       else if(isxdigit(buf[i]))           //是否為a~f或者A~F
       {
            result+=tolower(buf[i])-87;
       }
       else
       {
           result += buf[i]-'0';
       }                                        
    }
    result *= neg;
    return result ;
}

6.3定義命令回調(diào)函數(shù)

我們舉兩個(gè)例子:第一個(gè)是不帶參數(shù)的例子,輸入命令后,函數(shù)返回一個(gè)“Helloworld!”字符串;第二個(gè)是帶參數(shù)的例子,我們輸入命令和參數(shù)后,函數(shù)返回每一個(gè)參數(shù)值。我們?cè)谥v數(shù)據(jù)結(jié)構(gòu)的時(shí)候特別提到過命令回調(diào)函數(shù)的原型,這里要根據(jù)這個(gè)函數(shù)原型來聲明命令回調(diào)函數(shù)。

6.3.1不帶參數(shù)的命令回調(diào)函數(shù)舉例

/*打印字符串:Hello world!*/
void printf_hello(int32_t argc,void *cmd_arg)
{
   MY_DEBUGF(CMD_LINE_DEBUG,("Hello world!\n"));
}

6.3.2帶參數(shù)的命令行回調(diào)函數(shù)舉例

/*打印每個(gè)參數(shù)*/
void handle_arg(int32_t argc,void * cmd_arg)
{
   uint32_t i;
   int32_t  *arg=(int32_t *)cmd_arg;
    if(argc==0)
    {
       MY_DEBUGF(CMD_LINE_DEBUG,("無參數(shù)\n"));
    }
    else
    {
       for(i=0;i<argc;i++)
       {
           MY_DEBUGF(CMD_LINE_DEBUG,("第%d個(gè)參數(shù):%d\n",i+1,arg[i]));
       }
    }
}

6.4定義命令表

在講數(shù)據(jù)結(jié)構(gòu)的時(shí)候,我們定義了與命令有關(guān)的數(shù)據(jù)結(jié)構(gòu)。每條命令需要包括命名名、最大參數(shù)、命令回調(diào)函數(shù)、幫助等信息,這里要將每條命令組織成列表的形式。

/*命令表*/
const cmd_list_struct cmd_list[]={
/*   命令    參數(shù)數(shù)目    處理函數(shù)        幫助信息                         */   
{"hello",   0,      printf_hello,   "hello                      -打印HelloWorld!"},
{"arg",     8,      handle_arg,      "arg<arg1> <arg2> ...      -測(cè)試用,打印輸入的參數(shù)"},
};

如果要定義自己的命令,只需要按照6.3節(jié)的格式編寫命令回調(diào)函數(shù),然后將命令名、參數(shù)數(shù)目、回調(diào)函數(shù)和幫助信息按照本節(jié)格式加入到命令表中即可。

6.5命令行分析任務(wù)實(shí)現(xiàn)

有了上面的基礎(chǔ),命令行分析任務(wù)實(shí)現(xiàn)起來就非常輕松了,源碼如下:

/*命令行分析任務(wù)*/
void vTaskCmdAnalyze( void *pvParameters )
{
   uint32_t i;
   int32_t rec_arg_num;
    char cmd_buf[CMD_LEN];      
    while(1)
    {
       uint32_t rec_num;
       ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
    rec_num=get_true_char_stream(cmd_analyze.processed_buf,cmd_analyze.rec_buf);
       /*從接收數(shù)據(jù)中提取命令*/
       for(i=0;i<CMD_LEN;i++)
       {
           if((i>0)&&((cmd_analyze.processed_buf[i]==' ')||(cmd_analyze.processed_buf[i]==0x0D)))
           {
                cmd_buf[i]='\0';        //字符串結(jié)束符
                break;
           }
           else
           {
                cmd_buf[i]=cmd_analyze.processed_buf[i];
           }
       }
       rec_arg_num=cmd_arg_analyze(&cmd_analyze.processed_buf[i],rec_num);
       for(i=0;i<sizeof(cmd_list)/sizeof(cmd_list[0]);i++)
       {
           if(!strcmp(cmd_buf,cmd_list[i].cmd_name))       //字符串相等
           {
                if(rec_arg_num<0 || rec_arg_num>cmd_list[i].max_args)
                {
                    MY_DEBUGF(CMD_LINE_DEBUG,("參數(shù)數(shù)目過多!\n"));
                }
                else
                {
                    cmd_list[i].handle(rec_arg_num,(void *)cmd_analyze.cmd_arg);
                }
                break;
           }
       }
       if(i>=sizeof(cmd_list)/sizeof(cmd_list[0]))
       {
           MY_DEBUGF(CMD_LINE_DEBUG,("不支持的指令!\n"));
       }
    }
}

7.使用的串口工具

推薦使用SecureCRT軟件,這是我覺得最適合命令行交互的串口工具。此外,這個(gè)軟件非常強(qiáng)大,除了支持串口,還支持SSH、Telnet等。對(duì)于串口,SecureCRT工具還支持文件發(fā)送協(xié)議:Xmodem、Ymodem和Zmodem。這在使用串口遠(yuǎn)程升級(jí)時(shí)很有用,可以用來發(fā)送新的程序二進(jìn)制文件。我曾經(jīng)使用Ymodem做過遠(yuǎn)程升級(jí),以后有時(shí)間再詳細(xì)介紹SecureCRT的Ymodem功能細(xì)節(jié)。

要用于本文介紹的命令行解釋器,要對(duì)SecureCRT軟件做一些設(shè)置。

7.1設(shè)置串口參數(shù)

選擇Serial功能、設(shè)置端口、波特率、校驗(yàn)等,特別要注意的是不要勾選任何流控制選項(xiàng),如圖2-1所示。

圖2-1:設(shè)置串口參數(shù)

7.2設(shè)置新行模式

依次點(diǎn)擊菜單欄的“選項(xiàng)”---“會(huì)話選項(xiàng)”,在彈出的“會(huì)話選項(xiàng)”界面中,點(diǎn)擊左邊樹形菜單的“終端”---“仿真”---“模式”,在右邊的仿真模式區(qū)域選中“換行”和“新行模式”,如圖2-2所示。

圖2-2:設(shè)置新行模式

7.3設(shè)置本地回顯

依次點(diǎn)擊菜單欄的“選項(xiàng)”---“會(huì)話選項(xiàng)”,在彈出的“會(huì)話選項(xiàng)”界面中,點(diǎn)擊左邊樹形菜單的“終端”---“仿真”---“高級(jí)”,在右邊的“高級(jí)仿真”區(qū)域,選中“本地回顯”,如圖2-3所示。

圖2-3:設(shè)置本地回顯

8.測(cè)試

我們通過6.3節(jié)和6.4接定義了兩個(gè)命令,第一條命令的名字為”hello”,這是一個(gè)無參數(shù)命令,直接輸出字符串”Hello world!”。第二條命令的名字為”arg”,是一個(gè)帶參數(shù)命令,輸出每個(gè)參數(shù)的值。下面對(duì)這兩個(gè)命令進(jìn)行測(cè)試。

8.1無參數(shù)命令測(cè)試

設(shè)置好SecureCRT軟件,輸入字符”hello”后,按下回車鍵,設(shè)備會(huì)返回字符串”Hello world!”。如圖8-1所示。

圖8-1:無參數(shù)命令測(cè)試

8.2帶參數(shù)命令測(cè)試

設(shè)置好SecureCRT軟件,輸入字符”arg 1 2 -3 0x0a”后,按下回車鍵,設(shè)備會(huì)返回每個(gè)參數(shù)值。如圖8-2所示。

圖8-2:帶參數(shù)命令測(cè)試

以上就是FreeRTOS使用任務(wù)通知實(shí)現(xiàn)命令行解釋器的詳細(xì)內(nèi)容,更多關(guān)于FreeRTOS任務(wù)通知命令行解釋器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論