AVRコードメモリの行

当社では、AVRシリーズコントローラー用のプログラムを作成しています。 この記事では、コードメモリに配置された行を作成する方法について説明します。



最初は、エラーを生成するために次のコードは必要ありませんでしたが、最終的には予想よりもはるかに強力なツールを入手しました。



const char *pStr = PSTR("Hello"); //    . // error: statement-expressions are not allowed outside functions nor in template-argument lists int main() {…}
      
      





AVRシリーズのマイクロコントローラのメモリを操作する問題を知らない人は、スポイラーを見ることができます
AVRコントローラーは、2つの独立したアドレススペースを使用します。



  • コード用
  • RAMおよびレジスタ用。


GCCコンパイラは、2バイトポインターを使用して、コードメモリの最初の64K(残りは命令にのみ使用できます)またはRAM全体へのアクセスを提供します。



しかし、変数がどのメモリにあるかをポインタで見つけることはできません。 このため、avr-gccライブラリには、コードメモリとその中にある行を操作するための個別の関数があります。 関数名の末尾に接尾辞「_P」が付いています。 たとえば、strcpy_Pは、コードメモリ内の文字列へのポインタを取得するstrcpy関数に類似しています。



残念ながら、コンパイラは変数がどこで使用されるかを確認してそこに配置することができないため、プログラマーは自分でこれを処理する必要があります。 したがって、コードを処理するつもりであることを明示的に示すために、コードメモリにある変数をPROGMEMキーワードでマークする必要がありました。



ただし、これにより、プログラマが変数の正しい使用を監視する必要がなくなるわけではありません。



私たちにとって最も不便だったのは弦です。 文字列リテラルはRAMにあるオブジェクトです。つまり、RAMとコードメモリの両方を占有します(どこからでも初期化の値を取得する必要があります)。 繰り返しますが、これらはコードメモリで機能する関数の使用には適していません。 例:



 int main() { char dest[20]; strcpy_P(dest, "Hello world!"); }
      
      





このコードは、RAM内の文字列「Hello world!」と同じアドレスにあるコードメモリからデータを取得するため、未定義の結果につながります。



これらの場合、PSTRマクロ(テキスト)はavrライブラリで提供され、コードメモリにある文字列へのポインタを返します。



 int main() { char dest[20]; strcpy_P(dest, PSTR("Hello world!")); }
      
      





現在、このコードは機能し、RAMを使用しません。 ただし、このマクロを関数の範囲外にすることは価値があり、動作を停止します。



 const char *pStr = PSTR("Hello"); //    . // error: statement-expressions are not allowed outside functions nor in template-argument lists int main() {…}
      
      





私はこのコードのようなものを書かなければなりませんでした:



 extern const char PROGMEM caption1[]; const char caption1[] = "Hello"; const char *pStr = caption1;
      
      





これは大げさな例ですが、pStrの代わりに、文字列へのポインタを期待するある種のユーザー構造を初期化することを想像してください。



まず、メニュー構造を初期化する必要がありました。 すべての初期化は、コンパイル段階で静的に実行する必要がありました。



したがって、コードメモリ内の文字列へのポインターを取得するための信頼できる方法を探し始めました。 テンプレートクラスはこれに役立ちました。 テンプレートクラスの場合、コードメモリに配置された静的変数を作成し、そのポインターを取得できます。



 template <char value> struct ProgmemChar { static const char PROGMEM v; }; template <char value> const char ProgmemChar<value>::v = value; const char *pChar = &(ProgmemChar<'a'>::v);
      
      





しかし、文字列はパラメーターとしてテンプレートに渡されません。 したがって、文字列を文字に分割することにしました。 行をさらに文字に分割する方法を示しますが、ここではコードメモリ内の行の簡単な例を示します。



 template <char ch1, char ch2, char ch3, char ch4, char ch5> struct ProgmemString { static const char PROGMEM v[5]; }; template <char ch1, char ch2, char ch3, char ch4, char ch5> const char ProgmemString<ch1, ch2, ch3, ch4, ch5>::v[5] = {ch1, ch2, ch3, ch4, ch5}; const char *pStr = ProgmemString<'a', 'b', 'c', 'd', 0>::v;
      
      





