マイクロコントローラーのプログラミング時に、C ++と可変数の引数を持つテンプレートを使用する

Cortex Mxコアを備えたARM(STM32F10xを例として使用)



KDPV ARM Cortex M3 STM32F103c8t6マイクロコントローラーは、アマチュアプロジェクトの32ビットマイクロコントローラーとして広く使用されています。 ほぼすべてのマイクロコントローラーについては、特にコントローラーの周辺機器を決定するためのC ++ヘッダーファイルを含むSDKがあります。



そして、たとえば、シリアルポートはデータ構造として定義され、この構造のインスタンスはレジスタ用に予約されたアドレス領域にあり、特定のアドレスへのポインタを介してこの領域にアクセスできます。



これに出会ったことがない人のために、私はそれがどのように定義されているかについて少し説明します。これに精通している同じ読者はこの説明をスキップするかもしれません。



この構造とそのインスタンスは次のとおりです。



/* =========================================================================*/ typedef struct { __IO uint32_t CR1; /*!< USART Control register 1, Address offset: 0x00 */ . . . __IO uint32_t ISR; /*!< USART Interrupt and status register, ... */ } USART_TypeDef; // USART_Type   . /* =========================================================================*/ #define USART1_BASE (APBPERIPH_BASE + 0x00013800) . . . #define USART1 ((USART_TypeDef *) USART1_BASE) #define USART1_BASE 0x400xx000U
      
      





詳細はこちらをご覧くださいstm32f103xb.h≈800 kB



このファイルの定義のみを使用する場合、次のように記述する必要があります(シリアルポートステータスレジスタの使用例)。



 // ---------------------------------------------------------------------------- if (USART1->ISR & (ONE_ISR_FLAG & OTHER_ISR_FLAG)) { }
      
      





CMSISとHALとして知られる既存の独自ソリューションは、アマチュアプロジェクトで使用するには複雑すぎるため、使用する必要があります。



ただし、C ++で記述する場合、次のように記述できます。



 // ---------------------------------------------------------------------------- USART_TypeDef & Usart1 = *USART1; // ---------------------------------------------------------------------------- if (Usart1.ISR & (ONE_ISR_FLAG & OTHER_ISR_FLAG)) { }
      
      





可変参照は、ポインターで初期化されます。 これは少し安心ですが、楽しいです。 もちろん、これは小さなラッパークラスを記述する方が良いのですが、この手法はまだ有用です。



もちろん、私はすぐにこのラッパークラスをシリアルポート(EUSART-拡張ユニバーサルシリアル非同期受信機-トランスミッター)ですぐに書きたいと思います。非常に魅力的で、高度な機能、シリアル非同期トランシーバー、小型のマイクロコントローラーをデスクトップシステムまたはラップトップに接続できますが、マイクロコントローラーCortexは、開発されたクロックシステムによって区別されます。STM32F1xxシリーズでは、次のように、それから始めて、対応するI / Oポートピンをペリフェラルで動作するように設定する必要があります。 足の他のARMのCortexマイクロコントローラは、単に周辺と同時に入力または出力と仕事にポート端子を設定することはできません。



それでは、タイミングをオンにすることから始めましょう。 クロックシステムは、クロック制御のためにRCCレジスタと呼ばれ、特定のアドレス値が割り当てられるポインターであるデータ構造も表します。



 /* =========================================================================*/ typedef struct { . . . } RCC_TypeDef;
      
      





__IOがvolatileを定義する、このように宣言されたこの構造体のフィールド:



 /* =========================================================================*/ __IO uint32_t CR;
      
      





RCCからのレジスタに対応し、これらのレジスタの個々のビットがオンになるか、マイクロコントローラ周辺のクロック機能がオンになります。 これはすべて、 ドキュメント(pdf)で詳しく説明されています



構造体へのポインターは次のように定義されます



 /* =========================================================================*/ #define RCC ((RCC_TypeDef *)RCC_BASE)
      
      





