كم مرة ، عند تطوير البرامج الثابتة لوحدة تحكم دقيقة ، أثناء تصحيح الأخطاء ، عندما لا تعمل وحدات البايت على 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 للواجهات
- النقص التام في قابلية النقل. عند اختيار وحدة تحكم مختلفة ، يفقد هذا الرمز معناها
مع كل أوجه القصور ، تظل هذه الطريقة شائعة جدًا ، على الأقل عندما تحتاج إلى "الشعور" بوحدة التحكم الجديدة من خلال
وظائف التهيئة
دعنا نحاول تلخيص وإخفاء العمل مع السجلات من المستخدم. وستساعدنا وظيفة 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.
نظرًا لأننا في وقت الترجمة نعرف جميع القيم التي يجب كتابتها في السجلات ، فسوف نتخلص من عمليات المكدس. للقيام بذلك ، سننشئ فئة منفصلة مع طريقة القالب ، وسنمنح الفئات الطرفية بخصائص (سمة القيمة) التي ستخزن قيمًا للسجلات المقابلة.
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.
تفسير
, :
SPI2EN IOPBEN. , DMA1EN, USART1EN IOPAEN .
, :
| 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 ، بينما إذا كانت الأجهزة الطرفية موجودة في كلتا القائمتين ، فإنها تحتفظ بحالتها.
تفسير
, :
SPI2EN IOPBEN, USART1EN IOPAEN , DMA1EN .
, :
| 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 ، المطلوبة لطرق الواجهة.
نظرًا لأن تحويلات البت هذه من نفس النوع ، سنحاول جعل التنفيذ عامًا قدر الإمكان لتجنب تكرار الكود.
وظيفة تقوم بتنفيذ عملية مجردة في قائمة
قوائم التشغيل
Lists – , , .
operation – , 2 Lists .
isEnd – , Lists.
(1) Lists 1 , (2).
– (3) (4) Lists, (6). (7) , (6), (5) Lists. (1).
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).
بعد ذلك ، سننفذ العملية الخاصة بالوظيفة الوصفية السابقة ، والتي ستقوم بتنفيذ إجراءات مجردة لكل مصطلح على حدة على معلمات غير نمطية من قائمتين:
عملية القيمين
List1 List2 – , .
operation – , .
Result – , .
(1), , Result.
(2) Result (3). (4) , .
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;
};
جيثب