2010-08-23 66 views
4

我想解釋一個命令字符串,由一個微控制器(PIC16F877A,如果有任何區別)通過串行接收。C/C++中的微控制器串行命令解釋器;如何做到這一點;

該串具有一個非常簡單的和直FOWARD格式化: $ AABBCCDDEE(5總共11個字符的2個chracters +「$」「塊」)其中: $ AA =命令的實際名稱(可能是字母,數字,都是強制性的); BB-EE =參數(數字;可選);

我想用C/C++編寫代碼。

我想我可以通過串行抓取字符串,將其切換成塊,switch(){case}和memcmp命令塊($ AA)。然後我可以有一個二進制決策樹來利用BB CC DD和EE塊。

我想知道如果這是正確的方式做到這一點(它似乎對我來說很醜陋,當然必須有一種不那麼繁瑣的方式來做到這一點!)。

回答

3

根據定義,ASCII接口很難看。理想情況下,你有一些幀結構,也許你有,$表示幀之間的分界,你說他們是11個字符的長度。如果總是11是好的,如果只是有時更難,希望在開始時有一個$,最後有0x0A和0x0D/0x0A(CR/LF)。通常我有一個代碼模塊,它只是從串口提取字節並將它們放入一個(循環)緩衝區。緩衝時間可以追溯到串口幾乎沒有緩衝器的時代,但即使在今天,尤其是微控制器,情況依然如此。然後是監視搜索幀的緩衝區的另一個代碼模塊。理想情況下,此緩衝區足夠大,可以將幀留在那裏,併爲下一幀留出空間,並且不需要另一個緩衝區來保存接收到的幀的副本。使用循環緩衝區,這個第二個模塊可以移動(如果需要,丟棄它)指向幀標記開始的頭指針並等待全幀數據。一旦完整幀出現在那裏,它會調用處理該幀的另一個函數。這個功能可能就是你所問的那個功能。 「只是編碼」可能是答案,你是在一個微控制器中,所以你不能在操作系統解決方案上使用懶惰的高級桌面應用程序。如果您自己創建或通過庫提供給您,您將需要某種類型的strcmp函數,或者不依賴於您的解決方案。蠻力if(strncmp(& frame [1],「bob」,3)== 0)then,else if(strncmp(& frame [1],「ted」,3)then,else if ...當然但是你可以用這種東西來咀嚼你的ROM,而這種方法所需的緩衝可以咀嚼很多內存,這個方法是非常可讀和可維護的,並且可移植,但可能不會很快(可維護通常與可靠和/或性能衝突),但這可能不是問題,只要在下一個出現之前,或在未處理的數據從循環緩衝區溢出之前,您可以處理該數據。任務框架檢查器例程可以簡單地檢查框架是否良好,我通常會放置開始和結束標記,長度和某種算術校驗和,如果它是一個壞框架,它會被丟棄,這節省了很多代碼檢查的不好/損壞的數據當幀處理例程返回到幀例程的搜索時,因爲它不再需要,所以可以清除幀頭指針以清除幀。幀檢查器可能只驗證一個幀並將其交給另一個執行解析的函數。這種安排中的每個樂高積木都有一個非常簡單的任務,並且假定下面的樂高積木已經正確地完成了任務。模塊化,面向對象,無論您想使用什麼術語,都可以使設計,編碼,維護和調試變得更容易。 (以性能和資源爲代價)。這種方法適用於任何串行類型的數據流,例如微控制器中的串行端口(具有足夠的資源)以及桌面上的應用程序,用於查看串行端口的串行數據或TCP數據,這些數據也是串行的而不是面向幀的。

如果你的micro不具備所有的資源,那麼狀態機方法也可以很好地工作。每個到達的字節在狀態機的一個狀態中打勾。從空閒等待第一個字節開始,是第一個字節a $?不要丟棄它並回到閒置狀態。如果第一個字節是$,則轉到下一個狀態。如果您正在查找「和」,「添加」,「或」和「異或」等命令,則第二個狀態將與「a」,「o」和「x」進行比較,如果沒有這些去閒置。如果一個a然後進入一個比較n和d的狀態,如果一個o然後轉到尋找r的狀態。如果r或者狀態的查找沒有看到r,那麼就進入空閒狀態,如果是,則處理該命令,然後進入空閒狀態。代碼是可讀的,您可以查看狀態機並查看單詞a,n,d,a,d,d,o,r,x,o,r以及它們最終導致的位置,但通常不被視爲可讀代碼。這種方法使用非常少的內存,與其他解析方法相比,它更傾向於ROM,但總體上可以使用最少量的ROM。這裏再次是非常便攜的,除了微控制器之外,但在微控制器之外,人們可能會認爲你對這種代碼是瘋狂的(當然,如果這是verilog或vhdl的話)。這種方法很難維護,難於閱讀,但速度非常快且可靠,並使用最少的資源。

要解釋命令後採取什麼方法,您必須確保您可以執行命令而不會丟失串口上的任何字節,無論是通過確定性的代碼或中斷或任何其他方式。

底線ascii接口總是醜陋的,它們的代碼,無論你使用多少層庫來簡化工作,執行的結果指令都很難看。根據定義,一種尺寸不適合任何人。只需開始編碼,試試一個狀態機,然後嘗試if-then-else-strncmp,然後進行優化。您應該快速地看到哪一種方法在編碼風格,工具/處理器以及正在解決的問題方面表現最佳。

+0

謝謝! 你和shodanex的答案對我來說是最有用的。 有限狀態機似乎是正確的選擇,儘管它不像模塊化方法那麼直觀。 而且,是的,ASCII接口很醜。不是我的選擇:P – Pav 2010-08-26 06:18:51

2

這取決於你想要得到多少奇特,有多少不同的命令,以及是否可能經常添加新命令。

您可以創建一個數據結構,將每個有效的命令字符串與相應的函數指針相關聯 - 使用bsearch()訪問的排序列表可能沒有問題,儘管散列表可能具有更好的性能(因爲有效的命令是事先知道的,你可以使用像gperf這樣的工具來構建完美的哈希)。

bsearch()方法可能是這個樣子:

void func_aa(char args[11]); 
void func_cc(char args[11]); 
void func_xy(char args[11]); 

struct command { 
    char *name; 
    void (*cmd_func)(char args[11]); 
} command_tbl[] = { 
    { "AA", func_aa }, 
    { "CC", func_cc }, 
    { "XY", func_xy } 
}; 

#define N_CMDS (sizeof command_tbl/sizeof command_tbl[0]) 

static int comp_cmd(const void *c1, const void *c2) 
{ 
    const struct command *cmd1 = c1, *cmd2 = c2; 

    return memcmp(cmd1->name, cmd2->name, 2); 
} 

static struct command *get_cmd(char *name) 
{ 
    struct command target = { name, NULL }; 

    return bsearch(&target, command_tbl, N_CMDS, sizeof command_tbl[0], comp_cmd); 
} 

然後,如果你有command_str指向從串口一個字符串,你可以這樣做派遣合適的功能:

struct command *cmd = get_cmd(command_str + 1); 

if (cmd) 
    cmd->cmd_func(command_str); 
+0

謝謝! 我需要閱讀散列表和散列函數:D – Pav 2010-08-23 08:44:01

5

不要過度設計它!這並不意味着盲目編碼,但是一旦你設計了一些看起來可以完成的工作,就可以開始實施它。實施將爲您提供有關您架構的反饋。

例如,在編寫開關盒時,您可能會看到自己重寫的代碼非常類似於剛剛爲上一個案例編寫的代碼。實際上寫下一個算法會幫助你看到你沒有想到的一些問題,或者你沒有看到的一些簡化。

不要瞄準第一次嘗試的最佳代碼。瞄準

  • 容易閱讀
  • 易於調試

採取litlle步驟。你不必一次性實現整個事情。

  • 從串口抓取字符串。看起來很簡單,對吧?那麼,我們先做,只是打印出這些命令。
  • 將命令從參數中分離出來。
  • 提取參數。每個命令的提取都是一樣的嗎?你能設計一個適用於每個命令的數據結構嗎?

一旦你做對了,你可以開始考慮更好的解決方案。

+0

謝謝! 我正要這樣做..盲目地啓動編碼,即:D – Pav 2010-08-23 08:41:44

0

不知道你是否仍在爲此工作。但我正在開發一個類似的項目,並發現一個嵌入式命令行解釋器http://sourceforge.net/projects/ecli/?source=recommended。沒錯,他們嵌入了應用程序。

cli_engine函數確實有助於從命令行獲取輸入。

警告:除自述文件外沒有其他文檔。我仍在研究整合框架的一些錯誤,但這無疑給了我一個良好的開端。您必須自己處理比較字符串(即使用strcmp)。