この例は、サイズがちょうど4文字で、末尾が0で終わる行に対して機能します。 また、行ProgmemString <'a'、0、0、0、0>も5バイトを占有します。



この問題を解決するために、テンプレートクラスの部分的な特殊化を使用して、別の行サイズをテンプレートに追加しました。 基本テンプレートクラスは次のとおりです。



 template<size_t S, char... L>struct _Pstr;
      
      





次に、文字列を文字に分割する問題に戻ります。 正直に言うと、ソース文字列からi番目(0〜N-1)の文字をN回取得するマクロを作成するよりも優れた方法を考え出すことができなかったため、これは依然として問題です。



 #define SPLIT_TO_CHAR_4(STR) STR[0], STR[1], STR[2], STR[3]
      
      





このマクロは、少なくとも4文字の文字列を文字に分割します。 この場合、N = 4です。



プリプロセッサの後のコードを見ると、次のコードが表示されます。



 "Hello world!"[0], "Hello world!"[1], "Hello world!"[2], "Hello world!"[3]
      
      





これは長いテキストであることに同意しますが、我慢します。 さらに、コンパイラ自体はすでに4文字しか生成していません。



より重要な問題は、大きなインデックスを持つキャラクターをキャプチャすることでした。 Nが大きい場合(およびすべての行をNより短くする必要がある場合)、行の外にある文字を取得したい場合は必ずあり、コンパイルエラーが発生します。



最初の有効なオプションは次の方法でした:



  1. 元の行に、文字「\ 0」で構成され、長さがN文字の文字列を追加します。 追加は次のように行われました:#define ADD_STR(STR)STR "\ 0 \ 0 \ ... \ 0"。
  2. 結果の文字列に対してSPLIT_TO_CHAR操作を実行します。


このメソッドは機能しますが、プリプロセッサの後のコードをN * N文字増やすことが保証されています。 その結果、コンパイラの制限をすぐに取得できます。



幸いなことに、C ++ 11およびconstexpr関数の登場により、文字セレクタークラスを使用して余分な文字を取り除くことができました。 簡潔にするため、_CS(Char Selector)と呼ばれます。



 struct _CS { template<size_t n> constexpr _CS(const char (&s)[n]) :s(s), l(n){} constexpr char operator [](size_t i){return i < l ?s[i] :0;} const char *s = 0; const size_t l = 0; };
      
      





私はずっと前にHabréでこのクラスのコードを覗きましたが、正確な場所を見つけることができません(著者のおかげです)。

文字分離マクロコードがよりシンプルになりました。



 #define SPLIT_TO_CHAR(STR) _CS(STR)[0], _CS(STR)[1], …, _CS(STR)[N-1]
      
      





