قم بتشغيل ملحقات وحدة التحكم في دورة ساعة واحدة أو 500 سطر سحري من التعليمات البرمجية





كم مرة ، عند تطوير البرامج الثابتة لوحدة تحكم دقيقة ، أثناء تصحيح الأخطاء ، عندما لا تعمل وحدات البايت على UART ، تصرخ: "آه ، بالضبط! لم يتم تمكين تسجيل الوقت! " أو ، عندما غيرت ساق LED ، هل نسيت "تطبيق الطاقة" على المنفذ الجديد؟ في كثير من الأحيان ، على ما أعتقد. أنا ، على الأقل - بالتأكيد.



للوهلة الأولى ، قد يبدو أن التحكم في توقيت المحيط أمر تافه: كتب 1 - ممكّن ، 0 - معطل.



لكن كلمة "بسيطة" ليست فعالة دائمًا ...



صياغة المشكلة



قبل كتابة الكود ، من الضروري تحديد المعايير التي يمكن من خلالها تقييمها. في حالة نظام تسجيل الوقت المحيطي لوحدة التحكم ، قد تبدو القائمة كما يلي:



  • في الأنظمة المضمنة ، أحد أهم المعايير هو أصغر كود ناتج ممكن ، يتم تنفيذه في أقصر وقت ممكن.
  • . - code review , /
  • , ,
  • ( )


بعد توضيح معايير التقييم ، سنقوم بتعيين مهمة محددة ، على طول الطريق تحديد الشروط و "البيئة" للتنفيذ:



مترجم: GCC 10.1.1 + Make

Language: C ++ 17

البيئة: Visual Studio Code

Controller: stm32f103c8t6 (cortex-m3)

المهمة: تمكين تسجيل الوقت SPI2 ، USART1 (كلا الواجهتين تستخدمان DMA)



يرجع اختيار وحدة التحكم هذه ، بالطبع ، إلى انتشارها ، خاصة بفضل إحدى الحرف الشعبية الصينية - إنتاج ألواح Blue Pill.







من وجهة نظر الأيديولوجيا ، لا يهم أي جهاز تحكم يتم اختياره: stmf1 أو stmf4 أو lpc ، لأن يتم تقليل العمل مع نظام الساعة الطرفية فقط إلى الكتابة إلى بت معين إما 0 لإيقاف التشغيل أو 1 للتشغيل.



يوجد في stm32f103c8t6 3 سجلات مسؤولة عن تمكين تسجيل الوقت المحيطي: AHBENR و APB1ENR و APB2ENR.



لم يتم اختيار واجهات الأجهزة الخاصة بنقل البيانات SPI2 و USART1 بالصدفة ، لأنه من أجل عملها الكامل ، من الضروري تمكين بتات الساعة الموجودة في جميع السجلات المدرجة - بتات الواجهات نفسها ، DMA1 ، وكذلك بتات منافذ الإدخال والإخراج (GPIOB لـ SPI2 و GPIOA لـ USART1).









تجدر الإشارة إلى أنه من أجل الأداء الأمثل مع تسجيل الوقت ، من الضروري مراعاة - يحتوي AHBENR على مورد مشترك يستخدم لتشغيل كل من SPI2 و USART1. أي أن تعطيل DMA سيؤدي على الفور إلى عدم تشغيل كلا الواجهتين ، وفي نفس الوقت ، لن تكون كفاءة إعادة الإغلاق صفرية ، بل سلبية ، لأن هذه العملية ستشغل ذاكرة البرنامج وستؤدي إلى استهلاك إضافي للساعة لقراءة وتعديل وكتابة سجل متغير.



بعد أن تعاملنا مع أهداف وظروف وخصائص المشكلة ، دعنا ننتقل إلى إيجاد الحلول.



النهج الأساسية



يحتوي هذا القسم على طرق نموذجية لتمكين ميزة تسجيل الوقت المحيطي التي صادفتها ، وبالتأكيد شاهدتها و / أو استخدمتها أيضًا. من الأبسط ، المطبقة في C ، إلى طي التعبير من C ++ 17. يتم النظر في مزاياها وعيوبها المتأصلة.



إذا كنت تريد الانتقال مباشرة إلى Metaprogramming ، فيمكنك تخطي هذا القسم والانتقال إلى التالي .



الكتابة المباشرة للسجلات



الطريقة الكلاسيكية "متوفرة خارج الصندوق" لكل من C و C ++. يوفر البائع ، في أغلب الأحيان ، ملفات رأس لوحدة التحكم ، حيث تكون جميع السجلات والبتات الخاصة بها معطلة بشكل افتراضي ، مما يجعل من الممكن البدء فورًا في العمل مع الأجهزة الطرفية:



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