通常、SDKを使用せずにレジスタビットを操作するには、次のようにします。



これがポートAのインクルードです。



 // ---------------------------------------------------------------------------- RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
      
      





一度に2つ以上のビットを有効にできます



 // ---------------------------------------------------------------------------- RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;
      
      





C ++の場合は少し変わっているように見えますが、珍しいことでもあります。 たとえば、OOPを使用して、このように別の方法で記述した方が良いでしょう。



 // ---------------------------------------------------------------------------- Rcc.PortOn(Port::A);
      
      





見た目は良くなっていますが、XXI世紀にはもう少し進んで、C ++ 17を使用し、可変数のパラメーターを持つテンプレートを使用してさらに美しいものを作成します。



 // ---------------------------------------------------------------------------- Rcc.PortOn<Port::A, Port::B>();
      
      





Rccは次のように定義されます:



 // ---------------------------------------------------------------------------- TRcc & Rcc = *static_cast<TRcc *>RCC;
      
      





これから、クロックレジスタのラッパーの構築を開始します。 最初に、クラスとそのクラスへのポインター(リンク)を定義します。



最初は、関数テンプレートのパラメーターを再帰的にアンパックして、C ++ 11/14標準で記述したかったのです。 これに関する良い記事は、記事の最後のリンクセクションにあります。



 // ============================================================================ enum class GPort : uint32_t { A = RCC_APB2ENR_IOPAEN, B = RCC_APB2ENR_IOPBEN, C = RCC_APB2ENR_IOPCEN, }; // ---------------------------------------------------------------------------- class TRcc: public ::RCC_TypeDef { private: TRcc() = delete; ~TRcc() = delete; // ======================================================================== public: template<GPort... port> inline void PortOn(void) //    (inline) { //    -Og  -O0 APB2ENR |= SetBits<(uint32_t)port...>(); } // ------------------------------------------------------------------------ #define BITMASK 0x01 //    ,   #define MASKWIDTH 1 //      .   //          #undef. private: //   (fold)   . template<uint8_t bitmask> inline constexpr uint32_t SetBits(void) { //   ,  GPort  enum // (, , bitmask    ). // static_assert(bitmask < 16, " ."); return bitmask; } template<uint8_t bit1, uint8_t bit2, uint8_t... bit> inline constexpr uint32_t SetBits(void) { return SetBits<bit1>() | SetBits<bit2, bit...>(); } }; #undef BITMASK #undef MASKWIDTH // ------------------------------------------------------------------------ TRcc & Rcc = *static_cast<TRcc *>RCC;
      
      





ポートクロックイネーブル機能の呼び出しを検討してください。



  Rcc.PortOn<GPort::A>();
      
      





