1クロックサイクルまたは魔法の500行のコードでコントローラー周辺機器をオンにします





マイクロコンピュータ用のファームウェアを開発するとき、デバッグ中に、バイトがUARTで実行されていないときに、次のように叫ぶ頻度があります。クロッキングを有効にしませんでした!」または、LEDレッグを変更したときに、新しいポートに「電力を供給する」ことを忘れましたか?かなり頻繁に、私は思います。私は、少なくとも-確かに。



一見すると、周辺のタイミングを制御するのは簡単なように思えるかもしれません。1-有効、0-無効と書かれています。



しかし、「シンプル」が常に効果的であるとは限りません...



問題の定式化



コードを書く前に、それを評価できる基準を決定する必要があります。コントローラのペリフェラルクロッキングシステムの場合、リストは次のようになります。



  • 組み込みシステムでは、最も重要な基準の1つは、可能な限り短い時間で実行される、結果として得られる最小のコードです。
  • . - code review , /
  • , ,
  • ( )


評価基準を明確にした後、実装の条件と「環境」を定義する過程で、特定のタスクを設定します。



コンパイラ:GCC 10.1.1 +

言語の作成:C ++ 17

環境:Visual Studioコード

コントローラ:stm32f103c8t6(cortex-m3)

タスク:クロッキングを有効にするSPI2、USART1(両方ともDMAを使用するインターフェース)



このコントローラーの選択は、もちろん、特に中国の民芸品の1つであるBlue Pillボードの製造のおかげで、その普及によるものです。







イデオロギーの観点からは、どのコントローラーを選択するかは重要ではありません。stmf1、stmf4、またはlpcです。周辺クロックシステムでの作業は、オフの場合は0、オンの場合は1のいずれかの特定のビットへの書き込みにのみ制限されます。



stm32f103c8t6には、ペリフェラルクロッキングの有効化を担当する3つのレジスタ(AHBENR、APB1ENR、APB2ENR)があります。



データ転送SPI2およびUSART1のハードウェアインターフェイスは、完全に機能するために、リストされているすべてのレジスタにあるクロックビット(インターフェイス自体のビット、DMA1、および入力出力ポートのビット(SPI2の場合はGPIOB、USART1の場合はGPIOA))を有効にする必要があるため、偶然に選択されませんでした。









クロッキングで最適なパフォーマンスを得るには、次のことを考慮する必要があることに注意してください。AHBENRには、SPI2とUSART1の両方の機能に使用される共有リソースが含まれています。つまり、DMAを無効にすると、すぐに両方のインターフェイスが動作しなくなります。同時に、この操作はプログラムメモリを消費し、揮発性レジスタの読み取り、変更、書き込みに追加のクロックを消費するため、再閉路効率はゼロではなく負になります。



問題の目標、条件、機能を扱った後、解決策を見つけることに移りましょう。



基本的なアプローチ



このセクションには、私が遭遇したペリフェラルクロッキングを有効にする一般的な方法が含まれています。確かに、それらを確認および/または使用したこともあります。Cで実装された単純なものから、C ++ 17からのフォールド式まで。それらの固有の長所と短所が考慮されます。



メタプログラミングに直接移動する場合は、このセクションをスキップして次のセクションに進むことができます



レジスタへの直接書き込み



CとC ++の両方で「箱から出してすぐに利用できる」という古典的な方法。ベンダーは、ほとんどの場合、コントローラー用のヘッダーファイルを提供します。このヘッダーファイルでは、すべてのレジスターとそのビットがデフォルト設定されているため、周辺機器の操作をすぐに開始できます。



int main(){
  RCC->AHBENR  |= RCC_AHBENR_DMA1EN;
  RCC->APB2ENR |= RCC_APB2ENR_IOPAEN
               |  RCC_APB2ENR_IOPBEN
               |  RCC_APB2ENR_USART1EN;
  RCC->APB2ENR |= RCC_APB1ENR_SPI2EN;
}


