在1个时钟周期内打开控制器外设或使用500行代码进行魔术处理





多久为微控制器开发固件时,在调试过程中,未在UART上运行字节时,您会大声喊道:“啊!没有启用时钟!” 或者,当您更换LED支脚时,您是否忘了为新端口“通电”?我认为通常是这样。我,至少-可以肯定。



乍一看,控制外围设备的时序似乎很简单:写入1-启用,0-禁用。



但是“简单”并不总是有效的...



问题的提法



在编写代码之前,有必要确定评估代码的标准。对于控制器外设时钟系统,列表可能如下所示:



  • 在嵌入式系统中,最重要的标准之一是在尽可能短的时间内执行最小的结果代码。
  • . - code review , /
  • , ,
  • ( )


弄清评估标准后,我们​​将在定义条件和实现的“环境”的过程中设置一个特定的任务:



编译器:GCC 10.1.1 + Make

语言:C ++ 17

环境:Visual Studio代码

控制器:stm32f103c8t6(cortex-m3)

任务:启用时钟SPI2,USART1(均使用DMA的两个接口)



控制器的选择当然是由于它的普遍性,特别是由于中国民间工艺之一-蓝色药丸板的生产。







从意识形态的角度来看,选择哪个控制器无关紧要: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个字节。查看



优点:



  • 最小代码大小和执行速度
  • 最简单,最明显的方法


缺点:



  • 必须记住寄存器的名称和位的名称,或者经常参考手册
  • 在代码中容易出错。读者一定已经注意到,USART1被重新启用,而不是SPI2。
  • 为了使某些外围设备正常工作,还需要启用其他外围设备,例如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编号,它几乎是相同的


尽管我们摆脱了直接在用户代码中写入寄存器的方式,但是付出了什么代价?打开所需的内存大小和执行时间已加倍,并且将继续增加,其中涉及更多外围设备。



时钟使能功能



让我们将时钟的修改包装在一个单独的函数中,假设这将减少所需的内存量。同时,我们将为外围设备引入一个标识符参数-减少驱动程序代码:



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]




与以前的版本相比,用户代码的简便性得到了极大的提高,出错的可能性变得最小,并且内存消耗保持在同一水平。



而且,看来,您可以在此停下来,但是...





扩展功能



让我们转向目标之一:

除了启用和禁用外设时钟的基本功能外,还需要高级功能


假设任务是使设备低功耗,为此,当然,需要关闭控制器不使用的所有外围设备以退出省电模式。



在本文开头所述的情况下,我们将假定唤醒事件的生成器为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。另外,在这种特殊情况下,有时两个接口都无法使用,并且实际上会发生紧急情况。



让我们尝试找到解决方案...



结构体



为了简化理解和发展,我们将描述我们想要的通用时序结构:







它仅包含四个模块:



独立:



  • IPower-用户界面,准备数据以写入寄存器
  • 硬件-将值写入控制器寄存器


取决于硬件:

  • 外围设备-项目中使用的外围设备,告诉接口应该打开或关闭哪些设备
  • 适配器-传输要写入硬件的值,指示应将值写入哪个寄存器


IPower接口



考虑所有要求后,我们将在界面中定义所需的方法:



template<typename… Peripherals>
Enable();

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

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


启用-启用模板参数中指定的外围设备。



EnableExcept-启用EnableList参数中指定的外围设备,但ExceptList中指定的外围设备除外。



说明


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方法来补充



保留-从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方法,但是其余的呢?



如果我们像解释中那样限制使用两种类型的外围设备,则不会出现任何困难。但是,当项目使用许多不同的外围设备时,会出现问题-您不能在模板中显式使用多个参数包,因为 编译器将无法确定一个终止于何处,第二个终止于何处:



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


但是即使在这种情况下,接口也将严格地与寄存器的数量绑定在一起,因此,对于每种类型的控制器,都将有必要编写自己的单独类,并进行许多相同类型的操作,而又不可能分成抽象层。



由于使用的所有外设和时钟寄存器在编译阶段都是已知的,因此可以使用元编程来解决该任务。



元编程



由于元编程不是基于普通类型而是基于列表,因此我们将定义两个实体,它们将使用典型和非典型参数进行操作:



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.检查清单是否为空



是空的
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,这是接口方法所必需的。



由于这些位转换是相同类型的,因此我们将尝试使实现尽可能通用,以避免代码重复。



对列表执行抽象操作的函数



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



接下来,我们将实现前一个元函数的操作,该元函数将对来自两个列表的非典型参数执行逐项抽象操作:



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



位运算功能:



按位运算
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该类使您可以将外围设备组合到一个列表中,然后可以在方法中使用它:



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


写寄存器



该类由一个递归模板方法组成,该方法的任务是将值写入适配器传递的控制器寄存器中。



该方法接受3个非典型值列表<…>参数的列表作为参数



  • SetListResetList-要在寄存器中设置/重置的位值序列的列表
  • 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>();
    }
  };

};


该类仅包含将在程序集列表中包含的代码行。



现在,该结构的所有块均已准备就绪,让我们继续进行测试。



测试代码



让我们回顾最后一个问题的条件:



  • 启用S​​PI2和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,GCC 10.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