マイクロコントローラー上のDIYラインコマンドインタープリター

開発中の各デバイスでは、最も一般的でシンプルなインターフェイスのように、UARTでデバッグ出力がありました。

そして、遅かれ早かれ、パッシブ出力に加えて、毎回同じUARTを介してコマンドを入力したかったのです。 通常、これは、非常に大量の情報をオンデマンドでデバッグしたいときに発生しました(たとえば、独自のファイルシステムを開発するときのNANDFLASHのステータス)。 また、ボード上のいくつかの周辺機器で作業をリハーサルするために、GPIOのレッグをプログラムで制御したいことがありました。

どういうわけか、さまざまなコマンドを処理できるCLIが必要でした。 誰かがこれらの目的のために既製のツールに出くわした場合-コメントのリンクに感謝します。 それまでの間、私は自分で書きました。



重要度の降順での要件:

  1. 言語C 状況は変わるかもしれませんが、私は他のものでマイクロコントローラー用のソフトウェアを書く準備ができていません。
  2. UARTからの文字列の受信と処理。 簡単にするために、すべての行は'\ n'で終わります。
  3. コマンドにパラメーターを渡す機能。 パラメータのセットは、チームごとに異なります。
  4. 新しいチームの追加のしやすさ。
  5. 異なるソースファイルに新しいコマンドを追加する機能。 つまり ファイル「 new_feature.c 」の次の機能の実装を開始します。CLIソースには触れませんが、同じファイル「 new_feature.c 」に新しいコマンドを追加します。
  6. 使用される最小リソース(RAM、ROM、CPU)。


受信した文字を静的バッファに保存し、行頭のスペースを破棄して改行文字を待つUARTドライバーについては詳しく説明しません。

もっと面白いものから始めましょう- '\ n'で終わる行があります。 次に、適切なコマンドを見つけて実行する必要があります。

フォーム内のソリューション

typedef void (*cmd_callback_ptr)(const char*); typedef struct { const char *cmd_name; cmd_callback_ptr callback; }command_definition;
      
      





登録したチームのセットで、探している名前のチームを探します。 思わぬ障害-この検索を実装する方法? または、より正確には、このセットを作成する方法は?

C ++の場合、最も明白な解決策は、 std :: map <char *、cmd_callback_ptr>を使用して、(どのようにでも)検索することです。 その後、コマンドを登録するプロセスは、辞書へのハンドラー関数へのポインターを追加することになります。 しかし、私はCで書いており、まだC ++に切り替えたくありません。

次のアイデアは、グローバル配列command_definition registered_commands [] = {...}ですが、この方法は、異なるファイルからコマンドを追加する要件に違反します。

「より大きな」配列を作成し、関数などのコマンドを追加します

 #define MAX_COMMANDS 100 command_definition registered_commands[MAX_COMMANDS]; void add_command(const char *name, cmd_callback_ptr callback) { static size_t commands_count = 0; if (commands_count == MAX_COMMANDS) return; registered_command[commands_count].cmd_name = name; registered_command[commands_count].callback = callback; commands_count++; }
      
      



私もそう感じません MAX_COMMANDS定数を絶えず修正するか、メモリを無駄に消費する必要があります...一般に、それはなんとなくugいです:-)

動的メモリ割り当てで同じことを行い、追加ごとにreallocで割り当てられた配列を増やすことはおそらく良い方法ですが、動的メモリを台無しにしたくはありませんでした(プロジェクトの他の場所では使用されませんが、ROMで多くのコードを必要とし、 RAMはゴムではありません)。



その結果、私は次の好奇心に気付きましたが、残念ながら、最もポータブルなソリューションではありません。

 #define REGISTER_COMMAND(name, func) const command_definition handler_##name __attribute__ ((section ("CONSOLE_COMMANDS"))) = \ { \ .cmd_name = name, \ .callback = func \ } extern const command_definition *start_CONSOLE_COMMANDS; //     CONSOLE_COMMANDS extern const command_definition *stop_CONSOLE_COMMANDS; //     CONSOLE_COMMANDS command_definition *findCommand(const char *name) { for (command_definition *cur_cmd = start_CONSOLE_COMMANDS; cur_cmd < stop_CONSOLE_COMMANDS; cur_cmd++) { if (strcmp(name, cur_cmd->cmd_name) == 0) { return cur_cmd; } } return NULL; }
      
      



ここでのすべての魔法は、グローバル変数を作成するREGISTER_COMMANDマクロにあります。これにより、コードが実行されたときに、厳密に次々にメモリに格納されます。 そして、このマジックはセクション属性に依存しており、この変数はメモリの別のセクションにこの変数を配置する必要があることをリンカーに伝えます。 したがって、出力では、前の例のregistered_commands配列に非常に似たものが得られますが、その中に含まれる要素の数を事前に知る必要はありません。 そして、リンカはこの配列の始まりと終わりへのポインタを提供します。

要約すると、このソリューションの長所と短所を書き留めてください。

長所:



短所:



最後のプラスを犠牲にして最後のマイナスを克服できます-コマンドをRAMに配置し、それらをソートできます。 または、 strcmpを介さずに比較するハッシュ関数を事前計算することもできます。



All Articles