4


1

C / C ++のマイクロコントローラーシリアルコマンドインタープリター。それを行う方法。

シリアル経由でマイクロコントローラ(PIC16f877Aの場合は違いがあります)によって受信されたコマンド文字列を解釈したいと思います。

文字列は非常にシンプルでわかりやすい形式です。$ AABBCCDDEE(2文字の5つの「ブロック」+合計11文字の「$」)ここで、$ AA =コマンドの実際の名前(文字、数字、両方とも可能) ;必須); BB-EE =パラメーター(数値、オプション);

C / C ++でコードを書きたいです。

シリアル経由で文字列を取得し、ブロックにハックし、()\ {case}を切り替え、コマンドブロック($ AA)をmemcmpすることができると思います。 それから、BB CC DDおよびEEブロックを利用するためのバイナリ決定ツリーを持つことができます。

それが正しい方法であるかどうかを知りたいです(私にはちょっとkindいようですが、確かにこれを行うのに退屈な方法はないはずです!)。

4 Answer


5


それを過剰に設計しないでください! それは盲目的にコーディングすることを意味するものではありませんが、仕事ができるように見える何かを設計したら、それを実装し始めることができます。 実装により、アーキテクチャに関するフィードバックが得られます。

たとえば、スイッチケースを作成する場合、前のケースで作成したコードと非常によく似たコードを書き直していることがあります。 実際にアルゴリズムを書き留めておくと、思いもよらなかった問題や、見なかった単純化を確認するのに役立ちます。

最初の試行で最高のコードを目指してはいけません。 目指す

  • 読みやすい

  • デバッグが簡単

少しずつ進んでください。 一度に全部を実装する必要はありません。

  • シリアルポートから文字列を取得します。 簡単そうですね。 じゃあ まず、コマンドを出力するだけです。

  • コマンドをパラメーターから分離します。

  • パラメーターを抽出します。 抽出はそれぞれに対して同じになりますか コマンド? すべてのコマンドに有効なデータ構造を設計できますか?

正しく完了したら、より良い解決策を考え始めることができます。


3