リスト
    // AHBENR( DMA1)
  ldr     r3, .L3
  ldr     r2, [r3, #20]
  orr     r2, r2, #1
  str     r2, [r3, #20]
    // APB2ENR( GPIOA, GPIOB, USART1)
  ldr     r2, [r3, #24]
  orr     r2, r2, #16384
  orr     r2, r2, #12
  str     r2, [r3, #24]
    // APB1ENR( SPI2)
  ldr     r2, [r3, #28]
  orr     r2, r2, #16384
  str     r2, [r3, #28]




コードサイズ:36バイト。長所を見る







  • 最小コードサイズと実行速度
  • 最も簡単で明白な方法


マイナス:



  • レジスターの名前とビットの名前を覚えておくか、常にマニュアルを参照する必要があります
  • コードを間違えるのは簡単です。読者は、SPI2の代わりにUSART1が再び有効になったことに気付いたに違いありません。
  • 一部のペリフェラルユニットが機能するには、インターフェイスのGPIOやDMAなどの他のペリフェラルも有効にする必要があります
  • 移植性の完全な欠如。別のコントローラーを選択すると、このコードはその意味を失います


すべての欠点がありますが、少なくとも次の「Hello、World!」を作成して新しいコントローラーを「感じる」必要がある場合は、この方法は非常に人気がありますLEDを点滅させることによって。



初期化機能



レジスターを使用した作業をユーザーから抽象化して非表示にしてみましょう。そして、通常のC関数はこれに役立ちます:



void UART1_Init(){
 RCC->AHBENR  |= RCC_AHBENR_DMA1EN;
 RCC->APB2ENR |= RCC_APB2ENR_IOPAEN
              |  RCC_APB2ENR_USART1EN;
  //  
}

void SPI2_Init(){
 RCC->AHBENR  |= RCC_AHBENR_DMA1EN;
 RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
 RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;
  //  
}

int main(){
  UART1_Init();
  SPI2_Init();
}


コードサイズ:72バイト。見て



リスト
UART1_Init():
    // AHBENR( DMA1)
  ldr     r2, .L2
  ldr     r3, [r2, #20]
  orr     r3, r3, #1
  str     r3, [r2, #20]
    // APB2ENR( GPIOA, USART1)
  ldr     r3, [r2, #24]
  orr     r3, r3, #16384
  orr     r3, r3, #4
  str     r3, [r2, #24]
  bx      lr
SPI2_Init():
    // (!) AHBENR( DMA1)
  ldr     r3, .L5
  ldr     r2, [r3, #20]
  orr     r2, r2, #1
  str     r2, [r3, #20]
    // (!) APB2ENR( GPIOB)
  ldr     r2, [r3, #24]
  orr     r2, r2, #8
  str     r2, [r3, #24]
    //  APB1ENR( SPI2)
  ldr     r2, [r3, #28]
  orr     r2, r2, #16384
  str     r2, [r3, #28]
  bx      lr
main:
   push    {r3, lr}
   bl      UART1_Init()
   bl      SPI2_Init()




長所:



  • 毎回マニュアルを見る必要はありません。
  • エラーは、ペリフェラルドライバーを作成する段階でローカライズされます
  • カスタムコードは読みやすい


マイナス:



  • 必要な命令の数は、関係する周辺機器の数の倍数で増加しています
  • 多くのコードの重複-UARTとSPI番号ごとに、実質的に同一になります


ユーザーコードのレジスタへの直接書き込みを排除しましたが、どのくらいの費用がかかりますか?オンにするために必要なメモリサイズと実行時間は2倍になり、今後も増え続け、より多くの周辺機器が関与します。



クロックイネーブル機能



必要なメモリの量が減ると仮定して、クロックの変更を別の関数でラップしましょう。同時に、周辺機器の識別子パラメータを導入して、ドライバコードを削減します。



void PowerEnable(uint32_t ahb, uint32_t apb2, uint32_t apb1){
    RCC->AHBENR  |= ahb;
    RCC->APB2ENR |= apb2;
    RCC->APB1ENR |= apb1;
}

void UART_Init(int identifier){
    uint32_t ahb = RCC_AHBENR_DMA1EN, apb1 = 0U, apb2 = 0U;
    if (identifier == 1){
      apb2 = RCC_APB2ENR_IOPAEN | RCC_APB2ENR_USART1EN;
    } 
    else if (identifier == 2){…}
    PowerEnable(ahb, apb2, apb1);
  //  
}

void SPI_Init(int identifier){
    uint32_t ahb = RCC_AHBENR_DMA1EN, apb1 = 0U, apb2 = 0U;
    if (identifier == 1){…} 
    else if (identifier == 2){
      apb2 = RCC_APB2ENR_IOPBEN;
      apb1 = RCC_APB1ENR_SPI2EN;
    }
    PowerEnable(ahb, apb2, apb1);
  //  
}

int main(){
  UART_Init(1);
  SPI_Init(2);
}


コードサイズ:92バイト。見て



リスト
PowerEnable(unsigned long, unsigned long, unsigned long):
  push    {r4}
  ldr     r3, .L3
  ldr     r4, [r3, #20]
  orrs    r4, r4, r0
  str     r4, [r3, #20]
  ldr     r0, [r3, #24]
  orrs    r0, r0, r1
  str     r0, [r3, #24]
  ldr     r1, [r3, #28]
  orrs    r1, r1, r2
  str     r1, [r3, #28]
  pop     {r4}
  bx      lr
UART_Init(int):
  push    {r3, lr}
  cmp     r0, #1
  mov     r2, #0
  movw    r1, #16388
  it      ne
  movne   r1, r2
  movs    r0, #1
  bl      PowerEnable(unsigned long, unsigned long, unsigned long)
  pop     {r3, pc}
SPI_Init(int):
  push    {r3, lr}
  cmp     r0, #2
  ittee   eq
  moveq   r1, #8
  moveq   r1, #16384
  movne   r1, #0
  movne   r2, r1
  movs    r0, #1
  bl      PowerEnable(unsigned long, unsigned long, unsigned long)
  pop     {r3, pc}
main:
   push    {r3, lr}
   movs    r0, #1
   bl      UART_Init(int)
   movs    r0, #2
   bl      SPI_Init(int)




長所:

  • マイクロプロセッサドライバの記述コードを短縮することができました
  • 結果として生じる命令の数は減少しました*


マイナス:



  • 実行時間の増加


*はい、この場合、実行可能コードのサイズは以前のバージョンと比較して大きくなっていますが、これは条件付き演算子の出現によるものであり、各タイプのペリフェラルの少なくとも2つのコピーを使用するとその影響を中和できます。



なぜなら include関数はパラメーターを受け取り、スタック操作がアセンブラーに表示されます。これもパフォーマンスに悪影響を及ぼします。



この時点で、マクロを除いて、純粋なCで使用される主なアプローチが考慮されているため、私たちの力はすべてプラス移行する価値があると思います。しかし、この方法も最適とはほど遠いものであり、ユーザーコードを間違える可能性があります。



値のプロパティとテンプレート



積極的なアプローチを検討し始めると、クラスコンストラクタにクロッキングを含めるオプションをすぐにスキップします。この方法は、実際にはCスタイルの初期化関数と同じです。



コンパイル時にレジスタに書き込む必要のあるすべての値がわかっているため、スタック操作を削除します。これを行うには、テンプレートメソッドを使用して別のクラスを作成し、対応するレジスタの値を格納するプロパティ(値の特性)周辺クラスに付与します



struct Power{
template< uint32_t valueAHBENR, uint32_t valueAPB2ENR, uint32_t valueAPB1ENR>
    static void Enable(){
//   = 0,         
        if constexpr (valueAHBENR)
            RCC->AHBENR |= valueAHBENR;
        if constexpr (valueAPB2ENR)
            RCC->APB2ENR |= valueAPB2ENR;
        if constexpr (valueAPB1ENR)
            RCC->APB1ENR |= valueAPB1ENR;
    };

};

template<auto identifier>
struct UART{
//   identifier        
  static constexpr auto valueAHBENR = RCC_AHBENR_DMA1EN;
  static constexpr auto valueAPB1ENR = identifier == 1 ? 0U : RCC_APB1ENR_USART2EN;
  static constexpr auto valueAPB2ENR = RCC_APB2ENR_IOPAEN
                                    |  (identifier == 1 ? RCC_APB2ENR_USART1EN : 0U);
    //  
};

template<auto identifier>
struct SPI{
  static constexpr auto valueAHBENR = RCC_AHBENR_DMA1EN;
  static constexpr auto valueAPB1ENR = identifier == 1 ? 0U : RCC_APB1ENR_SPI2EN;
  static constexpr auto valueAPB2ENR = RCC_APB2ENR_IOPBEN
                                    |  (identifier == 1 ? RCC_APB2ENR_SPI1EN : 0U);
    //  
};

int main(){
    //     
  using uart = UART<1>;
  using spi = SPI<2>;

  Power::Enable<
                uart::valueAHBENR  | spi::valueAHBENR,
                uart::valueAPB2ENR | spi::valueAPB2ENR,
                uart::valueAPB1ENR | spi::valueAPB1ENR
                >();
}


コードサイズ:36バイト。見て



リスト
main:
    // AHBENR( DMA1)
  ldr     r3, .L3
  ldr     r2, [r3, #20]
  orr     r2, r2, #1
  str     r2, [r3, #20]
    // APB2ENR( GPIOA, GPIOB, USART1)
  ldr     r2, [r3, #24]
  orr     r2, r2, #16384
  orr     r2, r2, #12
  str     r2, [r3, #24]
    // APB1ENR( SPI2)
  ldr     r2, [r3, #28]
  orr     r2, r2, #16384
  str     r2, [r3, #28]




長所:



  • サイズと実行時間は、レジスターに直接書き込むリファレンスバージョンと同じであることが判明しました。
  • プロジェクトをスケーリングするのは非常に簡単です-周辺のプロパティ値に対応するを追加するだけで十分です


マイナス:



  • valueプロパティを間違ったパラメータに入力すると、間違いを犯す可能性があります
  • レジスタへの直接書き込みと同様に、移植性が低下します
  • 建設過負荷


いくつかの目標を達成することができましたが、それを使うのは便利ですか?周辺の別のブロックを追加するには、メソッドテンプレートのパラメータでクラスプロパティの正しい配置を制御する必要があるため、そうではないと思います。



理想的...ほぼ



カスタムコードの量とエラーの可能性を減らすために、パラメータパックを使用します。これにより、カスタムコード内の周辺クラスのプロパティへのアクセスが削除されます。これにより、クロッキングを有効にする方法のみが変更されます。



struct Power{
template<typename... Peripherals>
  static void Enable(){
      //        | 
      //    value = uart::valueAHBENR | spi::valueAHBENR  ..
    if constexpr (constexpr auto value = (Peripherals::valueAHBENR | ... ); value)
      RCC->AHBENR |= value;
    if constexpr (constexpr auto value = (Peripherals::valueAPB2ENR | ... ); value)
      RCC->APB2ENR |= value;
    if constexpr (constexpr auto value = (Peripherals::valueAPB1ENR | ... ); value)
      RCC->APB1ENR |= value;
  };
};
int main(){
    //     
  using uart = UART<1>;
  using spi = SPI<2>;

  Power::Enable<uart, spi>();
}


コードサイズ:36バイト。見て



リスト
main:
    // AHBENR( DMA1)
  ldr     r3, .L3
  ldr     r2, [r3, #20]
  orr     r2, r2, #1
  str     r2, [r3, #20]
    // APB2ENR( GPIOA, GPIOB, USART1)
  ldr     r2, [r3, #24]
  orr     r2, r2, #16384
  orr     r2, r2, #12
  str     r2, [r3, #24]
    // APB1ENR( SPI2)
  ldr     r2, [r3, #28]
  orr     r2, r2, #16384
  str     r2, [r3, #28]




以前のバージョンと比較して、ユーザーコードの単純さが大幅に向上し、エラーの可能性が最小限に抑えられ、メモリ消費量は同じレベルに保たれています。



そして、これで止まるようですが...





機能の拡張



目標の1つに目を向けましょう:

ペリフェラルクロッキングを有効または無効にする基本機能に加えて、高度な機能が必要です


タスクがデバイスを低電力にすることであると仮定します。このためには、もちろん、コントローラーが省電力モードを終了するために使用しないすべての周辺機器をオフにする必要があります。



記事の冒頭で述べた条件のコンテキストでは、USART1がウェイクアップイベントのジェネレータであり、SPI2と対応するGPIOBポートを無効にする必要があると想定します。この場合、共有リソースDMA1は有効のままにする必要があります。



前のセクションのオプションを使用すると、この問題を効率的かつ最適に解決することはできません。同時に、関連するブロックを手動で制御する必要があります。

たとえば、最後の方法を考えてみましょう。



int main(){
  using uart = UART<1>;
  using spi = SPI<2>;
    //  USART, SPI, DMA, GPIOA, GPIOB
  Power::Enable<uart, spi>();

    // Some code

    //  SPI  GPIOB  (!)  DMA
  Power::Disable<spi>();
    
    //   DMA (!)  USART  GPIOA
  Power::Enable<uart>();
    
    // Sleep();

    //  SPI  GPIOB (!)  DMA
  Power::Enable<spi>();
}


コードサイズ:100バイト。見て



リスト
main:
        // AHBENR( DMA1)
        ldr     r3, .L3
        ldr     r2, [r3, #20]
        orr     r2, r2, #1
        str     r2, [r3, #20]
       // APB2ENR( GPIOA, GPIOB, USART1)
        ldr     r2, [r3, #24]
        orr     r2, r2, #16384
        orr     r2, r2, #12
        str     r2, [r3, #24]
       // APB1ENR( SPI2)
        ldr     r2, [r3, #28]
        orr     r2, r2, #16384
        str     r2, [r3, #28]
        //  SPI2
       // AHBENR( DMA1)
        ldr     r2, [r3, #20]
        bic     r2, r2, #1
        str     r2, [r3, #20]
       // APB2ENR( GPIOB)
        ldr     r2, [r3, #24]
        bic     r2, r2, #8
        str     r2, [r3, #24]
       // APB1ENR( SPI2)
        ldr     r2, [r3, #28]
        bic     r2, r2, #16384
        str     r2, [r3, #28]
        //  (!)  USART1
        // AHBENR( DMA1)
        ldr     r2, [r3, #20]
        orr     r2, r2, #1
        str     r2, [r3, #20]
       // APB2ENR( GPIOA, USART1)
        ldr     r2, [r3, #24]
        orr     r2, r2, #16384
        orr     r2, r2, #4
        str     r2, [r3, #24]
        // Sleep();
        // AHBENR( DMA1)
        ldr     r2, [r3, #20]
        orr     r2, r2, #1
        str     r2, [r3, #20]
       // APB2ENR( GPIOB)
        ldr     r2, [r3, #24]
        orr     r2, r2, #8
        str     r2, [r3, #24]
       // APB1ENR( SPI2)
        ldr     r2, [r3, #28]
        orr     r2, r2, #16384
        str     r2, [r3, #28]





同時に、レジスターの参照コードは68バイトを要しました。ビュー



明らかに、そのようなタスクの場合、障害はDMAなどの共有リソースになります。さらに、この特定のケースでは、両方のインターフェースが動作不能になる瞬間があり、実際には緊急事態が発生します。



解決策を見つけてみましょう...



構造



理解と開発を簡素化するために、一般的なクロッキング構造を希望どおりに示します。







これは、4つのブロックのみで構成されています







  • IPower-レジスタに書き込むためのデータを準備するユーザーインターフェイス
  • ハードウェア-コントローラーレジスタへの値の書き込み


ハードウェア依存:

  • ペリフェラル-プロジェクトで使用され、どのデバイスをオンまたはオフにする必要があるかをインターフェイスに指示するペリフェラル
  • アダプター-ハードウェアに書き込まれる値を転送し、どのレジスタに書き込む必要があるかを示します


IPowerインターフェース



すべての要件を考慮して、インターフェイスで必要なメソッドを定義します。



template<typename… Peripherals>
Enable();

template<typename EnableList, typename ExceptList>
EnableExcept();

template<typename EnableList, typename DisableList>
Keep();


有効-テンプレートパラメータで指定された周辺機器を有効にします。



EnableExcept - ExceptListで指定されたものを除き、EnableListパラメーターで指定された周辺機器を有効にします。



説明


0 0 0 0
0 1 0 0
1 0 1 0
1 1 0 0


, :

EnableExcept<spi, uart>();


SPI2EN IOPBEN. , DMA1EN, USART1EN IOPAEN .



, :



resultEnable = (enable ^ except) & enable




これらは、反対のことを行う補完的なDisableメソッドによって補完されます。



Keep -EnableListからペリフェラルを有効にし、DisableListからペリフェラルを無効にしますが、ペリフェラルが両方のリストに存在する場合は、その状態を保持します。



説明


0 0 0 0
0 1 0 1
1 0 1 0
1 1 0 0


, :

Keep<spi, uart>();


SPI2EN IOPBEN, USART1EN IOPAEN , DMA1EN .



, :



resultEnable = (enable ^ disable) & enable
resultDisable = (enable ^ disable) & disable




on / offメソッドは、fold式ですでにかなりうまく実装されていますが、残りはどうですか?



説明のように2種類の周辺機器を使用するように制限すれば、問題は発生しません。ただし、プロジェクトで多くの異なる周辺デバイスを使用すると、問題が発生します。テンプレートで複数のパラメータパックを明示的に使用することはできません。コンパイラーは、一方がどこで終わり、もう一方がどこから始まるかを判別できません。



template<typename… EnableList, typename… ExceptList>
EnableExcept(){…};
  //     EnableList   ExceptList
EnableExcept<spi2, pin3, uart1, pin1, i2c3>();


周辺機器用に別のラッパークラスを作成し、それをメソッドに渡すことができます。



template<typename… Peripherals>
PowerWrap{
  static constexpr auto valueAHBENR = (Peripherals::valueAHBENR | …);
  static constexpr auto valueAPB1ENR = (Peripherals:: valueAPB1ENR | …);
  static constexpr auto valueAPB2ENR = (Peripherals:: valueAPB2ENR | …);
};

using EnableList = PowerWrap<spi2, uart1>;
using ExceptList = PowerWrap<pin1, i2c1>;

EnableExcept<EnableList, ExceptList>();


ただし、この場合、インターフェイスはレジスタの数に厳密に関連付けられるため、コントローラのタイプごとに、同じタイプの多くの操作を使用し、抽象レイヤーに分割することなく、独自の個別のクラスを作成する必要があります。



使用されるすべてのペリフェラルとクロックレジスタはコンパイル段階でわかっているため、メタプログラミングを使用してタスクを解決できます。



メタプログラミング



メタプログラミングは通常のタイプではなくリストを使用した作業に基づいているため、典型的なパラメーターと非典型的なパラメーターで動作する2つのエンティティを定義します。



template<typename... Types>
struct Typelist{};

template<auto... Values>
struct Valuelist{};
using listT = Typelist<char, int> ;//     char  int
using listV = Valuelist<8,9,5,11> ;//   4  


これらのリストで役立つことを行う前に、より複雑なアクションを実行できるようにするいくつかの基本的な操作を実装する必要があります。



1.リストから最初のアイテムを取得する



前面
  //  
template<typename List>
struct front;

  //    
  //         
template<typename Head, typename... Tail>
struct front<Typelist<Head, Tail...>>{ 
    //   
  using type = Head; 
};

 //     
template<auto Head, auto... Tail>
struct front<Valuelist<Head, Tail...>> {
  //   
  static constexpr auto value = Head;
};

  //    
template<typename List>
using front_t = typename front<List>::type;

template<typename List>
static constexpr auto front_v = front<List>::value;

  // 
using listT = Typelist<char, bool, int>;
using type = front_t<listT>; // type = char

using listV = Valuelist<9,8,7>;
constexpr auto value = front_v<listV>; //value = 9




2.リストから最初のアイテムを削除する



pop_front
template<typename List>
struct pop_front;

  //    
  //         
template<typename Head, typename... Tail>
struct pop_front<Typelist<Head, Tail...>> {
  //  ,   
  using type = Typelist<Tail...>;
};

template<auto Head, auto... Tail>
struct pop_front<Valuelist<Head, Tail...>> {
  using type = Valuelist<Tail...>;
};

template<typename List>
using pop_front_t = typename pop_front<List>::type;

 // 
using listT = Typelist<char, bool, int>;
using typeT = pop_front_t<listT>; // type = Typelist<bool, int>

using listV = Valuelist<9,8,7>;
using typeV = pop_front_t<listV>; // type = Valuelist<8,7>




3.リストの先頭にアイテムを追加する

push_front
template<typename List, typename NewElement>
struct push_front;

template<typename... List, typename NewElement>
struct push_front<Typelist<List...>, NewElement> {
  using type = Typelist<NewElement, List...>;
};

template<typename List, typename NewElement>
using push_front_t = typename push_front<List, NewElement>::type;

  // 
using listT = Typelist<char, bool, int>;
using typeT = push_front_t<listT, long >; // type = Typelist<long, char, bool, int>





4.リストの最後に非典型的なパラメータを追加する



push_back_value
template<typename List, auto NewElement>
struct push_back;

template<auto... List, auto NewElement>
struct push_back<Valuelist<List...>, NewElement>{
  using type = Valuelist<List..., NewElement>;
};

template<typename List, auto NewElement>
using push_back_t = typename push_back<List, NewElement>::type;

  // 
using listV = Valuelist<9,8,7>;
using typeV = push_back_t<listV, 6>; // typeV = Valuelist<9,8,7,6>





5.リストの空をチェックする



is_empty
template<typename List>
struct is_empty{
    static constexpr auto value = false;
};

 //    ,   
template<>
struct is_empty<Typelist<>>{
    static constexpr auto value = true;
};

template<typename List>
static constexpr auto is_empty_v = is_empty<List>::value;

 // 
using listT = Typelist<char, bool, int>;
constexpr auto value = is_empty_v<listT>; // value = false




6.リスト内のアイテムの数を見つける



size_of_list
  //        ,
  //   count,       2  
template<typename List, std::size_t count = 0>
struct size_of_list : public size_of_list<pop_front_t<List>, count + 1>{};

  //      
template<std::size_t count>
struct size_of_list<Typelist<>, count>{
  static constexpr std::size_t value = count;
};

  //        
template<std::size_t count>
struct size_of_list<Valuelist<>, count>{
  static constexpr std::size_t value = count;
};

template<typename List>
static constexpr std::size_t size_of_list_v = size_of_list<List>::value;

  // 
using listT = Typelist<char, bool, int>;
constexpr auto value = size_of_list_v <listT>; // value = 3




今、すべての基本的なアクションが定義されていることを、あなたはビット演算のためのメタ関数を書くに移動することができますそしてXOR、インタフェース・メソッドのために必要とされます。



これらのビット変換は同じタイプであるため、コードの重複を避けるために、実装をできるだけ一般的にするように努めます。



リストに対して抽象演算を実行する関数



Lists_operation
template<template<typename first, typename second> class operation,
         typename Lists, bool isEnd = size_of_list_v<Lists> == 1>
class lists_operation{

  using first = front_t<Lists>; // (3)
  using second = front_t<pop_front_t<Lists>>; // (4)
  using next = pop_front_t<pop_front_t<Lists>>; // (5)
  using result = operation<first, second>; // (6)

public:

  using type = typename 
      lists_operation<operation, push_front_t<next, result>>::type; // (7)

};

template<template<typename first, typename second> class operation, typename List>
class lists_operation<operation, List, true>{ // (1)
public:
  using type = front_t<List>; // (2)
};


Lists – , , .

operation – , 2 Lists .

isEnd – , Lists.



(1) Lists 1 , (2).



– (3) (4) Lists, (6). (7) , (6), (5) Lists. (1).



次に、前のメタ関数の操作を実装します。これは、2つのリストからの非定型パラメーターに対して用語ごとの抽象アクションを実行します。



valuelists_operation
template<template <auto value1, auto value2> typename operation, 
         typename List1, typename List2, typename Result = Valuelist<>>
struct operation_2_termwise_valuelists{
  constexpr static auto newValue = 
      operation<front_v<List1>, front_v<List2>>::value; // (2)
  
  using nextList1 = pop_front_t<List1>;
  using nextList2 = pop_front_t<List2>;
    
  using result = push_back_value_t<Result, newValue>; // (3)
  using type = typename 
      operation_2_termwise_valuelists <operation, nextList1, nextList2, result>::type; // (4)
};

template<template <auto value1, auto value2> typename operation, typename Result>
struct operation_2_termwise_valuelists <operation, Valuelist<>, Valuelist<>, Result>{ // (1)
  using type = Result;
};


List1 List2 – , .

operation – , .

Result – , .



(1), , Result.



(2) Result (3). (4) , .



ビット操作機能:



bitwise_operation
template<auto value1, auto value2>
struct and_operation{ static constexpr auto value = value1 & value2;};

template<auto value1, auto value2>
struct or_operation{ static constexpr auto value = value1 | value2;};

template<auto value1, auto value2>
struct xor_operation{ static constexpr auto value = value1 ^ value2;};




使いやすくするためにエイリアスを作成する必要があります。

エイリアス
  //       2 
template<typename List1, typename List2>
using operation_and_termwise_t = typename 
          operation_2_termwise_valuelists<and_operation, List1, List2>::type;

template<typename List1, typename List2>
using operation_or_termwise_t = typename 
          operation_2_termwise_valuelists<or_operation, List1, List2>::type;

template<typename List1, typename List2>
using operation_xor_termwise_t = typename 
          operation_2_termwise_valuelists<xor_operation, List1, List2>::type;

  //        
template<typename... Lists>
using lists_termwise_and_t = typename 
          lists_operation<operation_and_termwise_t, Typelist<Lists...>>::type;

template<typename... Lists>
using lists_termwise_or_t= typename 
          lists_operation<operation_or_termwise_t, Typelist<Lists...>>::type;

template<typename... Lists>
using lists_termwise_xor_t = typename 
          lists_operation<operation_xor_termwise_t, Typelist<Lists...>>::type;


( ).



インターフェイスの実装に戻る



使用されるコントローラーと周辺機器の両方がコンパイル段階でわかっているため、インターフェースを実装するための論理的な選択は、CRTPイディオムを使用した静的多態性です。テンプレートパラメータとして、インターフェイスは特定のコントローラのアダプタクラスを取り、それがこのインターフェイスから継承します。



template<typename adapter>  
struct IPower{

  template<typename... Peripherals>
  static void Enable(){
     
      //    ,   ‘power’
      //      
    using tEnableList = lists_termwise_or_t<typename Peripherals::power...>;

      //  Valuelist<…>,   0, 
      //     
    using tDisableList = typename adapter::template fromValues<>::power;
   
      //   /  
  adapter:: template _Set<tEnableList , tDisableList>();
  }

  template<typename EnableList, typename ExceptList>
  static void EnableExcept(){

    using tXORedList = lists_termwise_xor_t <
        typename EnableList::power, typename ExceptList::power>;

    using tEnableList = lists_termwise_and_t <
        typename EnableList::power, tXORedList>;

    using tDisableList = typename adapter::template fromValues<>::power;

    adapter:: template _Set<tEnableList , tDisableList>();
  }

  template<typename EnableList, typename DisableList>
    static void Keep(){

    using tXORedList = lists_termwise_xor_t <
        typename EnableList::power, typename DisableList::power>;

    using tEnableList = lists_termwise_and_t <
        typename EnableList::power, tXORedList>;

    using tDisableList = lists_termwise_and_t <
        typename DisableList::power, tXORedList>;

    adapter:: template _Set<tEnableList , tDisableList>();
  }

  template<typename... PeripheralsList>
  struct fromPeripherals{
    using power = lists_termwise_or_t<typename PeripheralsList::power...>;
  };

};


また、インターフェイスには組み込みのfromPeripheralsクラス含まれており、ペリフェラルを1つのリストに結合して、メソッドで使用できます。



  using listPower = Power::fromPeripherals<spi, uart>;

  Power::Enable<listPower>();


無効化 メソッドも同様に実装されます。



コントローラアダプタ



アダプタークラスでは、クロッキングレジスタのアドレスを設定し、それらに書き込む順序を決定してから、指定されたレジスタのビットを設定またはクリアするクラスに直接制御を移す必要があります。



struct Power: public IPower<Power>{

  static constexpr uint32_t 
    _addressAHBENR  = 0x40021014,
    _addressAPB2ENR = 0x40021018,
    _addressAPB1ENR = 0x4002101C;
  
  using AddressesList = Valuelist<
      _addressAHBENR, _addressAPB1ENR, _addressAPB2ENR>;

  template<typename EnableList, typename DisableList>
  static void _Set(){
    //   ,    
    HPower:: template ModifyRegisters<EnableList, DisableList, AddressesList>();
  }
    
  template<uint32_t valueAHBENR = 0, uint32_t valueAPB1ENR = 0, uint32_t valueAPB2ENR = 0>
  struct fromValues{
    using power = Valuelist<valueAHBENR, valueAPB1ENR, valueAPB2ENR>;
  };

};


周辺機器



アダプターのfromValues構造を使用 して、周辺にpowerプロパティを付与します



template<int identifier>
struct SPI{
  //   identifier       
  using power = Power::fromValues<
      RCC_AHBENR_DMA1EN, //    ,
      RCC_APB1ENR_SPI2EN, //     
      RCC_APB2ENR_IOPBEN>::power;
};

template<int identifier>
struct UART{
  using power = Power::fromValues<
      RCC_AHBENR_DMA1EN,
      0U, 
      RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN>::power;
};


レジスタへの書き込み



このクラスは、アダプタから渡されたコントローラレジスタに値を書き込むことをタスクとする再帰テンプレートメソッドで構成されています。



このメソッドは、非典型的なValuelist <…>パラメーターの3つのリストをパラメーターとして受け入れます



  • SetListおよびResetList-レジスタで設定/リセットされるビット値のシーケンスのリスト
  • AddressesList-前のパラメーターからの値が書き込まれるレジスタアドレスのリスト


struct HPower{

  template<typename SetList, typename ResetList, typename AddressesList>
    static void ModifyRegisters(){
    if constexpr (!is_empty_v<SetList> && !is_empty_v<ResetList> && 
		  !is_empty_v<AddressesList>){

        //    
      constexpr auto valueSet = front_v<SetList>;
      constexpr auto valueReset = front_v<ResetList>;

      if constexpr(valueSet || valueReset){

        constexpr auto address = front_v<AddressesList>;
        using pRegister_t = volatile std::remove_const_t<decltype(address)>* const;
        auto& reg = *reinterpret_cast<pRegister_t>(address);

        // (!)  ,      
        reg = (reg &(~valueReset)) | valueSet;
      }

        //                  
      using tRestSet = pop_front_t<SetList>;
      using tRestReset = pop_front_t<ResetList>;
      using tRestAddress = pop_front_t<AddressesList>;
      
        //    ,     
      ModifyRegisters<tRestSet, tRestReset, tRestAddress>();
    }
  };

};


このクラスには、アセンブリリストに含まれる唯一のコード行が含まれています。



構造のすべてのブロックの準備ができたので、テストに移りましょう。



コードのテスト



最後の問題の状態を思い出してみましょう。



  • SPI2とUSART1の有効化
  • 「省電力モード」に入る前にSPI2をオフにする
  • 「省電力モード」終了後にSPI2を有効にする


//    
using spi = SPI<2>;
using uart = UART<1>;

//     ( )
using listPowerInit = Power::fromPeripherals<spi, uart>;
using listPowerDown = Power::fromPeripherals<spi>;
using listPowerWake = Power::fromPeripherals<uart>;

int main() {

   //  SPI2, UASRT1, DMA1, GPIOA, GPIOB
    Power::Enable<listPowerInit>();

    // Some code
    
    //   SPI2  GPIOB
    Power::DisableExcept<listPowerDown, listPowerWake>();

    //Sleep();

    //   SPI2  GPIOB
    Power::EnableExcept<listPowerDown, listPowerWake>();
}



コードサイズ:68バイト*、レジスタへの直接書き込みの場合と同様。



リスト
main:
  // AHBENR( DMA1)
  ldr     r3, .L3
  ldr     r2, [r3, #20]
  orr     r2, r2, #1
  str     r2, [r3, #20]
  // APB1ENR( SPI2
  ldr     r2, [r3, #28]
  orr     r2, r2, #16384
  str     r2, [r3, #28]
  // APB2ENR( GPIOA, GPIOB, USART1)
  ldr     r2, [r3, #24]
  orr     r2, r2, #16384
  orr     r2, r2, #12
  str     r2, [r3, #24]
  // APB1ENR( SPI2)
  ldr     r2, [r3, #28]
  bic     r2, r2, #16384
  str     r2, [r3, #28]
  // APB2ENR( GPIOB)
  ldr     r2, [r3, #24]
  bic     r2, r2, #8
  str     r2, [r3, #24]
  // APB1ENR( SPI2
  ldr     r2, [r3, #28]
  orr     r2, r2, #16384
  str     r2, [r3, #28]
  // APB2ENR( GPIOB)
  ldr     r2, [r3, #24]
  orr     r2, r2, #8
  str     r2, [r3, #24]




* GCC 9.2.1を使用すると、これGCC10.1.1より8バイト多くなりますリストからわかるように、いくつかの不要な命令が追加されます。たとえば、アドレス(ldrに読み取る前に、add命令(adds)がありますが、これらの命令はオフセット付きの読み取りに置き換えることができます。新しいバージョンは、これらの操作を最適化します。同時に、clangは同じリストを生成します。



結果



記事の冒頭で設定した目標は達成されました。実行速度と効率はレジスターへの直接書き込みのレベルにとどまり、ユーザーコードのエラーの可能性は最小限に抑えられます。



おそらくソースコードの量と開発の複雑さは冗長に見えるかもしれませんが、そのような多くの抽象化のおかげで、新しいコントローラーへの移行は最小限の労力で済みます:理解可能なアダプターコードの30行+周辺機器ユニットあたり5行。



完全なコード
type_traits_custom.hpp
#ifndef _TYPE_TRAITS_CUSTOM_HPP
#define _TYPE_TRAITS_CUSTOM_HPP

#include <type_traits>

/*!
  @file
  @brief Traits for metaprogramming
*/

/*!
  @brief Namespace for utils.
*/
namespace utils{

/*-----------------------------------Basic----------------------------------------*/

/*!
  @brief Basic list of types
  @tparam Types parameter pack
*/
template<typename... Types>
struct Typelist{};

/*!
  @brief Basic list of values
  @tparam Values parameter pack
*/
template<auto... Values>
struct Valuelist{};

/*------------------------------End of Basic--------------------------------------*/

/*----------------------------------Front-------------------------------------------
  Description:  Pop front type or value from list

  using listOfTypes = Typelist<int, short, bool, unsigned>;
  using listOfValues = Valuelist<1,2,3,4,5,6,1>;

  |-----------------|--------------------|----------|
  |      Trait      |    Parameters      |  Result  |
  |-----------------|--------------------|----------|
  |     front_t     |   <listOfTypes>    |    int   |
  |-----------------|--------------------|----------|
  |     front_v     |   <listOfValues>   |     1    |
  |-----------------|--------------------|----------| */

namespace{

template<typename List>
struct front;

template<typename Head, typename... Tail>
struct front<Typelist<Head, Tail...>>{ 
  using type = Head; 
};

template<auto Head, auto... Tail>
struct front<Valuelist<Head, Tail...>> {
  static constexpr auto value = Head;
};

}

template<typename List>
using front_t = typename front<List>::type;

template<typename List>
static constexpr auto front_v = front<List>::value;

/*----------------------------------End of Front----------------------------------*/

/*----------------------------------Pop_Front---------------------------------------
  Description:  Pop front type or value from list and return rest of the list

  using listOfTypes = Typelist<int, short, bool>;
  using listOfValues = Valuelist<1,2,3,4,5,6,1>;

  |-----------------|--------------------|------------------------|
  |      Trait      |    Parameters      |         Result         |
  |-----------------|--------------------|------------------------|
  |   pop_front_t   |    <listOfTypes>   | Typelist<short, bool>  |
  |-----------------|--------------------|------------------------|
  |   pop_front_t   |   <listOfValues>   | Valuelist<2,3,4,5,6,1> |
  |-----------------|--------------------|------------------------| */

namespace{

template<typename List>
struct pop_front;

template<typename Head, typename... Tail>
struct pop_front<Typelist<Head, Tail...>> {
  using type = Typelist<Tail...>;
};

template<auto Head, auto... Tail>
struct pop_front<Valuelist<Head, Tail...>> {
  using type = Valuelist<Tail...>;
};

}

template<typename List>
using pop_front_t = typename pop_front<List>::type;

/*------------------------------End of Pop_Front----------------------------------*/

/*----------------------------------Push_Front--------------------------------------
  Description:  Push new element to front of the list

  using listOfTypes = Typelist<short, bool>;

  |-----------------------|--------------------------|-------------------------------|
  |      Trait            |        Parameters        |             Result            |
  |-----------------------|--------------------------|-------------------------------|
  |      push_front_t     |   <listOfTypes, float>   | Typelist<float, short, bool>  |
  |-----------------------|--------------------------|-------------------------------| */

namespace{

template<typename List, typename NewElement>
struct push_front;

template<typename... List, typename NewElement>
struct push_front<Typelist<List...>, NewElement> {
  using type = Typelist<NewElement, List...>;
};

}

template<typename List, typename NewElement>
using push_front_t = typename push_front<List, NewElement>::type;

/*------------------------------End of Push_Front---------------------------------*/

/*----------------------------------Push_Back---------------------------------------
  Description:  Push new value to back of the list

  using listOfValues = Valuelist<1,2,3,4,5,6>;

  |-----------------------|--------------------------|-------------------------------|
  |      Trait            |        Parameters        |             Result            |
  |-----------------------|--------------------------|-------------------------------|
  |   push_back_value_t   |     <listOfValues, 0>    |    Valuelist<1,2,3,4,5,6,0>   |
  |-----------------------|--------------------------|-------------------------------| */

namespace{

template<typename List, auto NewElement>
struct push_back_value;

template<auto... List, auto NewElement>
struct push_back_value<Valuelist<List...>, NewElement>{
  using type = Valuelist<List..., NewElement>;
};

}

template<typename List, auto NewElement>
using push_back_value_t = typename push_back_value<List, NewElement>::type;

/*----------------------------------End of Push_Back------------------------------*/

/*-----------------------------------Is_Empty---------------------------------------
  Description:  Check parameters list for empty and return bool value

  using listOfTypes = Typelist<int, short, bool, unsigned>;
  using listOfValues = Valuelist<>;

  |-------------------------|--------------------|----------|
  |          Trait          |     Parameters     |  Result  |
  |-------------------------|--------------------|----------|
  |        is_empty_v       |    <listOfTypes>   |  false   |
  |-------------------------|--------------------|----------|
  |        is_empty_v       |   <listOfValues>   |   true   |
  |-------------------------|--------------------|----------| */

namespace{
/*!
  @brief Check the emptiness of the types in parameters.   \n 
    E.g.: is_empty<int, short, bool>::value;
*/ 
template<typename List>
struct is_empty{
    static constexpr auto value = false;
};

/*!
  @brief Check the emptiness of the types in parameter. Specializatio for empty parameters   \n 
    E.g.: is_empty<>::value;
*/ 
template<>
struct is_empty<Typelist<>>{
    static constexpr auto value = true;
};

template<>
struct is_empty<Valuelist<>>{
    static constexpr auto value = true;
};

}

/*!
  @brief Check the emptiness of the types-list in parameter.   \n 
    E.g.: using list = Typelist<int, short, bool>; is_empty_v<list>;
*/ 
template<typename List>
static constexpr auto is_empty_v = is_empty<List>::value;

/*--------------------------------End of Is_Empty---------------------------------*/

/*---------------------------------Size_Of_List-------------------------------------
  Description:  Return number of elements in list

  using listOfTypes = Typelist<int, float, double, bool>;

  |------------------|--------------------|----------|
  |       Trait      |     Parameters     |  Result  |
  |------------------|--------------------|----------|
  |  size_of_list_v  |     listOfTypes    |    4     |
  |------------------|--------------------|----------| */

namespace{

template<typename List, std::size_t count = 0U>
struct size_of_list : public size_of_list<pop_front_t<List>, count + 1>{};

template<std::size_t count>
struct size_of_list<Typelist<>, count>{
  static constexpr std::size_t value = count;
};

template<std::size_t count>
struct size_of_list<Valuelist<>, count>{
  static constexpr std::size_t value = count;
};

}

template<typename List>
static constexpr std::size_t size_of_list_v = size_of_list<List>::value;

/*-------------------------------End Size_Of_List---------------------------------*/

/*---------------------------------Lists Operation--------------------------------*/

  /*Description: Operations with lists of values

  using list1 = Valuelist<1, 4, 8, 16>;
  using list2 = Valuelist<1, 5, 96, 17>;

  |------------------------------|-------------------|---------------------------|
  |               Trait          |    Parameters     |           Result          |
  |------------------------------|-------------------|---------------------------|
  |     lists_termwise_and_t     |  <list1, list2>   |  Valuelist<1, 4, 0, 16>   |
  |------------------------------|-------------------|---------------------------|
  |     lists_termwise_or_t      |  <list1, list2>   |  Valuelist<1, 5, 104, 17> |
  |---------------------------- -|-------------------|---------------------------|
  |     lists_termwise_xor_t     |  <list1, list2>   |  Valuelist<0, 1, 104, 1>  |
  |------------------------------|-------------------|---------------------------| */

namespace{

template<template <auto value1, auto value2> typename operation, 
         typename List1, typename List2, typename Result = Valuelist<>>
struct operation_2_termwise_valuelists{
  constexpr static auto newValue = operation<front_v<List1>, front_v<List2>>::value;
  using nextList1 = pop_front_t<List1>;
  using nextList2 = pop_front_t<List2>;
    
  using result = push_back_value_t<Result, newValue>;
  using type = typename 
      operation_2_termwise_valuelists<operation, nextList1, nextList2, result>::type;
};

template<template <auto value1, auto value2> typename operation, typename Result>
struct operation_2_termwise_valuelists<operation, Valuelist<>, Valuelist<>, Result>{
  using type = Result;
};

template<template <auto value1, auto value2> typename operation, 
         typename List2, typename Result>
struct operation_2_termwise_valuelists<operation, Valuelist<>, List2, Result>{
  using type = typename 
      operation_2_termwise_valuelists<operation, Valuelist<0>, List2, Result>::type;
};

template<template <auto value1, auto value2> typename operation, 
         typename List1, typename Result>
struct operation_2_termwise_valuelists<operation, List1, Valuelist<>, Result>{
  using type = typename 
      operation_2_termwise_valuelists<operation, List1, Valuelist<0>, Result>::type;
};

template<template<typename first, typename second> class operation,
         typename Lists, bool isEnd = size_of_list_v<Lists> == 1>
class lists_operation{

  using first = front_t<Lists>;
  using second = front_t<pop_front_t<Lists>>;
  using next = pop_front_t<pop_front_t<Lists>>;
  using result = operation<first, second>;

public:

  using type = typename lists_operation<operation, push_front_t<next, result>>::type;

};

template<template<typename first, typename second> class operation,
         typename Lists>
class lists_operation<operation, Lists, true>{
public:
  using type = front_t<Lists>;
};

template<auto value1, auto value2>
struct and_operation{ static constexpr auto value = value1 & value2;};

template<auto value1, auto value2>
struct or_operation{ static constexpr auto value = value1 | value2;};

template<auto value1, auto value2>
struct xor_operation{ static constexpr auto value = value1 ^ value2;};

template<typename List1, typename List2>
using operation_and_termwise_t = typename 
    operation_2_termwise_valuelists<and_operation, List1, List2>::type;

template<typename List1, typename List2>
using operation_or_termwise_t = typename 
    operation_2_termwise_valuelists<or_operation, List1, List2>::type;

template<typename List1, typename List2>
using operation_xor_termwise_t = typename 
    operation_2_termwise_valuelists<xor_operation, List1, List2>::type;

}

template<typename... Lists>
using lists_termwise_and_t = 
    typename lists_operation<operation_and_termwise_t, Typelist<Lists...>>::type;

template<typename... Lists>
using lists_termwise_or_t = typename 
    lists_operation<operation_or_termwise_t, Typelist<Lists...>>::type;

template<typename... Lists>
using lists_termwise_xor_t = typename 
    lists_operation<operation_xor_termwise_t, Typelist<Lists...>>::type;

/*--------------------------------End of Lists Operation----------------------------*/

} // !namespace utils

#endif //!_TYPE_TRAITS_CUSTOM_HPP







IPower.hpp
#ifndef _IPOWER_HPP
#define _IPOWER_HPP

#include "type_traits_custom.hpp"

#define __FORCE_INLINE __attribute__((always_inline)) inline

/*!
  @brief Controller's peripherals interfaces
*/
namespace controller::interfaces{

/*!
  @brief Interface for Power(Clock control). Static class. CRT pattern
  @tparam <adapter> class of specific controller
*/
template<typename adapter>  
class IPower{

  IPower() = delete;

public:

  /*!
    @brief Enables peripherals Power(Clock)
    @tparam <Peripherals> list of peripherals with trait 'power'
  */
  template<typename... Peripherals>
  __FORCE_INLINE static void Enable(){
    using tEnableList = utils::lists_termwise_or_t<typename Peripherals::power...>;
    using tDisableList = typename adapter::template fromValues<>::power;
   adapter:: template _Set<tEnableList, tDisableList>();
  }

  /*!
    @brief Enables Power(Clock) except listed peripherals in 'ExceptList'. 
      If Enable = Exception = 1, then Enable = 0, otherwise depends on Enable.
    @tparam <EnableList> list to enable, with trait 'power'
    @tparam <ExceptList> list of exception, with trait 'power'
  */
  template<typename EnableList, typename ExceptList>
  __FORCE_INLINE static void EnableExcept(){
    using tXORedList = utils::lists_termwise_xor_t<typename EnableList::power, typename ExceptList::power>;
    using tEnableList = utils::lists_termwise_and_t<typename EnableList::power, tXORedList>;
    using tDisableList = typename adapter::template fromValues<>::power;
    adapter:: template _Set<tEnableList, tDisableList>();
  }

  /*!
    @brief Disables peripherals Power(Clock)
    @tparam <Peripherals> list of peripherals with trait 'power'
  */
  template<typename... Peripherals>
  __FORCE_INLINE static void Disable(){
    using tDisableList = utils::lists_termwise_or_t<typename Peripherals::power...>;
    using tEnableList = typename adapter::template fromValues<>::power;
    adapter:: template _Set<tEnableList, tDisableList>();
  }

  /*!
    @brief Disables Power(Clock) except listed peripherals in 'ExceptList'. 
      If Disable = Exception = 1, then Disable = 0, otherwise depends on Disable.
    @tparam <DisableList> list to disable, with trait 'power'
    @tparam <ExceptList> list of exception, with trait 'power'
  */
  template<typename DisableList, typename ExceptList>
  __FORCE_INLINE static void DisableExcept(){
    using tXORedList = utils::lists_termwise_xor_t<typename DisableList::power, typename ExceptList::power>;
    using tDisableList = utils::lists_termwise_and_t<typename DisableList::power, tXORedList>;
    using tEnableList = typename adapter::template fromValues<>::power;
    adapter:: template _Set<tEnableList, tDisableList>();
  }

  /*!
    @brief Disable and Enables Power(Clock) depends on values. 
      If Enable = Disable = 1, then Enable = Disable = 0, otherwise depends on values
    @tparam <EnableList> list to enable, with trait 'power'
    @tparam <DisableList> list to disable, with trait 'power'
  */
  template<typename EnableList, typename DisableList>
  __FORCE_INLINE static void Keep(){
    using tXORedList = utils::lists_termwise_xor_t<typename EnableList::power, typename DisableList::power>;
    using tEnableList = utils::lists_termwise_and_t<typename EnableList::power, tXORedList>;
    using tDisableList = utils::lists_termwise_and_t<typename DisableList::power, tXORedList>;
    adapter:: template _Set<tEnableList, tDisableList>();
  }

  /*!
    @brief Creates custom 'power' list from peripherals. Peripheral driver should implement 'power' trait.
      E.g.: using power = Power::makeFromValues<1, 512, 8>::power; 
    @tparam <PeripheralsList> list of peripherals with trait 'power'
  */
 template<typename... PeripheralsList>
  class fromPeripherals{
    fromPeripherals() = delete;
    using power = utils::lists_termwise_or_t<typename PeripheralsList::power...>;
    friend class IPower<adapter>;
  };

};

} // !namespace controller::interfaces

#undef   __FORCE_INLINE

#endif // !_IPOWER_HPP







HPower.hpp
#ifndef _HPOWER_HPP
#define _HPOWER_HPP

#include "type_traits_custom.hpp"

#define __FORCE_INLINE __attribute__((always_inline)) inline

/*!
  @brief Hardware operations
*/
namespace controller::hardware{

/*!
  @brief Implements hardware operations with Power(Clock) registers
*/
class HPower{

  HPower() = delete;

protected:

/*!
  @brief Set or Reset bits in the registers
  @tparam <SetList> list of values to set 
  @tparam <ResetList> list of values to reset
  @tparam <AddressesList> list of registers addresses to operate
*/
  template<typename SetList, typename ResetList, typename AddressesList>
  __FORCE_INLINE static void ModifyRegisters(){
    using namespace utils;

    if constexpr (!is_empty_v<SetList> && !is_empty_v<ResetList> && 
		  !is_empty_v<AddressesList>){

      constexpr auto valueSet = front_v<SetList>;
      constexpr auto valueReset = front_v<ResetList>;

      if constexpr(valueSet || valueReset){
        constexpr auto address = front_v<AddressesList>;
          
        using pRegister_t = volatile std::remove_const_t<decltype(address)>* const;
        auto& reg = *reinterpret_cast<pRegister_t>(address);

        reg = (reg &(~valueReset)) | valueSet;
      }
        
      using tRestSet = pop_front_t<SetList>;
      using tRestReset = pop_front_t<ResetList>;
      using tRestAddress = pop_front_t<AddressesList>;
      
      ModifyRegisters<tRestSet, tRestReset, tRestAddress>();
    }
  };

};

} // !namespace controller::hardware

#undef __FORCE_INLINE

#endif // !_HPOWER_HPP







stm32f1_Power.hpp
#ifndef _STM32F1_POWER_HPP
#define _STM32F1_POWER_HPP

#include <cstdint>
#include "IPower.hpp"
#include "HPower.hpp"
#include "type_traits_custom.hpp"

#define __FORCE_INLINE __attribute__((always_inline)) inline

/*!
  @brief Controller's peripherals
*/
namespace controller{

/*!
  @brief Power managment for controller
*/
class Power: public interfaces::IPower<Power>, public hardware::HPower{

  Power() = delete;

public:

  /*!
    @brief Creates custom 'power' list from values. Peripheral driver should implement 'power' trait.
      E.g.: using power = Power::fromValues<1, 512, 8>::power; 
    @tparam <valueAHB=0> value for AHBENR register
    @tparam <valueAPB1=0> value for APB1ENR register
    @tparam <valueAPB2=0> value for APB1ENR register
  */
  template<uint32_t valueAHBENR = 0, uint32_t valueAPB1ENR = 0, uint32_t valueAPB2ENR = 0>
  struct fromValues{
    fromValues() = delete;
    using power = utils::Valuelist<valueAHBENR, valueAPB1ENR, valueAPB2ENR>;
  };

private: 

  static constexpr uint32_t 
    _addressAHBENR  = 0x40021014,
    _addressAPB2ENR = 0x40021018,
    _addressAPB1ENR = 0x4002101C;
  
  using AddressesList = utils::Valuelist<_addressAHBENR, _addressAPB1ENR, _addressAPB2ENR>;

  template<typename EnableList, typename DisableList>
  __FORCE_INLINE static void _Set(){
    HPower:: template ModifyRegisters<EnableList, DisableList, AddressesList>();
  }

  friend class IPower<Power>;

};

} // !namespace controller

#undef __FORCE_INLINE

#endif // !_STM32F1_POWER_HPP







stm32f1_SPI.hpp
#ifndef _STM32F1_SPI_HPP
#define _STM32F1_SPI_HPP

#include "stm32f1_Power.hpp"

namespace controller{

template<auto baseAddress>
class SPI{

  static const uint32_t RCC_AHBENR_DMA1EN = 1;
  static const uint32_t RCC_APB2ENR_IOPBEN = 8;
  static const uint32_t RCC_APB1ENR_SPI2EN = 0x4000;

  /*!
    @brief Trait for using in Power class. Consists of Valueslist with
      values for AHBENR, APB1ENR, APB2ENR registers 
  */
  using power = Power::fromValues<
           RCC_AHBENR_DMA1EN,
           RCC_APB1ENR_SPI2EN, 
           RCC_APB2ENR_IOPBEN>::power;

  template<typename>
  friend class interfaces::IPower;
};

}

#endif // !_STM32F1_SPI_HPP







stm32f1_UART.hpp
#ifndef _STM32F1_UART_HPP
#define _STM32F1_UART_HPP

#include "stm32f1_Power.hpp"

namespace controller{

template<auto baseAddress>
class UART{

  static const uint32_t RCC_AHBENR_DMA1EN = 1;
  static const uint32_t RCC_APB2ENR_IOPAEN = 4;
  static const uint32_t RCC_APB2ENR_USART1EN = 0x4000;

  /*!
    @brief Trait for using in Power class. Consists of Valueslist with
      values for AHBENR, APB1ENR, APB2ENR registers 
  */
  using power = Power::fromValues<
           RCC_AHBENR_DMA1EN,
           0U, 
           RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN>::power;

  template<typename>
  friend class interfaces::IPower;
};

}

#endif // !_STM32F1_UART_HPP







main.cpp
#include "stm32f1_Power.hpp"
#include "stm32f1_UART.hpp"
#include "stm32f1_SPI.hpp"

using namespace controller;

using spi = SPI<2>;
using uart = UART<1>;

using listPowerInit = Power::fromPeripherals<spi, uart>;
using listPowerDown = Power::fromPeripherals<spi>;
using listPowerWake = Power::fromPeripherals<uart>;

int main(){

  Power::Enable<listPowerInit>();

  //Some code

  Power::DisableExcept<listPowerDown, listPowerWake>();

  //Sleep();

  Power::EnableExcept<listPowerDown, listPowerWake>();

  while(1);
  return 1;
};







Github



All Articles