今ではすべてをまとめて収集することが残っています。



 //    template<size_t S, char... L>struct _PStr; //  ,    .     10  #define ARGS01(P, S) P##00 S #define ARGS02(P, S) ARGS01(P, S),P##01 S #define ARGS03(P, S) ARGS02(P, S),P##02 S #define ARGS04(P, S) ARGS03(P, S),P##03 S #define ARGS05(P, S) ARGS04(P, S),P##04 S #define ARGS06(P, S) ARGS05(P, S),P##05 S #define ARGS07(P, S) ARGS06(P, S),P##06 S #define ARGS08(P, S) ARGS07(P, S),P##07 S #define ARGS09(P, S) ARGS08(P, S),P##08 S #define ARGS0A(P, S) ARGS09(P, S),P##09 S //       ( 0  10 ).     0. template<char... L>struct _PStr<0x00, L...>{static const char PROGMEM v[];}; template<char... L>const char _PStr<0x00, L...>::v[] = {0}; template<ARGS01(char _,), char... L>struct _PStr<0x01, ARGS01(_,), L...>{static const char PROGMEM v[];}; template<ARGS01(char _,), char... L>const char _PStr<0x01, ARGS01(_,), L...>::v[] = {ARGS01(_,), 0}; template<ARGS02(char _,), char... L>struct _PStr<0x02, ARGS02(_,), L...>{static const char PROGMEM v[];}; template<ARGS02(char _,), char... L>const char _PStr<0x02, ARGS02(_,), L...>::v[] = {ARGS02(_,), 0}; template<ARGS03(char _,), char... L>struct _PStr<0x03, ARGS03(_,), L...>{static const char PROGMEM v[];}; template<ARGS03(char _,), char... L>const char _PStr<0x03, ARGS03(_,), L...>::v[] = {ARGS03(_,), 0}; template<ARGS04(char _,), char... L>struct _PStr<0x04, ARGS04(_,), L...>{static const char PROGMEM v[];}; template<ARGS04(char _,), char... L>const char _PStr<0x04, ARGS04(_,), L...>::v[] = {ARGS04(_,), 0}; template<ARGS05(char _,), char... L>struct _PStr<0x05, ARGS05(_,), L...>{static const char PROGMEM v[];}; template<ARGS05(char _,), char... L>const char _PStr<0x05, ARGS05(_,), L...>::v[] = {ARGS05(_,), 0}; template<ARGS06(char _,), char... L>struct _PStr<0x06, ARGS06(_,), L...>{static const char PROGMEM v[];}; template<ARGS06(char _,), char... L>const char _PStr<0x06, ARGS06(_,), L...>::v[] = {ARGS06(_,), 0}; template<ARGS07(char _,), char... L>struct _PStr<0x07, ARGS07(_,), L...>{static const char PROGMEM v[];}; template<ARGS07(char _,), char... L>const char _PStr<0x07, ARGS07(_,), L...>::v[] = {ARGS07(_,), 0}; template<ARGS08(char _,), char... L>struct _PStr<0x08, ARGS08(_,), L...>{static const char PROGMEM v[];}; template<ARGS08(char _,), char... L>const char _PStr<0x08, ARGS08(_,), L...>::v[] = {ARGS08(_,), 0}; template<ARGS09(char _,), char... L>struct _PStr<0x09, ARGS09(_,), L...>{static const char PROGMEM v[];}; template<ARGS09(char _,), char... L>const char _PStr<0x09, ARGS09(_,), L...>::v[] = {ARGS09(_,), 0}; template<ARGS0A(char _,), char... L>struct _PStr<0x0A, ARGS0A(_,), L...>{static const char PROGMEM v[];}; template<ARGS0A(char _,), char... L>const char _PStr<0x0A, ARGS0A(_,), L...>::v[] = {ARGS0A(_,), 0}; //   struct _CS { template<size_t n> constexpr _CS(const char (&s)[n]) :s(s), l(n){} constexpr char operator [](size_t i){return i < l ?s[i] :0;} const char *s = 0; const size_t l = 0; }; //      #define STR_UNION(...) __VA_ARGS__ //  ,    ,    . SPS = StaticProgramString. #define SPS(T) STR_UNION(_PStr<_CS(T).l - 1, ARGS0A(_CS(T)[0x, ])>::v)
      
      





メインマクロを要素に分解しましょう:





各行に対して、行の長さに適したテンプレートの独自の特化が選択されます。






要約すると、このマクロの助けを借りて、このマクロが使用されている場所に関係なく、コード内の行へのポインターを取得するだけでなく、PSTRに対する2つの明らかな利点も実装できました。





 template <class T, const char *name> struct NamedType { T value; static const char *getName() { return name; } }; NamedType<int, SPS("")> var1 = {3};
      
      





これらのテンプレートクラスを使用すると、プロジェクト内の変数に関するメタデータを収集できるため、ユーザーインターフェイスとカスタマイズの柔軟性が向上し、開発を大幅に簡素化できます。 しかし、それは別の話です。



All Articles