ASCIIインターフェイスは定義上見苦しいです。 理想的には、ある種のフレーム構造があります。これはおそらく持っているかもしれません。$はフレーム間の分割を示し、長さは11文字であると言います。 常に11であれば、それがより困難な場合は、開始時に$が、終了時に0x0Aまたは0x0D / 0x0A(CR / LF)が存在することを願っています。 通常、シリアルポートからバイトを抽出して(循環)バッファーに入れるコードのモジュールが1つあります。 シリアルポートがボード上にバッファをほとんど持たない時代にまで遡るバッファリングですが、今日でも、特にマイクロコントローラを使用すると、それは依然として事実です。 次に、フレームを検索するバッファを監視するコードの別のモジュール。 理想的には、このバッファーはフレームをそこに残して次のフレームのためのスペースを確保し、受信したフレームのコピーを保持するために別のバッファーを必要としないのに十分な大きさです。 循環バッファーを使用すると、この2番目のモジュールはフレームマーカーの先頭にヘッドポインターを移動(必要に応じて破棄)し、完全なフレームに相当するデータを待ちます。 完全なフレームがそこにあるように見えると、そのフレームを処理する別の関数を呼び出します。 その関数は、あなたが尋ねているものかもしれません。 そして、「それをコーディングするだけ」が答えかもしれません、あなたはマイクロコントローラにいるので、あなたはオペレーティングシステムソリューションで怠zyな高レベルのデスクトップアプリケーションを使うことはできません。 自分で作成した場合、またはライブラリを介して使用できる場合、またはソリューションに依存しない場合は、何らかのstrcmp関数が必要になります。 総当たりif(strncmp(&frame [1]、 "bob"、3)== 0)then、else if(strncmp(&frame [1]、 "ted"、3)then、else if …​ 確かに動作しますが、そのようなことでROMを噛むかもしれません。 そして、この種のアプローチに必要なバッファリングは、大量のRAMを消費する可能性があります。 ただし、このアプローチは非常に読みやすく保守しやすく、移植性があります。 高速ではない場合があります(通常は信頼性やパフォーマンスと競合します)が、次のものが来る前、または未処理のデータが循環バッファーから落ちる前に処理できる限り、それは問題になりません。 。 タスクに応じて、フレームチェッカールーチンはフレームが良好であることを単純にチェックする場合があります。通常、開始マーカーと終了マーカー、長さ、ある種の算術チェックサムを配置します。不良/破損データの確認。 フレーム処理ルーチンは、フレームの検索ルーチンに戻ると、ヘッドポインタを移動して、不要になったフレーム、または正常なフレームまたは不良なフレームをパージします。 フレームチェッカーは、フレームを検証し、解析を行う別の関数に渡すだけです。 この配置の各レゴブロックは非常に単純なタスクを持ち、その下のレゴブロックがそのタスクを適切に実行したという前提で動作します。 モジュラー、オブジェクト指向、どのような用語を使用しても、設計、コーディング、保守、デバッグがはるかに簡単になります。 (パフォーマンスとリソースを犠牲にして)。 このアプローチは、マイクロコントローラーのシリアルポート(十分なリソースを備えた)や、シリアルポートからのシリアルデータや、シリアルでフレーム指向ではないTCPデータを見るアプリケーションのシリアルポートである、あらゆるシリアルタイプストリームに適しています。

マイクロにすべてのリソースがない場合、ステートマシンアプローチも非常にうまく機能します。 到着する各バイトは、ステートマシンを1つの状態にします。 最初のバイトを待機しているアイドル状態から始めます。最初のバイトは$ですか? 破棄せずにアイドル状態に戻ります。 最初のバイトが$の場合、次の状態に進みます。 「and」、「add」、「or」、「xor」などのコマンドを探している場合、2番目の状態は「a」、「o」、「x」と比較されます。アイドル状態になります。 aがnとdを比較する状態に進む場合、oがrを探す状態に進む場合。 r inまたは状態の検索でrが表示されない場合はアイドルになり、そうでない場合はコマンドを処理してからアイドルになります。 コードは、ステートマシンを見て、a、n、d、a、d、d、o、r、x、o、rの単語と、それらが最終的にどこにつながるかを見ることができるという意味で読み取り可能です読み取り可能なコードとは見なされません。 このアプローチはごくわずかなRAMを使用し、ROMにもう少し依存しますが、他の解析アプローチと比較して、全体的に最小のROMを使用できます。 そして、ここでもマイクロコントローラーを超えて非常に移植性がありますが、マイクロコントローラーの外部の人々は、この種のコードに狂っていると思うかもしれません(もちろん、これがverilogまたはvhdlである場合はそうではありません)。 このアプローチは、維持するのが難しく、読むのが難しいですが、非常に高速で信頼性が高く、使用するリソースも最小限です。

コマンドが解釈された後のアプローチを問題にするためには、コードまたは割り込みなどの決定的なパフォーマンスによって、シリアルポートでバイトを失うことなくコマンドを実行できることを保証する必要があります。

結論として、ASCIIインターフェイスは常にいものであり、それらのコードは、ジョブを簡単にするために使用するライブラリの層数に関係なく、実行される結果の命令はいものです。 また、定義上、1つのサイズは誰にも適合しません。 コーディングを開始し、ステートマシンを試し、if-then-else-strncmpを試し、その間に最適化を行います。 コーディングスタイル、ツール/プロセッサ、および解決される問題の両方で、どれが最適かをすぐに確認できます。


2


それは、どの程度空想を得たいか、いくつの異なるコマンドがあるか、そして新しいコマンドが頻繁に追加される可能性があるかどうかに依存します。

有効な各コマンド文字列を対応する関数ポインターに関連付けるデータ構造を作成できます-「bsearch()」でアクセスされるソートされたリストはおそらく問題ありませんが、ハッシュテーブルはパフォーマンスが向上する可能性がある代替手段です(有効なセットコマンドは事前に知られているので、http://www.gnu.org/software/gperf/ [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


あなたがまだこれに取り組んでいるかどうかわからない。 しかし、私は同様のプロジェクトに取り組んでおり、埋め込まれたコマンドラインインタープリターhttp://sourceforge.net/projects/ecli/?source=recommendedを見つけました。 そうです、彼らは組み込みアプリケーションを念頭に置いていました。

cli_engine関数は、コマンドラインから入力を取得するのに非常に役立ちます。

警告:READMEファイル以外にドキュメントはありません。 私はまだフレームワークを統合するいくつかのバグに取り組んでいますが、これは間違いなく有利なスタートを切りました。 文字列の比較に対処する必要があります(つまり、 strcmpを使用して)自分で。