Allumez les périphériques du contrôleur en 1 cycle d'horloge ou 500 lignes de code magiques





Combien de fois, lors du développement du firmware d'un microcontrôleur, lors du débogage, lorsque les octets ne fonctionnent pas sur l'UART, vous vous exclamez: «Ahh, exactement! N'a pas activé le pointage! " Ou, lorsque vous avez changé la jambe LED, avez-vous oublié de «mettre sous tension» le nouveau port? Assez souvent, je pense. Moi, au moins - bien sûr.



À première vue, il peut sembler que le contrôle de la synchronisation de la périphérie est trivial: écrit 1 - activé, 0 - désactivé.



Mais «simple» n'est pas toujours efficace ...



Formulation du problème



Avant d'écrire le code, il est nécessaire de déterminer les critères par lesquels il peut être évalué. Dans le cas du système d'horloge périphérique du contrôleur, la liste peut ressembler à ceci:



  • Dans les systèmes embarqués, l'un des critères les plus importants est le code résultant le plus petit possible, exécuté dans les plus brefs délais.
  • . - code review , /
  • , ,
  • ( )


Après avoir clarifié les critères d'évaluation, nous définirons une tâche spécifique, tout en définissant les conditions et «l'environnement» d'implémentation:



Compilateur: GCC 10.1.1 + Make

Language: C ++ 17

Environnement: Visual Studio Code

Controller: stm32f103c8t6 (cortex-m3)

Tâche: activer l'horloge SPI2, USART1 (les deux interfaces utilisant DMA)



Le choix de ce contrôleur est dû, bien sûr, à sa prédominance, notamment grâce à l'un des métiers traditionnels chinois - la production de cartes Blue Pill.







Du point de vue de l'idéologie, peu importe le contrôleur choisi: stmf1, stmf4 ou lpc, car travailler avec le système d'horloge périphérique se réduit uniquement à écrire sur un certain bit soit 0 pour éteindre, soit 1 pour allumer.



Dans stm32f103c8t6, ​​il existe 3 registres responsables de l'activation de l'horloge périphérique: AHBENR, APB1ENR, APB2ENR.



Les interfaces matérielles pour le transfert de données SPI2 et USART1 n'ont pas été choisies par hasard, car pour leur plein fonctionnement, il est nécessaire d'activer les bits d'horloge situés dans tous les registres listés - les bits des interfaces elles-mêmes, DMA1, ainsi que les bits des ports d'entrée-sortie (GPIOB pour SPI2 et GPIOA pour USART1).









Il convient de noter que pour des performances optimales avec la synchronisation, il est nécessaire de prendre en compte - AHBENR contient une ressource partagée utilisée pour le fonctionnement de SPI2 et USART1. Autrement dit, la désactivation du DMA entraînera immédiatement l'inopérabilité des deux interfaces, en même temps, l'efficacité de la réenclenchement ne sera même pas nulle, mais négative, car cette opération occupera la mémoire du programme et entraînera une consommation d'horloge supplémentaire pour la lecture-modification-écriture d'un registre volatil.



Après avoir traité les objectifs, les conditions et les caractéristiques du problème, passons à la recherche de solutions.



Approches de base



Cette section contient des moyens typiques d'activer la synchronisation des périphériques que j'ai rencontrés et, bien sûr, vous les avez également vus et / ou utilisés. Des plus simples, implémentés en C, à l'expression de pliage à partir de C ++ 17. Leurs avantages et inconvénients inhérents sont pris en compte.



Si vous souhaitez accéder directement à la métaprogrammation, vous pouvez ignorer cette section et passer à la suivante .



Écriture directe dans les registres



La méthode classique, "disponible immédiatement" pour C et C ++. Le fournisseur fournit le plus souvent des fichiers d'en-tête pour le contrôleur, dans lesquels tous les registres et leurs bits sont mis par défaut, ce qui permet de commencer immédiatement à travailler avec des périphériques:



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


Référencement
    // 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]




Taille du code: 36 octets. Voir les



pros:



  • Taille minimale du code et vitesse d'exécution
  • Le moyen le plus simple et le plus évident


