Wie oft, wenn Sie Firmware für einen Mikrocontroller entwickeln, während des Debuggens, wenn die Bytes nicht auf dem UART ausgeführt werden, rufen Sie aus: „Ahh, genau! Taktung nicht aktiviert! " Oder haben Sie beim Wechseln des LED-Fußes vergessen, den neuen Anschluss mit Strom zu versorgen? Sehr oft denke ich. Zumindest ich - ganz sicher.
Auf den ersten Blick scheint es trivial zu sein, das Timing der Peripherie zu steuern: 1 - aktiviert, 0 - deaktiviert.
Aber "einfach" ist nicht immer effektiv ...
Formulierung des Problems
Vor dem Schreiben des Codes müssen die Kriterien festgelegt werden, anhand derer er bewertet werden kann. Im Fall eines Peripherietaktsystems der Steuerung kann die Liste folgendermaßen aussehen:
- In eingebetteten Systemen ist eines der wichtigsten Kriterien der kleinstmögliche resultierende Code, der in kürzester Zeit ausgeführt wird.
- . - code review , /
- , ,
- ( )
Nachdem wir die Bewertungskriterien geklärt haben, legen wir eine bestimmte Aufgabe fest und definieren die Bedingungen und die "Umgebung" für die Implementierung:
Compiler: GCC 10.1.1 + Make
Language: C ++ 17
Umgebung: Visual Studio Code
Controller: stm32f103c8t6 (cortex-m3)
Aufgabe: Aktivieren der Taktung SPI2, USART1 (beide Schnittstellen verwenden DMA)
Die Wahl dieses Controllers ist natürlich auf seine Verbreitung zurückzuführen, insbesondere dank eines der chinesischen Volkshandwerke - der Herstellung von Blue Pill-Boards.
Aus ideologischer Sicht spielt es keine Rolle, welcher Controller gewählt wird: stmf1, stmf4 oder lpc, da Das Arbeiten mit dem peripheren Taktsystem reduziert sich nur auf das Schreiben auf ein bestimmtes Bit, entweder 0 zum Ausschalten oder 1 zum Einschalten.
In stm32f103c8t6 gibt es 3 Register, die für die Aktivierung der peripheren Taktung verantwortlich sind: AHBENR, APB1ENR, APB2ENR.
Die Hardwareschnittstellen für die Datenübertragung SPI2 und USART1 wurden nicht zufällig ausgewählt, da für ihre volle Funktion die in allen aufgelisteten Registern befindlichen Taktbits aktiviert werden müssen - die Bits der Schnittstellen selbst, DMA1, sowie die Bits der Eingabe-Ausgabe-Ports (GPIOB für SPI2 und GPIOA für USART1).
Es sollte beachtet werden, dass Sie für eine optimale Leistung beim Takten Folgendes berücksichtigen müssen: AHBENR enthält eine gemeinsam genutzte Ressource, die für die Funktion von SPI2 und USART1 verwendet wird. Das heißt, das Deaktivieren von DMA führt sofort zur Inoperabilität beider Schnittstellen, gleichzeitig ist die Wiedereinschaltleistung nicht einmal Null, sondern negativ, da diese Operation den Programmspeicher belegt und zu einem zusätzlichen Taktverbrauch für das Lesen, Ändern und Schreiben eines flüchtigen Registers führt.
Nachdem wir uns mit den Zielen, Bedingungen und Merkmalen des Problems befasst haben, wollen wir weiter nach Lösungen suchen.
Grundlegende Ansätze
Dieser Abschnitt enthält typische Möglichkeiten zum Aktivieren der peripheren Taktung, auf die ich gestoßen bin, und natürlich haben Sie sie auch gesehen und / oder verwendet. Von einfacheren, in C implementierten bis hin zum Falten von Ausdrücken aus C ++ 17. Ihre inhärenten Vor- und Nachteile werden berücksichtigt.
Wenn Sie direkt zur Metaprogrammierung wechseln möchten, können Sie diesen Abschnitt überspringen und mit dem nächsten fortfahren .
Direktes Schreiben in Register
Der klassische Weg, "sofort einsatzbereit" für C und C ++. Der Anbieter stellt in der Regel Header-Dateien für den Controller bereit, in denen alle Register und ihre Bits voreingestellt sind, sodass sofort mit der Arbeit mit Peripheriegeräten begonnen werden kann:
int main(){
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN
| RCC_APB2ENR_IOPBEN
| RCC_APB2ENR_USART1EN;
RCC->APB2ENR |= RCC_APB1ENR_SPI2EN;
…
}
Auflistung
// 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]
Codegröße: 36 Bytes. Vorteile
anzeigen:
- Minimale Codegröße und Ausführungsgeschwindigkeit
- Der einfachste und naheliegendste Weg
Minuspunkte:
- Es ist notwendig, sich die Namen der Register und die Namen der Bits zu merken oder sich ständig auf das Handbuch zu beziehen
- Es ist leicht, einen Fehler in Ihrem Code zu machen. Der Leser muss bemerkt haben, dass USART1 anstelle von SPI2 wieder aktiviert wurde.
- Damit einige Peripheriegeräte funktionieren, müssen Sie auch andere Peripheriegeräte wie GPIO und DMA für Schnittstellen aktivieren
- Völlige mangelnde Portabilität. Bei Auswahl eines anderen Controllers verliert dieser Code seine Bedeutung
Trotz aller Mängel bleibt diese Methode sehr beliebt, zumindest wenn Sie den neuen Controller "fühlen" müssen, indem Sie
Initialisierungsfunktionen
Versuchen wir, die Arbeit mit Registern vor dem Benutzer zu abstrahieren und zu verbergen. Und eine gewöhnliche C-Funktion hilft uns dabei:
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();
…
}
Codegröße: 72 Bytes. Aussehen
Auflistung
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()
Vorteile:
- Sie müssen das Handbuch nicht für jeden Anlass lesen.
- Fehler werden beim Schreiben eines Peripherietreibers lokalisiert
- Benutzerdefinierter Code ist leicht zu lesen
Minuspunkte:
- Die Anzahl der erforderlichen Anweisungen hat sich um ein Vielfaches der Anzahl der beteiligten Peripheriegeräte erhöht
- Viel Code-Duplizierung - für jede UART- und SPI-Nummer ist sie praktisch identisch
Obwohl wir das direkte Schreiben in Register im Benutzercode losgeworden sind, zu welchem Preis? Die erforderliche Speichergröße und Ausführungszeit zum Einschalten haben sich verdoppelt und werden weiter zunehmen, wobei mehr Peripheriegeräte beteiligt sind.
Uhrfreigabefunktion
Lassen Sie uns die Änderung der Uhren in eine separate Funktion einbinden, vorausgesetzt, dies reduziert den erforderlichen Speicherplatz. Gleichzeitig werden wir einen Bezeichnerparameter für die Peripheriegeräte einführen, um den Treibercode zu reduzieren:
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);
…
}
Codegröße: 92 Bytes. Aussehen
Auflistung
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)
Vorteile:
- Der Beschreibungscode der Mikrocontroller-Treiber konnte gekürzt werden
- Die resultierende Anzahl von Anweisungen nahm ab *
Minuspunkte:
- Erhöhte Ausführungszeit
* Ja, in diesem Fall hat sich die Größe des ausführbaren Codes im Vergleich zur vorherigen Version erhöht. Dies ist jedoch auf das Auftreten von bedingten Operatoren zurückzuführen, deren Einfluss neutralisiert werden kann, wenn mindestens 2 Kopien jedes Peripherietyps verwendet werden.
weil Die Include-Funktion akzeptiert Parameter. Anschließend werden im Assembler Stapeloperationen angezeigt, die sich ebenfalls negativ auf die Leistung auswirken.
An diesem Punkt denke ich, dass
Werteigenschaften und Vorlagen
Wenn wir uns mit dem positiven Ansatz befassen, werden wir sofort die Option überspringen, die Taktung in den Klassenkonstruktor aufzunehmen, da Diese Methode unterscheidet sich eigentlich nicht von Initialisierungsfunktionen im C-Stil.
Da wir zur Kompilierungszeit alle Werte kennen, die in Register geschrieben werden müssen, werden wir Stapeloperationen los. Zu diesem Zweck erstellen wir eine separate Klasse mit einer Vorlagenmethode und verleihen den peripheren Klassen Eigenschaften ( Werteigenschaft ) , in denen Werte für die entsprechenden Register gespeichert werden.
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
>();
…
}
Codegröße: 36 Bytes. Aussehen
Auflistung
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]
Vorteile:
- Die Größe und Ausführungszeit waren die gleichen wie in der Referenzversion mit direktem Schreiben in Register
- Es ist ziemlich einfach, das Projekt zu skalieren - fügen Sie einfach
Wasser hinzu, dasdem Eigenschaftswert der Peripherie entspricht
Minuspunkte:
- Es ist möglich, einen Fehler zu machen, indem Sie die value-Eigenschaft in den falschen Parameter setzen
- Wie beim direkten Schreiben in Register leidet die Portabilität
- Bauüberlastung
Wir konnten mehrere gesetzte Ziele erreichen, aber ist es bequem, sie zu verwenden? Ich denke nicht, denn um einen weiteren Peripherieblock hinzuzufügen, ist es notwendig, die korrekte Anordnung der Klasseneigenschaften in den Parametern der Methodenvorlage zu steuern.
Ideal ... fast
Um die Menge an benutzerdefiniertem Code und die Möglichkeit von Fehlern zu verringern, verwenden wir das Parameterpaket, mit dem der Zugriff auf die Eigenschaften der Peripherieklassen im benutzerdefinierten Code entfernt wird. Dadurch wird nur die Methode zum Aktivieren der Taktung geändert:
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>();
…
}
Codegröße: 36 Bytes. Aussehen
Auflistung
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]
Im Vergleich zur Vorgängerversion hat sich die Einfachheit des Benutzercodes erheblich erhöht, die Fehlerwahrscheinlichkeit ist minimal geworden und der Speicherverbrauch ist auf dem gleichen Niveau geblieben.
Und anscheinend können Sie damit aufhören, aber ...
Funktionalität erweitern
Wenden wir uns einem unserer Ziele zu:
Zusätzlich zu den grundlegenden Funktionen zum Aktivieren und Deaktivieren der Peripherietaktung sind erweiterte Funktionen erforderlich
Angenommen, die Aufgabe besteht darin, das Gerät stromsparend zu machen. Dazu müssen natürlich alle Peripheriegeräte ausgeschaltet werden, die der Controller nicht zum Verlassen des Energiesparmodus verwendet.
Im Zusammenhang mit den am Anfang des Artikels geäußerten Bedingungen wird davon ausgegangen, dass USART1 der Generator des Weckereignisses ist und SPI2 und der entsprechende GPIOB-Port deaktiviert werden sollten. In diesem Fall muss die gemeinsam genutzte Ressource DMA1 aktiviert bleiben.
Mit einer Option aus dem vorherigen Abschnitt ist es nicht möglich, dieses Problem sowohl effizient als auch optimal zu lösen und gleichzeitig die manuelle Steuerung der beteiligten Blöcke nicht manuell zu steuern.
Nehmen wir zum Beispiel den letzten Weg:
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>();
…
}
Codegröße: 100 Bytes. Aussehen
Auflistung
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]
Gleichzeitig benötigte der Referenzcode in den Registern 68 Bytes. Ansicht
Offensichtlich besteht der Stolperstein für solche Aufgaben aus gemeinsam genutzten Ressourcen wie DMA. Darüber hinaus wird es in diesem speziellen Fall einen Moment geben, in dem beide Schnittstellen nicht mehr funktionsfähig sind und tatsächlich eine Notsituation auftritt.
Versuchen wir eine Lösung zu finden ...
Struktur
Um das Verständnis und die Entwicklung zu vereinfachen, werden wir die allgemeine Taktstruktur so darstellen, wie wir sie haben möchten:
Sie besteht nur aus vier Blöcken:
Unabhängig:
- IPower - eine Benutzeroberfläche, die Daten für das Schreiben in Register vorbereitet
- Hardware - Schreiben von Werten in Controller-Register
Hardware-abhängig:
- Peripheriegeräte - Peripheriegeräte, die im Projekt verwendet werden und der Schnittstelle mitteilen, welche Geräte ein- oder ausgeschaltet werden sollen
- Adapter - überträgt Werte, die in die Hardware geschrieben werden sollen, und gibt genau an, in welche Register sie geschrieben werden sollen
IPower-Schnittstelle
Unter Berücksichtigung aller Anforderungen definieren wir die in der Schnittstelle erforderlichen Methoden:
template<typename… Peripherals>
Enable();
template<typename EnableList, typename ExceptList>
EnableExcept();
template<typename EnableList, typename DisableList>
Keep();
Aktivieren - Aktiviert die im Vorlagenparameter angegebenen Peripheriegeräte.
EnableExcept - Aktiviert Peripheriegeräte, die im Parameter EnableList angegeben sind, mit Ausnahme der in ExceptList angegebenen.
Erläuterung
, :
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
Diese werden durch komplementäre Deaktivierungsmethoden ergänzt , die das Gegenteil bewirken .
Behalten - Peripheriegeräte aus EnableList aktivieren, Peripheriegeräte aus DisableList deaktivieren. Wenn Peripheriegeräte in beiden Listen vorhanden sind, behalten sie ihren Status bei.
Erläuterung
, :
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
Die Ein / Aus-Methoden wurden bereits mit Fold-Ausdruck ziemlich gut implementiert, aber was ist mit dem Rest?
Wenn wir uns auf die Verwendung von zwei Arten von Peripherie beschränken, wie in der Erklärung beschrieben, entstehen keine Schwierigkeiten. Wenn jedoch viele verschiedene Peripheriegeräte in einem Projekt verwendet werden, tritt ein Problem auf - Sie können seitdem nicht explizit mehr als ein Parameterpaket in einer Vorlage verwenden Der Compiler kann nicht feststellen, wo einer endet und der zweite beginnt:
template<typename… EnableList, typename… ExceptList>
EnableExcept(){…};
// EnableList ExceptList
EnableExcept<spi2, pin3, uart1, pin1, i2c3>();
Es wäre möglich, eine separate Wrapper-Klasse für die Peripherie zu erstellen und an die Methode zu übergeben:
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>();
In diesem Fall wird die Schnittstelle jedoch fest an die Anzahl der Register gebunden. Daher muss für jeden Reglertyp eine eigene Klasse mit vielen Operationen desselben Typs und ohne die Möglichkeit der Aufteilung in abstrakte Schichten geschrieben werden.
Da alle verwendeten Peripheriegeräte und Taktregister in der Kompilierungsphase bekannt sind, kann die Aufgabe mithilfe der Metaprogrammierung gelöst werden.
Metaprogrammierung
Aufgrund der Tatsache, dass die Metaprogrammierung auf der Arbeit nicht mit gewöhnlichen Typen, sondern mit ihren Listen basiert, werden wir zwei Entitäten definieren, die mit typischen und nicht typischen Parametern arbeiten:
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
Bevor wir mit diesen Listen etwas Nützliches anfangen können, müssen wir einige grundlegende Operationen implementieren, auf deren Grundlage es möglich wird, komplexere Aktionen auszuführen.
1. Das erste Element aus der Liste abrufen
Vorderseite
//
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. Entfernen des ersten Elements aus der Liste
pop_front
template<typename List>
struct pop_front;
//
//
template<typename Head, typename... Tail>
struct pop_front<Typelist<Head, Tail...>> {
// ,
using type = Typelist<Tail...>;
};
template<auto Head, auto... Tail>
struct pop_front<Valuelist<Head, Tail...>> {
using type = Valuelist<Tail...>;
};
template<typename List>
using pop_front_t = typename pop_front<List>::type;
//
using listT = Typelist<char, bool, int>;
using typeT = pop_front_t<listT>; // type = Typelist<bool, int>
using listV = Valuelist<9,8,7>;
using typeV = pop_front_t<listV>; // type = Valuelist<8,7>
3. Hinzufügen eines Elements am Anfang der Liste
push_front
template<typename List, typename NewElement>
struct push_front;
template<typename... List, typename NewElement>
struct push_front<Typelist<List...>, NewElement> {
using type = Typelist<NewElement, List...>;
};
template<typename List, typename NewElement>
using push_front_t = typename push_front<List, NewElement>::type;
//
using listT = Typelist<char, bool, int>;
using typeT = push_front_t<listT, long >; // type = Typelist<long, char, bool, int>
4. Hinzufügen eines nicht standardmäßigen Parameters am Ende der Liste
push_back_value
template<typename List, auto NewElement>
struct push_back;
template<auto... List, auto NewElement>
struct push_back<Valuelist<List...>, NewElement>{
using type = Valuelist<List..., NewElement>;
};
template<typename List, auto NewElement>
using push_back_t = typename push_back<List, NewElement>::type;
//
using listV = Valuelist<9,8,7>;
using typeV = push_back_t<listV, 6>; // typeV = Valuelist<9,8,7,6>
5. Überprüfen Sie die Liste auf Leere
ist leer
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. Ermitteln der Anzahl der Elemente in der Liste
size_of_list
// ,
// count, 2
template<typename List, std::size_t count = 0>
struct size_of_list : public size_of_list<pop_front_t<List>, count + 1>{};
//
template<std::size_t count>
struct size_of_list<Typelist<>, count>{
static constexpr std::size_t value = count;
};
//
template<std::size_t count>
struct size_of_list<Valuelist<>, count>{
static constexpr std::size_t value = count;
};
template<typename List>
static constexpr std::size_t size_of_list_v = size_of_list<List>::value;
//
using listT = Typelist<char, bool, int>;
constexpr auto value = size_of_list_v <listT>; // value = 3
Nachdem alle grundlegenden Aktionen definiert sind, können Sie Metafunktionen für bitweise Operationen schreiben: oder , und , xor , die für Schnittstellenmethoden erforderlich sind.
Da diese Bittransformationen vom gleichen Typ sind, werden wir versuchen, die Implementierung so allgemein wie möglich zu gestalten, um Codeduplikationen zu vermeiden.
Eine Funktion, die eine abstrakte Operation für eine Liste ausführt
list_operation
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).
Als Nächstes implementieren wir die Operation für die vorherige Metafunktion, die termweise abstrakte Aktionen für nicht standardmäßige Parameter aus zwei Listen ausführt:
valuelists_operation
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) , .
Bitoperationsfunktionen:
bitweise_operation
template<auto value1, auto value2>
struct and_operation{ static constexpr auto value = value1 & value2;};
template<auto value1, auto value2>
struct or_operation{ static constexpr auto value = value1 | value2;};
template<auto value1, auto value2>
struct xor_operation{ static constexpr auto value = value1 ^ value2;};
Es bleiben Aliase zur einfacheren Verwendung zu erstellen:
Aliase
( ).
// 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;
( ).
Zurück zur Implementierung der Schnittstelle
Da sowohl der Controller als auch die verwendeten Peripheriegeräte zur Kompilierungszeit bekannt sind, ist die logische Wahl für die Implementierung der Schnittstelle der statische Polymorphismus mit der CRTP-Sprache . Als Vorlagenparameter verwendet eine Schnittstelle eine Adapterklasse eines bestimmten Controllers, die wiederum von dieser Schnittstelle erbt.
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...>;
};
};
Außerdem enthält die Schnittstelle eine integrierte fromPeripherals- Klasse , mit der Sie Peripheriegeräte in einer Liste kombinieren können, die dann in folgenden Methoden verwendet werden kann:
using listPower = Power::fromPeripherals<spi, uart>;
Power::Enable<listPower>();
Deaktivieren Methoden werden in ähnlicher Weise umgesetzt.
Controller-Adapter
In der Adapterklasse müssen Sie die Adressen der Taktregister festlegen und die Reihenfolge bestimmen, in der in diese geschrieben werden soll, und dann die Steuerung direkt an die Klasse übertragen, die die Bits der angegebenen Register setzt oder löscht.
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>;
};
};
Peripherie
Wir verleihen der Peripherie eine Leistungseigenschaft unter Verwendung der fromValues- Struktur des Adapters:
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;
};
Schreiben in Register
Die Klasse besteht aus einer rekursiven Vorlagenmethode, deren Aufgabe es ist, Werte in die vom Adapter übergebenen Controller-Register zu schreiben.
Die Methode akzeptiert 3 Listen nicht typischer Valuelist <…> -Parameter als Parameter :
- SetList und ResetList - Listen von Sequenzen von Bitwerten , die in einem Register gesetzt / zurückgesetzt werden sollen
- AddressesList - Eine Liste von Registeradressen, in die die Werte der vorherigen Parameter geschrieben werden
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>();
}
};
};
Die Klasse enthält die einzige Codezeile, die in der Assembly-Liste enthalten ist.
Nachdem alle Blöcke der Struktur fertig sind, fahren wir mit dem Testen fort.
Code testen
Erinnern wir uns an die Bedingungen des letzten Problems:
- Aktivieren von SPI2 und USART1
- Ausschalten von SPI2 vor dem Aufrufen des "Energiesparmodus"
- Aktivieren von SPI2 nach Verlassen des "Energiesparmodus"
//
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>();
…
}
Codegröße: 68 Bytes *, wie beim direkten Schreiben in Register.
Auflistung
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]
* Bei Verwendung von GCC 9.2.1 sind dies 8 Byte mehr als bei GCC 10.1.1 . Wie Sie aus der sehen Auflistung werden mehrere unnötigen Befehle hinzugefügt, zum Beispiel vor dem Lesen zu Adresse ( LDR ) gibt es einen Add - Befehl ( ergänzt ), obwohl diese Befehle mit dem Lesen mit einem Versatz ersetzt werden. Die neue Version optimiert diese Vorgänge. Gleichzeitig generiert clang die gleichen Einträge.
Ergebnis
Die zu Beginn des Artikels festgelegten Ziele wurden erreicht - die Ausführungsgeschwindigkeit und -effizienz sind auf dem Niveau des direkten Schreibens in das Register geblieben, die Wahrscheinlichkeit eines Fehlers im Benutzercode wird minimiert.
Möglicherweise scheinen das Volumen des Quellcodes und die Komplexität der Entwicklung überflüssig zu sein. Dank einer solchen Anzahl von Abstraktionen ist der Übergang zu einem neuen Controller jedoch mit minimalem Aufwand verbunden: 30 Zeilen klarer Adaptercode + 5 Zeilen pro Peripherieeinheit.
Vollständiger Code
type_traits_custom.hpp
#ifndef _TYPE_TRAITS_CUSTOM_HPP
#define _TYPE_TRAITS_CUSTOM_HPP
#include <type_traits>
/*!
@file
@brief Traits for metaprogramming
*/
/*!
@brief Namespace for utils.
*/
namespace utils{
/*-----------------------------------Basic----------------------------------------*/
/*!
@brief Basic list of types
@tparam Types parameter pack
*/
template<typename... Types>
struct Typelist{};
/*!
@brief Basic list of values
@tparam Values parameter pack
*/
template<auto... Values>
struct Valuelist{};
/*------------------------------End of Basic--------------------------------------*/
/*----------------------------------Front-------------------------------------------
Description: Pop front type or value from list
using listOfTypes = Typelist<int, short, bool, unsigned>;
using listOfValues = Valuelist<1,2,3,4,5,6,1>;
|-----------------|--------------------|----------|
| Trait | Parameters | Result |
|-----------------|--------------------|----------|
| front_t | <listOfTypes> | int |
|-----------------|--------------------|----------|
| front_v | <listOfValues> | 1 |
|-----------------|--------------------|----------| */
namespace{
template<typename List>
struct front;
template<typename Head, typename... Tail>
struct front<Typelist<Head, Tail...>>{
using type = Head;
};
template<auto Head, auto... Tail>
struct front<Valuelist<Head, Tail...>> {
static constexpr auto value = Head;
};
}
template<typename List>
using front_t = typename front<List>::type;
template<typename List>
static constexpr auto front_v = front<List>::value;
/*----------------------------------End of Front----------------------------------*/
/*----------------------------------Pop_Front---------------------------------------
Description: Pop front type or value from list and return rest of the list
using listOfTypes = Typelist<int, short, bool>;
using listOfValues = Valuelist<1,2,3,4,5,6,1>;
|-----------------|--------------------|------------------------|
| Trait | Parameters | Result |
|-----------------|--------------------|------------------------|
| pop_front_t | <listOfTypes> | Typelist<short, bool> |
|-----------------|--------------------|------------------------|
| pop_front_t | <listOfValues> | Valuelist<2,3,4,5,6,1> |
|-----------------|--------------------|------------------------| */
namespace{
template<typename List>
struct pop_front;
template<typename Head, typename... Tail>
struct pop_front<Typelist<Head, Tail...>> {
using type = Typelist<Tail...>;
};
template<auto Head, auto... Tail>
struct pop_front<Valuelist<Head, Tail...>> {
using type = Valuelist<Tail...>;
};
}
template<typename List>
using pop_front_t = typename pop_front<List>::type;
/*------------------------------End of Pop_Front----------------------------------*/
/*----------------------------------Push_Front--------------------------------------
Description: Push new element to front of the list
using listOfTypes = Typelist<short, bool>;
|-----------------------|--------------------------|-------------------------------|
| Trait | Parameters | Result |
|-----------------------|--------------------------|-------------------------------|
| push_front_t | <listOfTypes, float> | Typelist<float, short, bool> |
|-----------------------|--------------------------|-------------------------------| */
namespace{
template<typename List, typename NewElement>
struct push_front;
template<typename... List, typename NewElement>
struct push_front<Typelist<List...>, NewElement> {
using type = Typelist<NewElement, List...>;
};
}
template<typename List, typename NewElement>
using push_front_t = typename push_front<List, NewElement>::type;
/*------------------------------End of Push_Front---------------------------------*/
/*----------------------------------Push_Back---------------------------------------
Description: Push new value to back of the list
using listOfValues = Valuelist<1,2,3,4,5,6>;
|-----------------------|--------------------------|-------------------------------|
| Trait | Parameters | Result |
|-----------------------|--------------------------|-------------------------------|
| push_back_value_t | <listOfValues, 0> | Valuelist<1,2,3,4,5,6,0> |
|-----------------------|--------------------------|-------------------------------| */
namespace{
template<typename List, auto NewElement>
struct push_back_value;
template<auto... List, auto NewElement>
struct push_back_value<Valuelist<List...>, NewElement>{
using type = Valuelist<List..., NewElement>;
};
}
template<typename List, auto NewElement>
using push_back_value_t = typename push_back_value<List, NewElement>::type;
/*----------------------------------End of Push_Back------------------------------*/
/*-----------------------------------Is_Empty---------------------------------------
Description: Check parameters list for empty and return bool value
using listOfTypes = Typelist<int, short, bool, unsigned>;
using listOfValues = Valuelist<>;
|-------------------------|--------------------|----------|
| Trait | Parameters | Result |
|-------------------------|--------------------|----------|
| is_empty_v | <listOfTypes> | false |
|-------------------------|--------------------|----------|
| is_empty_v | <listOfValues> | true |
|-------------------------|--------------------|----------| */
namespace{
/*!
@brief Check the emptiness of the types in parameters. \n
E.g.: is_empty<int, short, bool>::value;
*/
template<typename List>
struct is_empty{
static constexpr auto value = false;
};
/*!
@brief Check the emptiness of the types in parameter. Specializatio for empty parameters \n
E.g.: is_empty<>::value;
*/
template<>
struct is_empty<Typelist<>>{
static constexpr auto value = true;
};
template<>
struct is_empty<Valuelist<>>{
static constexpr auto value = true;
};
}
/*!
@brief Check the emptiness of the types-list in parameter. \n
E.g.: using list = Typelist<int, short, bool>; is_empty_v<list>;
*/
template<typename List>
static constexpr auto is_empty_v = is_empty<List>::value;
/*--------------------------------End of Is_Empty---------------------------------*/
/*---------------------------------Size_Of_List-------------------------------------
Description: Return number of elements in list
using listOfTypes = Typelist<int, float, double, bool>;
|------------------|--------------------|----------|
| Trait | Parameters | Result |
|------------------|--------------------|----------|
| size_of_list_v | listOfTypes | 4 |
|------------------|--------------------|----------| */
namespace{
template<typename List, std::size_t count = 0U>
struct size_of_list : public size_of_list<pop_front_t<List>, count + 1>{};
template<std::size_t count>
struct size_of_list<Typelist<>, count>{
static constexpr std::size_t value = count;
};
template<std::size_t count>
struct size_of_list<Valuelist<>, count>{
static constexpr std::size_t value = count;
};
}
template<typename List>
static constexpr std::size_t size_of_list_v = size_of_list<List>::value;
/*-------------------------------End Size_Of_List---------------------------------*/
/*---------------------------------Lists Operation--------------------------------*/
/*Description: Operations with lists of values
using list1 = Valuelist<1, 4, 8, 16>;
using list2 = Valuelist<1, 5, 96, 17>;
|------------------------------|-------------------|---------------------------|
| Trait | Parameters | Result |
|------------------------------|-------------------|---------------------------|
| lists_termwise_and_t | <list1, list2> | Valuelist<1, 4, 0, 16> |
|------------------------------|-------------------|---------------------------|
| lists_termwise_or_t | <list1, list2> | Valuelist<1, 5, 104, 17> |
|---------------------------- -|-------------------|---------------------------|
| lists_termwise_xor_t | <list1, list2> | Valuelist<0, 1, 104, 1> |
|------------------------------|-------------------|---------------------------| */
namespace{
template<template <auto value1, auto value2> typename operation,
typename List1, typename List2, typename Result = Valuelist<>>
struct operation_2_termwise_valuelists{
constexpr static auto newValue = operation<front_v<List1>, front_v<List2>>::value;
using nextList1 = pop_front_t<List1>;
using nextList2 = pop_front_t<List2>;
using result = push_back_value_t<Result, newValue>;
using type = typename
operation_2_termwise_valuelists<operation, nextList1, nextList2, result>::type;
};
template<template <auto value1, auto value2> typename operation, typename Result>
struct operation_2_termwise_valuelists<operation, Valuelist<>, Valuelist<>, Result>{
using type = Result;
};
template<template <auto value1, auto value2> typename operation,
typename List2, typename Result>
struct operation_2_termwise_valuelists<operation, Valuelist<>, List2, Result>{
using type = typename
operation_2_termwise_valuelists<operation, Valuelist<0>, List2, Result>::type;
};
template<template <auto value1, auto value2> typename operation,
typename List1, typename Result>
struct operation_2_termwise_valuelists<operation, List1, Valuelist<>, Result>{
using type = typename
operation_2_termwise_valuelists<operation, List1, Valuelist<0>, Result>::type;
};
template<template<typename first, typename second> class operation,
typename Lists, bool isEnd = size_of_list_v<Lists> == 1>
class lists_operation{
using first = front_t<Lists>;
using second = front_t<pop_front_t<Lists>>;
using next = pop_front_t<pop_front_t<Lists>>;
using result = operation<first, second>;
public:
using type = typename lists_operation<operation, push_front_t<next, result>>::type;
};
template<template<typename first, typename second> class operation,
typename Lists>
class lists_operation<operation, Lists, true>{
public:
using type = front_t<Lists>;
};
template<auto value1, auto value2>
struct and_operation{ static constexpr auto value = value1 & value2;};
template<auto value1, auto value2>
struct or_operation{ static constexpr auto value = value1 | value2;};
template<auto value1, auto value2>
struct xor_operation{ static constexpr auto value = value1 ^ value2;};
template<typename List1, typename List2>
using operation_and_termwise_t = typename
operation_2_termwise_valuelists<and_operation, List1, List2>::type;
template<typename List1, typename List2>
using operation_or_termwise_t = typename
operation_2_termwise_valuelists<or_operation, List1, List2>::type;
template<typename List1, typename List2>
using operation_xor_termwise_t = typename
operation_2_termwise_valuelists<xor_operation, List1, List2>::type;
}
template<typename... Lists>
using lists_termwise_and_t =
typename lists_operation<operation_and_termwise_t, Typelist<Lists...>>::type;
template<typename... Lists>
using lists_termwise_or_t = typename
lists_operation<operation_or_termwise_t, Typelist<Lists...>>::type;
template<typename... Lists>
using lists_termwise_xor_t = typename
lists_operation<operation_xor_termwise_t, Typelist<Lists...>>::type;
/*--------------------------------End of Lists Operation----------------------------*/
} // !namespace utils
#endif //!_TYPE_TRAITS_CUSTOM_HPP
IPower.hpp
#ifndef _IPOWER_HPP
#define _IPOWER_HPP
#include "type_traits_custom.hpp"
#define __FORCE_INLINE __attribute__((always_inline)) inline
/*!
@brief Controller's peripherals interfaces
*/
namespace controller::interfaces{
/*!
@brief Interface for Power(Clock control). Static class. CRT pattern
@tparam <adapter> class of specific controller
*/
template<typename adapter>
class IPower{
IPower() = delete;
public:
/*!
@brief Enables peripherals Power(Clock)
@tparam <Peripherals> list of peripherals with trait 'power'
*/
template<typename... Peripherals>
__FORCE_INLINE static void Enable(){
using tEnableList = utils::lists_termwise_or_t<typename Peripherals::power...>;
using tDisableList = typename adapter::template fromValues<>::power;
adapter:: template _Set<tEnableList, tDisableList>();
}
/*!
@brief Enables Power(Clock) except listed peripherals in 'ExceptList'.
If Enable = Exception = 1, then Enable = 0, otherwise depends on Enable.
@tparam <EnableList> list to enable, with trait 'power'
@tparam <ExceptList> list of exception, with trait 'power'
*/
template<typename EnableList, typename ExceptList>
__FORCE_INLINE static void EnableExcept(){
using tXORedList = utils::lists_termwise_xor_t<typename EnableList::power, typename ExceptList::power>;
using tEnableList = utils::lists_termwise_and_t<typename EnableList::power, tXORedList>;
using tDisableList = typename adapter::template fromValues<>::power;
adapter:: template _Set<tEnableList, tDisableList>();
}
/*!
@brief Disables peripherals Power(Clock)
@tparam <Peripherals> list of peripherals with trait 'power'
*/
template<typename... Peripherals>
__FORCE_INLINE static void Disable(){
using tDisableList = utils::lists_termwise_or_t<typename Peripherals::power...>;
using tEnableList = typename adapter::template fromValues<>::power;
adapter:: template _Set<tEnableList, tDisableList>();
}
/*!
@brief Disables Power(Clock) except listed peripherals in 'ExceptList'.
If Disable = Exception = 1, then Disable = 0, otherwise depends on Disable.
@tparam <DisableList> list to disable, with trait 'power'
@tparam <ExceptList> list of exception, with trait 'power'
*/
template<typename DisableList, typename ExceptList>
__FORCE_INLINE static void DisableExcept(){
using tXORedList = utils::lists_termwise_xor_t<typename DisableList::power, typename ExceptList::power>;
using tDisableList = utils::lists_termwise_and_t<typename DisableList::power, tXORedList>;
using tEnableList = typename adapter::template fromValues<>::power;
adapter:: template _Set<tEnableList, tDisableList>();
}
/*!
@brief Disable and Enables Power(Clock) depends on values.
If Enable = Disable = 1, then Enable = Disable = 0, otherwise depends on values
@tparam <EnableList> list to enable, with trait 'power'
@tparam <DisableList> list to disable, with trait 'power'
*/
template<typename EnableList, typename DisableList>
__FORCE_INLINE static void Keep(){
using tXORedList = utils::lists_termwise_xor_t<typename EnableList::power, typename DisableList::power>;
using tEnableList = utils::lists_termwise_and_t<typename EnableList::power, tXORedList>;
using tDisableList = utils::lists_termwise_and_t<typename DisableList::power, tXORedList>;
adapter:: template _Set<tEnableList, tDisableList>();
}
/*!
@brief Creates custom 'power' list from peripherals. Peripheral driver should implement 'power' trait.
E.g.: using power = Power::makeFromValues<1, 512, 8>::power;
@tparam <PeripheralsList> list of peripherals with trait 'power'
*/
template<typename... PeripheralsList>
class fromPeripherals{
fromPeripherals() = delete;
using power = utils::lists_termwise_or_t<typename PeripheralsList::power...>;
friend class IPower<adapter>;
};
};
} // !namespace controller::interfaces
#undef __FORCE_INLINE
#endif // !_IPOWER_HPP
HPower.hpp
#ifndef _HPOWER_HPP
#define _HPOWER_HPP
#include "type_traits_custom.hpp"
#define __FORCE_INLINE __attribute__((always_inline)) inline
/*!
@brief Hardware operations
*/
namespace controller::hardware{
/*!
@brief Implements hardware operations with Power(Clock) registers
*/
class HPower{
HPower() = delete;
protected:
/*!
@brief Set or Reset bits in the registers
@tparam <SetList> list of values to set
@tparam <ResetList> list of values to reset
@tparam <AddressesList> list of registers addresses to operate
*/
template<typename SetList, typename ResetList, typename AddressesList>
__FORCE_INLINE static void ModifyRegisters(){
using namespace utils;
if constexpr (!is_empty_v<SetList> && !is_empty_v<ResetList> &&
!is_empty_v<AddressesList>){
constexpr auto valueSet = front_v<SetList>;
constexpr auto valueReset = front_v<ResetList>;
if constexpr(valueSet || valueReset){
constexpr auto address = front_v<AddressesList>;
using pRegister_t = volatile std::remove_const_t<decltype(address)>* const;
auto& reg = *reinterpret_cast<pRegister_t>(address);
reg = (reg &(~valueReset)) | valueSet;
}
using tRestSet = pop_front_t<SetList>;
using tRestReset = pop_front_t<ResetList>;
using tRestAddress = pop_front_t<AddressesList>;
ModifyRegisters<tRestSet, tRestReset, tRestAddress>();
}
};
};
} // !namespace controller::hardware
#undef __FORCE_INLINE
#endif // !_HPOWER_HPP
stm32f1_Power.hpp
#ifndef _STM32F1_POWER_HPP
#define _STM32F1_POWER_HPP
#include <cstdint>
#include "IPower.hpp"
#include "HPower.hpp"
#include "type_traits_custom.hpp"
#define __FORCE_INLINE __attribute__((always_inline)) inline
/*!
@brief Controller's peripherals
*/
namespace controller{
/*!
@brief Power managment for controller
*/
class Power: public interfaces::IPower<Power>, public hardware::HPower{
Power() = delete;
public:
/*!
@brief Creates custom 'power' list from values. Peripheral driver should implement 'power' trait.
E.g.: using power = Power::fromValues<1, 512, 8>::power;
@tparam <valueAHB=0> value for AHBENR register
@tparam <valueAPB1=0> value for APB1ENR register
@tparam <valueAPB2=0> value for APB1ENR register
*/
template<uint32_t valueAHBENR = 0, uint32_t valueAPB1ENR = 0, uint32_t valueAPB2ENR = 0>
struct fromValues{
fromValues() = delete;
using power = utils::Valuelist<valueAHBENR, valueAPB1ENR, valueAPB2ENR>;
};
private:
static constexpr uint32_t
_addressAHBENR = 0x40021014,
_addressAPB2ENR = 0x40021018,
_addressAPB1ENR = 0x4002101C;
using AddressesList = utils::Valuelist<_addressAHBENR, _addressAPB1ENR, _addressAPB2ENR>;
template<typename EnableList, typename DisableList>
__FORCE_INLINE static void _Set(){
HPower:: template ModifyRegisters<EnableList, DisableList, AddressesList>();
}
friend class IPower<Power>;
};
} // !namespace controller
#undef __FORCE_INLINE
#endif // !_STM32F1_POWER_HPP
stm32f1_SPI.hpp
#ifndef _STM32F1_SPI_HPP
#define _STM32F1_SPI_HPP
#include "stm32f1_Power.hpp"
namespace controller{
template<auto baseAddress>
class SPI{
static const uint32_t RCC_AHBENR_DMA1EN = 1;
static const uint32_t RCC_APB2ENR_IOPBEN = 8;
static const uint32_t RCC_APB1ENR_SPI2EN = 0x4000;
/*!
@brief Trait for using in Power class. Consists of Valueslist with
values for AHBENR, APB1ENR, APB2ENR registers
*/
using power = Power::fromValues<
RCC_AHBENR_DMA1EN,
RCC_APB1ENR_SPI2EN,
RCC_APB2ENR_IOPBEN>::power;
template<typename>
friend class interfaces::IPower;
};
}
#endif // !_STM32F1_SPI_HPP
stm32f1_UART.hpp
#ifndef _STM32F1_UART_HPP
#define _STM32F1_UART_HPP
#include "stm32f1_Power.hpp"
namespace controller{
template<auto baseAddress>
class UART{
static const uint32_t RCC_AHBENR_DMA1EN = 1;
static const uint32_t RCC_APB2ENR_IOPAEN = 4;
static const uint32_t RCC_APB2ENR_USART1EN = 0x4000;
/*!
@brief Trait for using in Power class. Consists of Valueslist with
values for AHBENR, APB1ENR, APB2ENR registers
*/
using power = Power::fromValues<
RCC_AHBENR_DMA1EN,
0U,
RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN>::power;
template<typename>
friend class interfaces::IPower;
};
}
#endif // !_STM32F1_UART_HPP
main.cpp
#include "stm32f1_Power.hpp"
#include "stm32f1_UART.hpp"
#include "stm32f1_SPI.hpp"
using namespace controller;
using spi = SPI<2>;
using uart = UART<1>;
using listPowerInit = Power::fromPeripherals<spi, uart>;
using listPowerDown = Power::fromPeripherals<spi>;
using listPowerWake = Power::fromPeripherals<uart>;
int main(){
Power::Enable<listPowerInit>();
//Some code
Power::DisableExcept<listPowerDown, listPowerWake>();
//Sleep();
Power::EnableExcept<listPowerDown, listPowerWake>();
while(1);
return 1;
};
Github