قائمة
    // AHBENR( DMA1)
  ldr     r3, .L3
  ldr     r2, [r3, #20]
  orr     r2, r2, #1
  str     r2, [r3, #20]
    // APB2ENR( GPIOA, GPIOB, USART1)
  ldr     r2, [r3, #24]
  orr     r2, r2, #16384
  orr     r2, r2, #12
  str     r2, [r3, #24]
    // APB1ENR( SPI2)
  ldr     r2, [r3, #28]
  orr     r2, r2, #16384
  str     r2, [r3, #28]




حجم الكود: 36 بايت. عرض



الايجابيات:



  • الحد الأدنى لحجم الكود وسرعة التنفيذ
  • الطريقة الأسهل والأكثر وضوحًا


سلبيات:



  • من الضروري تذكر أسماء السجلات وأسماء البتات ، أو الرجوع باستمرار إلى الدليل
  • من السهل ارتكاب خطأ في التعليمات البرمجية الخاصة بك. يجب أن يكون القارئ قد لاحظ أنه بدلاً من SPI2 ، تمت إعادة تمكين USART1.
  • لكي تعمل بعض الوحدات الطرفية ، تحتاج أيضًا إلى تمكين الأجهزة الطرفية الأخرى ، مثل GPIO و DMA للواجهات
  • النقص التام في قابلية النقل. عند اختيار وحدة تحكم مختلفة ، يفقد هذا الرمز معناها


مع كل أوجه القصور ، تظل هذه الطريقة شائعة جدًا ، على الأقل عندما تحتاج إلى "الشعور" بوحدة التحكم الجديدة من خلال كتابة عبارة "Hello، World!" التالية عن طريق وميض LED.



وظائف التهيئة



دعنا نحاول تلخيص وإخفاء العمل مع السجلات من المستخدم. وستساعدنا وظيفة C العادية في هذا:



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

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

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


حجم الكود: 72 بايت. نظرة



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




الايجابيات:



  • ليس عليك إلقاء نظرة على الدليل في كل مناسبة.
  • يتم ترجمة الأخطاء في مرحلة كتابة برنامج تشغيل طرفي
  • الكود المخصص سهل القراءة


سلبيات:



  • زاد عدد التعليمات المطلوبة بمضاعفات عدد الأجهزة الطرفية المعنية
  • يوجد الكثير من تكرار الكود - لكل رقم UART و SPI ، سيكون متطابقًا تقريبًا


رغم أننا تخلصنا من الكتابة المباشرة للسجلات في كود المستخدم ، لكن بأي ثمن؟ تضاعف حجم الذاكرة المطلوب ووقت التنفيذ للتشغيل وسيستمر في النمو مع مشاركة المزيد من الأجهزة الطرفية.



ساعة تمكين وظيفة



دعنا نلف تعديل الساعات في وظيفة منفصلة ، بافتراض أن هذا سيقلل من مقدار الذاكرة المطلوبة. في الوقت نفسه ، سنقدم معلمة معرف للأجهزة الطرفية - لتقليل رمز برنامج التشغيل:



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)




الايجابيات:

  • كان من الممكن تقصير رمز وصف برامج تشغيل وحدة التحكم الدقيقة
  • انخفض العدد الناتج من التعليمات *


سلبيات:



  • زيادة وقت التنفيذ


* نعم ، في هذه الحالة زاد حجم الكود القابل للتنفيذ مقارنة بالإصدار السابق ، ولكن هذا يرجع إلى ظهور عوامل شرطية ، يمكن تحييد تأثيرها إذا تم استخدام نسختين على الأقل من كل نوع من الأطراف.



لان تقبل وظيفة التضمين المعلمات ، ثم ظهرت عمليات المكدس في المجمع ، مما يؤثر أيضًا سلبًا على الأداء.



في هذه المرحلة ، أعتقد أن جميع قوانا تستحق الانتقال إلى الإيجابيات ، لأنه يتم النظر في الأساليب الرئيسية المستخدمة في 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]




الايجابيات:



  • اتضح أن الحجم ووقت التنفيذ هو نفسه كما في النسخة المرجعية مع الكتابة المباشرة للسجلات
  • من السهل جدًا توسيع نطاق المشروع - ما عليك سوى إضافة قيمة الخاصية المقابلة للمياه للمحيط


سلبيات:



  • من الممكن ارتكاب خطأ بوضع قيمة الخاصية في المعامل الخطأ
  • كما في حالة الكتابة المباشرة للسجلات - تعاني قابلية النقل
  • فائض البناء


تمكنا من تحقيق العديد من الأهداف المحددة ، ولكن هل من الملائم استخدامها؟ لا أعتقد ذلك ، لأنه لإضافة كتلة أخرى من المحيط ، من الضروري التحكم في الترتيب الصحيح لخصائص الفئة في معلمات قالب الطريقة.



مثالي ... تقريبا



لتقليل مقدار الكود المخصص وفرص الأخطاء ، سنستخدم حزمة المعلمات ، والتي ستزيل الوصول إلى خصائص الفئات الطرفية في الكود المخصص. سيؤدي هذا فقط إلى تغيير طريقة تمكين تسجيل الوقت:



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 ، باستثناء تلك المحددة في ExclusiveList.



تفسير


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




يتم استكمالها بطرق تعطيل تكميلية تقوم بالعكس.



Keep - تمكين الأجهزة الطرفية من EnableList ، وتعطيل الأجهزة الطرفية من DisableList ، بينما إذا كانت الأجهزة الطرفية موجودة في كلتا القائمتين ، فإنها تحتفظ بحالتها.



تفسير


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


, :

Keep<spi, uart>();


SPI2EN IOPBEN, USART1EN IOPAEN , DMA1EN .



, :



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




تم بالفعل تنفيذ أساليب التشغيل / الإيقاف بشكل جيد مع التعبير المطوي ، ولكن ماذا عن الباقي؟



إذا قصرنا أنفسنا على استخدام نوعين من المحيطات ، كما هو الحال في الشرح ، فلن تنشأ أية صعوبات. ومع ذلك ، عند استخدام العديد من الأجهزة الطرفية المختلفة في مشروع ما ، تنشأ مشكلة - لا يمكنك صراحة استخدام أكثر من حزمة معلمة واحدة في قالب ، حيث لن يتمكن المترجم من تحديد مكان انتهاء أحدهما ويبدأ الثاني:



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


ولكن في هذه الحالة ، ستصبح الواجهة مرتبطة بشكل صارم بعدد السجلات ، لذلك ، لكل نوع من أنواع وحدات التحكم ، سيصبح من الضروري كتابة فئة منفصلة خاصة به ، مع العديد من العمليات من نفس النوع وبدون إمكانية التقسيم إلى طبقات مجردة.



نظرًا لأن جميع الأجهزة الطرفية وسجلات الساعة المستخدمة معروفة في مرحلة التجميع ، يمكن حل المهمة باستخدام metaprogramming.



ميتابرومجة



نظرًا لحقيقة أن البرمجة الوصفية تعتمد على العمل ليس مع الأنواع العادية ، ولكن مع قوائمها ، فسوف نحدد كيانين يعملان بمعلمات نموذجية وغير نموذجية:



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. إضافة عنصر إلى بداية القائمة

دفع_مقدمة
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 ، المطلوبة لطرق الواجهة.



نظرًا لأن تحويلات البت هذه من نفس النوع ، سنحاول جعل التنفيذ عامًا قدر الإمكان لتجنب تكرار الكود.



وظيفة تقوم بتنفيذ عملية مجردة في قائمة



قوائم التشغيل
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).



بعد ذلك ، سننفذ العملية الخاصة بالوظيفة الوصفية السابقة ، والتي ستقوم بتنفيذ إجراءات مجردة لكل مصطلح على حدة على معلمات غير نمطية من قائمتين:



عملية القيمين
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 :



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 قوائم من معلمات Valuelist <…> غير النموذجية كمعلمات :



  • SetList و ResetList - قوائم تسلسل قيم البت التي سيتم تعيينها / إعادة تعيينها في السجل
  • قائمة العناوين - قائمة بعناوين التسجيل التي ستُكتب إليها القيم من المعلمات السابقة


struct HPower{

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

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

      if constexpr(valueSet || valueReset){

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

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

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

};


يحتوي الفصل على سطر التعليمات البرمجية الوحيد الذي سيتم تضمينه في قائمة التجميع.



الآن بعد أن أصبحت جميع كتل الهيكل جاهزة ، دعنا ننتقل إلى الاختبار.



اختبار الكود



لنتذكر شروط المشكلة الأخيرة:



  • تمكين SPI2 و USART1
  • إيقاف تشغيل SPI2 قبل الدخول إلى "وضع توفير الطاقة"
  • تمكين SPI2 بعد الخروج من "وضع توفير الطاقة"


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

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

int main() {

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

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

    //Sleep();

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



حجم الكود: 68 بايت * كما في حالة الكتابة المباشرة للسجلات.



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




* باستخدام GCC 9.2.1 ، وهذا يزيد 8 بايت عن GCC 10.1.1 . كما ترى من القائمة ، تمت إضافة العديد من الإرشادات غير الضرورية ، على سبيل المثال ، قبل القراءة إلى العنوان ( ldr ) توجد تعليمات إضافة ( تضيف ) ، على الرغم من أنه يمكن استبدال هذه التعليمات بقراءة مع إزاحة. الإصدار الجديد يحسن هذه العمليات. في نفس الوقت ، يولد كلانج نفس القوائم.



النتيجة



تم تحقيق الأهداف المحددة في بداية المقالة - ظلت سرعة التنفيذ والكفاءة على مستوى الكتابة المباشرة إلى السجل ، وتقليل احتمال حدوث خطأ في رمز المستخدم.



ربما يبدو حجم الكود المصدري وتعقيد التطوير زائدين عن الحاجة ، ولكن بفضل هذا العدد من التجريدات ، فإن الانتقال إلى وحدة تحكم جديدة سيستغرق الحد الأدنى من الجهد: 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;
};







جيثب



All Articles