Moins:



  • Il faut se souvenir des noms des registres et des noms des bits, ou se référer constamment au manuel
  • Il est facile de se tromper dans votre code. Le lecteur a dû remarquer qu'au lieu de SPI2, USART1 a été réactivé.
  • Pour que certaines unités périphériques fonctionnent, vous devez également activer d'autres périphériques, tels que GPIO et DMA pour les interfaces
  • Absence totale de portabilité. Lors du choix d'un contrôleur différent, ce code perd son sens


Avec toutes ses lacunes, cette méthode reste très populaire, du moins quand on a besoin de "sentir" le nouveau contrôleur en écrivant le prochain "Hello, World!" en faisant clignoter la LED.



Fonctions d'initialisation



Essayons d'abstraire et de cacher le travail avec les registres à l'utilisateur. Et une fonction C ordinaire nous aidera avec ceci:



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();
}


Taille du code: 72 octets. Regardez



Référencement
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()




Avantages:



  • Vous n'êtes pas obligé de consulter le manuel pour chaque occasion.
  • Les erreurs sont localisées au stade de l'écriture d'un pilote périphérique
  • Le code personnalisé est facile à lire


Moins:



  • Le nombre d'instructions requises a augmenté par multiples du nombre de périphériques impliqués
  • Beaucoup de duplication de code - pour chaque numéro UART et SPI, il sera pratiquement identique


Bien que nous nous soyons débarrassés de l'écriture directe dans les registres en code utilisateur, à quel prix? La taille de la mémoire et le temps d'exécution requis pour la mise sous tension ont doublé et continueront de croître avec davantage de périphériques impliqués.



Fonction d'activation de l'horloge



Emballons la modification des horloges dans une fonction séparée, en supposant que cela réduira la quantité de mémoire requise. Dans le même temps, nous allons introduire un paramètre d'identifiant pour les périphériques - pour réduire le code du pilote:



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);
}


Taille du code: 92 octets. Regardez



Référencement
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)




Avantages:

  • Il était possible de raccourcir le code de description des pilotes de microcontrôleur
  • Le nombre d'instructions résultant a diminué *


Moins:



  • Augmentation du temps d'exécution


* Oui, dans ce cas, la taille du code exécutable a augmenté par rapport à la version précédente, mais cela est dû à l'apparition d'opérateurs conditionnels, dont l'influence peut être neutralisée si au moins 2 copies de chaque type de périphérie sont utilisées.



Car la fonction include prend des paramètres, puis les opérations de pile sont apparues dans l'assembleur, ce qui a également un impact négatif sur les performances.



À ce stade, je pense que nos pouvoirs valent tous la peine de passer aux avantages , car les principales approches utilisées en C pur sont prises en compte, à l'exception des macros. Mais cette méthode est également loin d'être optimale et est associée à la probabilité potentielle de se tromper dans le code utilisateur.



Propriétés de valeur et modèles



En commençant à considérer l'approche positive, nous allons immédiatement sauter l'option d'inclure le pointage dans le constructeur de classe, car cette méthode n'est en fait pas différente des fonctions d'initialisation de style C.



Étant donné qu'au moment de la compilation, nous connaissons toutes les valeurs qui doivent être écrites dans les registres, nous nous débarrasserons des opérations de pile. Pour ce faire, nous allons créer une classe séparée avec une méthode de modèle, et doter les classes périphériques de propriétés (trait de valeur) qui stockeront les valeurs pour les registres correspondants.



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
                >();
}


Taille du code: 36 octets. Regardez



Référencement
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]




Avantages:



  • La taille et le temps d'exécution se sont avérés être les mêmes que dans la version de référence avec écriture directe dans les registres
  • Il est assez facile de faire évoluer le projet - il suffit d'ajouter la valeur de propriété correspondant à l' eau de la périphérie


Moins:



  • Il est possible de se tromper en mettant la propriété value dans le mauvais paramètre
  • Comme dans le cas de l'écriture directe sur les registres - la portabilité en souffre
  • Surcharge de construction


Nous avons pu atteindre plusieurs objectifs fixés, mais est-il pratique de l'utiliser? Je ne pense pas, car pour ajouter un autre bloc de périphériques, il est nécessaire de contrôler la disposition correcte des propriétés de classe dans les paramètres du modèle de méthode.



Idéal ... presque



Pour réduire la quantité de code personnalisé et les opportunités d'erreurs, nous utiliserons le pack de paramètres, qui supprimera l'accès aux propriétés des classes de périphériques dans le code personnalisé. Cela ne changera que la méthode d'activation du pointage:



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>();
}