GCCは、次の一連のコマンドにそれを展開します。



  ldr r3, [pc, #376] ; (0x8000608 <main()+392>) ldr r0, [r3, #24] orr.w r0, r0, #4 str r0, [r3, #24]
      
      





うまくいきましたか? 次を確認



  Rcc.PortOn<GPort::A, GPort::B, GPort::C>();
      
      





残念ながら、それほど素朴ではないGCCは、後続の再帰呼び出しを個別に展開しました。



  ldr r3, [pc, #380] ; (0x8000614 <main()+404>) ldr r0, [r3, #24] orr.w r0, r0, #4 ; APB2ENR |= GPort::A str r0, [r3, #24] ldr r0, [r3, #24] orr.w r0, r0, #28 ; APB2ENR |= Gport::B | GPort::C str r0, [r3, #24] #24]
      
      





GCCを守るために、これは常にそうであるとは言いませんが、I / Oポートクラスを実装するときに見られるより複雑なケースでのみです。 さて、C ++ 17が急いでいるので、組み込みのスクロール機能を使用してTRCCクラスを書き直してください。



 // ---------------------------------------------------------------------------- class TRcc: public ::RCC_TypeDef { private: TRcc() = delete; //     ,  ~TRcc() = delete; //    . // ======================================================================== public: template<GPort... port> inline void PortOn(void) //    (inline) { //    -Og  -O0 APB2ENR |= SetBits17<(uint32_t)port...>(); } // ------------------------------------------------------------------------ #define BITMASK 0x01 //    ,   #define MASKWIDTH 1 //      .   //          #undef. private: //   (fold)   . ++ 17. template<uint8_t... bitmask> inline constexpr uint32_t SetBits17(void) { return (bitmask | ...); //     ... | bit } }; #undef BITMASK #undef MASKWIDTH
      
      





今では判明しました:



 ldr r2, [pc, #372] ; (0x800060c <main()+396>) ldr r0, [r2, #24] orr.w r0, r0, #28 ; APB2ENR |= Gport::A | Gport::B | GPort::C str r0, [r3, #24]
      
      





また、クラスコードがよりシンプルになりました。



結論:C ++ 17では、可変パラメーターのテンプレートを使用して、レジスター定義を介してマイクロコントローラーを使用した従来の作業を使用した場合に得られるのと同じ最小命令セットを取得できますが、同時に強力なC ++タイピング、チェックのすべての利点が得られますコンパイル中、コードの基本クラスの構造を通じて再利用されます。



これは、C ++で書かれたこのようなものです。



 Rcc.PortOn<Port::A, Port::B, Port::C>();
      
      





そして、レジスターに関する古典的なテキスト:



 RCC->APB2 |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;
      
      





最適な一連の指示で展開します。 GCCによって生成されたコードは次のとおりです(-Ogからの最適化):



  ldr r2, [pc, #372] ; (0x800060c <main()+396>) [  RCC] ldr r0, [r2, #0] ; r0 = RCC->APB2 // [  APB2] orr.w r0, r0, #160 ; r0 |= 0x10100000 str r0, [r2, #0] ; RCC->APB2 = r0
      
      





これで作業を続け、入出力ポートのクラスを作成する必要があります。 I / Oポートビットの操作は、1つのポートレッグの構成に4ビットが割り当てられているため複雑です。したがって、16ビットポートには64ビットの構成が必要であり、2つの32ビットCRLおよびCRHレジスタに分割されます。 さらに、ビットマスクの幅は1を超えます。ただし、ここでは、C ++ 17をスクロールするとその機能が示されます。



画像



次に、TGPIOクラスと、他の周辺機器、シリアルポート、I2C、SPI、DAP、タイマーなどを操作するためのクラスが記述されます。これらは通常、ARM Cortexマイクロコントローラーに存在し、そのようなLEDで点滅することができます。



しかし、それについては次のノートで説明します。 githubのプロジェクトのソース



メモを書くときに使用されるインターネット記事



C ++ 11の可変数の引数を持つテンプレート

テンプレートの革新

C ++ 17.の言語の革新 パート1.畳み込みと導出

STMマイクロコントローラのドキュメントへのリンクのリスト

可変パラメーターマクロ



このメモを書くように促したKhabrの記事



Attiny13の信号機



ジュリアン・アサンジ、英国警察に逮捕

あいまいな記憶としての空間



書かれた2019年4月12日-幸せな宇宙飛行士の日!







PS
Stm CubeMxのSTM32F103c8t6 CubeMXの画像STM32F103c8t6。



開始点として、 GNU MCU Eclipse ARM EmbeddedおよびSTM CubeMXマイクロコントローラーを操作するためにEclips拡張機能によって作成されたテキストが使用されます 。つまり、標準C ++、 _ start ()および_init()関数のファイルがあり、割り込みベクトルの定義はEclipse MCUから取得されますARM Embedded、およびCortex M3コアのレジスタおよびワークファイルは、CubeMXによって作成されたプロジェクトからのものです。







PPS
KDPVでは、STM32F103c8t6コントローラーを使用したデバッグが表示されます。 誰もがそのようなボードを持っているわけではありませんが、購入することは難しくありませんが、これはこの記事の範囲外です。




All Articles