Taille du code: 36 octets. Regardez



Référencement
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]




Par rapport à la version précédente, la simplicité du code utilisateur a considérablement augmenté, la probabilité d'erreurs est devenue minime et la consommation de mémoire est restée au même niveau.



Et, semble-t-il, vous pouvez vous arrêter là-dessus, mais ...





Extension des fonctionnalités



Passons à l'un de nos objectifs:

Outre les capacités de base d'activation et de désactivation de la synchronisation des périphériques, des fonctionnalités avancées sont requises


Supposons que la tâche consiste à rendre l'appareil à faible consommation d'énergie, et pour cela, bien sûr, il est nécessaire d'éteindre tous les périphériques que le contrôleur n'utilise pas pour quitter le mode d'économie d'énergie.



Dans le contexte des conditions exprimées au début de l'article, nous supposerons que le générateur de l'événement de réveil sera USART1, et SPI2 et le port GPIOB correspondant doivent être désactivés. Dans ce cas, la ressource partagée DMA1 doit rester activée.



En utilisant une option de la section précédente, il ne sera pas possible de résoudre ce problème à la fois efficacement et de manière optimale, et, en même temps, sans utiliser le contrôle manuel des blocs impliqués.

Par exemple, prenons le dernier moyen:



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>();
}


Taille du code: 100 octets. Regardez



Référencement
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]





Dans le même temps, le code de référence dans les registres prenait 68 octets. Vue



Évidemment, pour de telles tâches, la pierre d'achoppement sera les ressources partagées telles que DMA. De plus, dans ce cas particulier, il y aura un moment où les deux interfaces deviendront inopérantes et, en fait, une situation d'urgence se produira.



Essayons de trouver une solution ...



Structure



Pour simplifier la compréhension et le développement, nous allons décrire la structure de synchronisation générale telle que nous voulons qu'elle soit:







Elle se compose de seulement quatre blocs:



Indépendant:



  • IPower - une interface utilisateur qui prépare les données pour l'écriture dans les registres
  • Matériel - écriture de valeurs dans les registres du contrôleur


Dépend du matériel:

  • Périphériques - périphériques qui sont utilisés dans le projet et indiquent à l'interface quels périphériques doivent être activés ou désactivés
  • Adaptateur - transfère les valeurs à écrire vers le matériel, indiquant dans quels registres elles doivent être écrites


Interface IPower



En tenant compte de toutes les exigences, nous définirons les méthodes requises dans l'interface:



template<typename… Peripherals>
Enable();

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

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


Activer - active les périphériques spécifiés dans le paramètre de modèle.



EnableExcept - active les périphériques spécifiés dans le paramètre EnableList, à l'exception de ceux spécifiés dans ExceptList.



Explication


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




Celles-ci sont complétées par des méthodes Disable complémentaires qui font le contraire.



Keep - activer les périphériques à partir de EnableList, désactiver les périphériques de DisableList, tandis que si les périphériques sont présents dans les deux listes, il conserve son état.



Explication


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




Les méthodes on / off ont déjà été assez bien implémentées avec l'expression fold, mais qu'en est-il du reste?



Si nous nous limitons à utiliser 2 types de périphérie, comme cela est fait dans l'explication, aucune difficulté ne se posera. Cependant, lorsqu'un projet utilise de nombreux périphériques différents, un problème survient - vous ne pouvez pas utiliser explicitement plus d'un pack de paramètres dans un modèle, car le compilateur ne pourra pas déterminer où se termine et où commence le second:



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


Il serait possible de créer une classe wrapper séparée pour la périphérie et de la transmettre à la méthode:



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>();


Mais dans ce cas, l'interface deviendra rigidement liée au nombre de registres, par conséquent, pour chaque type de contrôleur, il deviendra nécessaire d'écrire sa propre classe distincte, avec de nombreuses opérations du même type et sans possibilité de diviser en couches abstraites.



Puisque tous les périphériques et registres d'horloge utilisés sont connus au stade de la compilation, la tâche peut être résolue à l'aide de la métaprogrammation.



Métaprogrammation



Étant donné que la métaprogrammation est basée sur le travail non pas avec des types ordinaires, mais avec leurs listes, nous définirons deux entités qui fonctionneront avec des paramètres typiques et non typiques:



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  


Avant de faire quoi que ce soit d'utile avec ces listes, nous devons implémenter quelques opérations de base qui permettront d'effectuer des actions plus complexes.



1. Récupération du premier élément de la liste



de face
  //  
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. Suppression du premier élément de la liste



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. Ajout d'un élément au début de la liste

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. Ajout d'un paramètre non standard à la fin de la liste



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. Vérification du vide dans la liste



est vide
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. Recherche du nombre d'éléments dans la liste



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




Maintenant que toutes les actions de base sont définies, vous pouvez passer à l'écriture de métafonctions pour les opérations au niveau du bit: ou , et , xor , qui sont nécessaires pour les méthodes d'interface.



Puisque ces transformations de bits sont du même type, nous essaierons de rendre l'implémentation aussi générale que possible afin d'éviter la duplication de code.



Une fonction qui effectue une opération abstraite sur une liste



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).



Ensuite, nous implémenterons l'opération pour la métafonction précédente, qui effectuera des actions abstraites terme par terme sur des paramètres atypiques à partir de deux listes:



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) , .



Fonctions d'opération de bit:



opération_bits
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;};




Il reste à créer des alias pour une utilisation plus facile:

alias
  //       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;


( ).



Revenir à l'implémentation de l'interface



Comme le contrôleur et les périphériques utilisés sont connus au moment de la compilation, le choix logique pour implémenter l'interface est le polymorphisme statique avec l'idiome CRTP . En tant que paramètre de modèle, une interface prend une classe d'adaptateur d'un contrôleur spécifique, qui, à son tour, hérite de cette interface.



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...>;
  };

};


En outre, l'interface contient une classe fromPeripherals intégrée qui vous permet de combiner des périphériques en une seule liste, qui peut ensuite être utilisée dans les méthodes:



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

  Power::Enable<listPower>();


Les méthodes de désactivation sont implémentées de la même manière.



Adaptateur de contrôleur



Dans la classe d'adaptateur, vous devez définir les adresses des registres d'horloge et déterminer la séquence dans laquelle y écrire, puis transférer le contrôle directement à la classe, qui définira ou effacera les bits des registres indiqués.



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>;
  };

};


Périphérie



Nous dotons la périphérie d'une propriété power en utilisant la structure fromValues ​​de l' adaptateur:



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;
};


Écrire dans les registres



La classe se compose d'une méthode de modèle récursive dont la tâche consiste à écrire des valeurs dans les registres de contrôleur transmis par l'adaptateur.



La méthode accepte 3 listes de paramètres Valuelist <…> non typiques comme paramètres :



  • SetList et ResetList - listes de séquences de valeurs de bits à définir / réinitialiser dans un registre
  • AddressesList - une liste d'adresses de registre dans lesquelles les valeurs des paramètres précédents seront écrites


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>();
    }
  };

};


La classe contient la seule ligne de code qui sera incluse dans la liste d'assembly.



Maintenant que tous les blocs de la structure sont prêts, passons aux tests.



Tester le code



Rappelons les conditions du dernier problème:



  • Activation de SPI2 et USART1
  • Éteindre SPI2 avant d'entrer en "mode d'économie d'énergie"
  • Activation de SPI2 après avoir quitté le "mode d'économie d'énergie"


//    
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>();
}



Taille du code: 68 octets *, comme dans le cas de l'écriture directe dans les registres.



Référencement
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]




* En utilisant GCC 9.2.1, c'est 8 octets de plus que GCC 10.1.1 . Comme vous pouvez le voir dans la liste , plusieurs instructions inutiles sont ajoutées, par exemple, avant de lire à l'adresse ( ldr ), il y a une instruction d'ajout ( ajoute ), bien que ces instructions puissent être remplacées par une lecture avec un décalage. La nouvelle version optimise ces opérations. Dans le même temps, clang génère les mêmes listes.



Résultat



Les objectifs fixés au début de l'article ont été atteints - la vitesse d'exécution et l'efficacité sont restées au niveau de l'écriture directe dans le registre, la probabilité d'une erreur dans le code utilisateur est minimisée.



Peut-être que le volume du code source et la complexité du développement sembleront redondants, cependant, grâce à un tel nombre d'abstractions, le passage à un nouveau contrôleur demandera un minimum d'effort: 30 lignes de code adaptateur compréhensible + 5 lignes par unité périphérique.



Code complet
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