How often, when developing firmware for a microcontroller, during debugging, when the bytes are not running on the UART, you exclaim: βAhh, exactly! Didn't enable clocking! " Or, when you changed the LED leg, did you forget to "apply power" to the new port? Quite often, I think. I, at least - for sure.
At first glance, it may seem that controlling the timing of the periphery is trivial: wrote 1 - enabled, 0 - disabled.
But βsimpleβ is not always effective ...
β
Formulation of the problem
Before writing the code, it is necessary to determine the criteria by which it can be evaluated. In the case of the controller peripheral clocking system, the list may look like this:
- In embedded systems, one of the most important criteria is the smallest possible resulting code, executed in the shortest possible time.
- . - code review , /
- , ,
- ( )
After clarifying the evaluation criteria, we will set a specific task, along the way defining the conditions and "environment" for implementation:
Compiler: GCC 10.1.1 + Make
Language: C ++ 17
Environment: Visual Studio Code
Controller: stm32f103c8t6 (cortex-m3)
Task: enable clocking SPI2, USART1 (both interfaces using DMA)
The choice of this controller is due, of course, to its prevalence, especially thanks to one of the Chinese folk crafts - the production of Blue Pill boards.
From the point of view of ideology, it doesn't matter which controller is chosen: stmf1, stmf4 or lpc, since working with the peripheral clock system is reduced only to writing to a certain bit either 0 for turning off, or 1 for turning on.
In stm32f103c8t6 there are 3 registers that are responsible for enabling peripheral clocking: AHBENR, APB1ENR, APB2ENR.
The hardware interfaces for data transfer SPI2 and USART1 were not chosen by chance, because for their full functioning, it is necessary to enable the clock bits located in all the listed registers - the bits of the interfaces themselves, DMA1, as well as the bits of the input-output ports (GPIOB for SPI2 and GPIOA for USART1).
It should be noted that for optimal performance with clocking, you must take into account - AHBENR contains a shared resource used for the functioning of both SPI2 and USART1. That is, disabling DMA will immediately lead to the inoperability of both interfaces, at the same time, the reclosing efficiency will not even be zero, but negative, because this operation will occupy the program memory and will lead to additional clock consumption for reading-modify-writing a volatile register.
Having dealt with the goals, conditions and features of the problem, let's move on to finding solutions.
β
Basic approaches
This section contains typical ways to enable peripheral clocking that I have come across and, for sure, you have also seen and / or used them. From simpler ones, implemented in C, to fold expression from C ++ 17. Their inherent advantages and disadvantages are considered.
If you want to go directly to metaprogramming, then you can skip this section and go to the next .
Direct writing to registers
The classic way, "available out of the box" for both C and C ++. The vendor, most often, provides header files for the controller, in which all registers and their bits are defaulted, which makes it possible to immediately start working with peripherals:
int main(){
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN
| RCC_APB2ENR_IOPBEN
| RCC_APB2ENR_USART1EN;
RCC->APB2ENR |= RCC_APB1ENR_SPI2EN;
β¦
}
Listing
// 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]
Code size: 36 bytes. View
Pros:
- Minimum code size and execution speed
- The easiest and most obvious way
Minuses:
- It is necessary to remember the names of the registers and the names of the bits, or constantly refer to the manual
- It's easy to make a mistake in your code. The reader probably noticed that instead of SPI2, USART1 was re-enabled.
- For some peripheral units to work, you also need to enable other peripherals, such as GPIO and DMA for interfaces
- Complete lack of portability. When choosing a different controller, this code loses its meaning
With all the shortcomings, this method remains very popular, at least when you need to "feel" the new controller by
Initialization functions
Let's try to abstract and hide the work with registers from the user. And an ordinary C function will help us with this:
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();
β¦
}
Code size: 72 bytes. Look
Listing
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()
Pros:
- You don't have to look at the manual for every occasion.
- Errors are localized at the stage of writing a peripheral driver
- Custom code is easy to read
Minuses:
- The number of required instructions has increased in multiples of the number of peripherals involved
- A lot of code duplication - for each UART and SPI number it will be virtually identical
Although we got rid of direct writing to registers in user code, at what cost? The required memory size and execution time for turning on have doubled and will continue to grow, with more peripherals involved.
Clock enable function
Let's wrap the modification of the clocks in a separate function, assuming that this will reduce the amount of memory required. At the same time, we will introduce an identifier parameter for the peripherals - to reduce the driver code:
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);
β¦
}
Code size: 92 bytes. Look
Listing
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)
Pros:
- It was possible to shorten the description code of microcontroller drivers
- The resulting number of instructions decreased *
Minuses:
- Increased execution time
* Yes, in this case the size of the executable code has increased compared to the previous version, but this is due to the appearance of conditional operators, the influence of which can be neutralized if at least 2 copies of each type of periphery are used.
Because the include function accepts parameters, then stack operations appeared in the assembler, which also negatively affects performance.
At this point, I think that
β
Value properties and templates
Starting to consider the positive approach, we will immediately skip the option of including clocking in the class constructor, since this method is actually no different from C-style initializing functions.
Since at compile time we know all the values ββthat need to be written to registers, we will get rid of stack operations. To do this, we will create a separate class with a template method, and endow the peripheral classes with properties (value trait) that will store values ββfor the corresponding registers.
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
>();
β¦
}
Code size: 36 bytes. Look
Listing
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]
Pros:
- The size and execution time turned out to be the same as in the reference version with direct writing to registers
- It is quite easy to scale the project - it is enough to add
watercorresponding property-value of the periphery
Minuses:
- It is possible to make a mistake by putting the value property in the wrong parameter
- As with direct writing to registers - portability suffers
- Construction overload
We were able to achieve several set goals, but is it convenient to use it? I think not, because in order to add another block of peripherals, it is necessary to control the correct arrangement of class properties in the parameters of the method template.
Ideal ... almost
To reduce the amount of custom code and opportunities for errors, we will use the parameter pack, which will remove access to the properties of the peripheral classes in the custom code. This will only change the method for enabling clocking:
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>();
β¦
}
Code size: 36 bytes. Look
Listing
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]
Compared to the previous version, the simplicity of the user code has significantly increased, the probability of errors has become minimal, and the memory consumption has remained at the same level.
And, it seems, you can stop at this, but ...
β
Expanding functionality
Let's turn to one of our goals:
In addition to the basic capabilities of enabling and disabling peripheral clocking, advanced functionality is required
Suppose that the task is to make the device low-power, and for this, of course, it is required to turn off all the peripherals that the controller does not use to exit the power-saving mode.
In the context of the conditions voiced at the beginning of the article, we will assume that the generator of the wakeup event will be USART1, and SPI2 and the corresponding GPIOB port must be disabled. In this case, the shared resource DMA1 must remain enabled.
Using any option from the previous section, it will not be possible to solve this problem both efficiently and optimally, and, at the same time, without using manual control of the blocks involved.
For example, let's take the last way:
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>();
β¦
}
Code size: 100 bytes. Look
Listing
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]
At the same time, the reference code in the registers took 68 bytes. View
Obviously, for such tasks, the stumbling block will be shared resources such as DMA. In addition, in this particular case, there will be a moment when both interfaces become inoperable and, in fact, an emergency situation will occur.
Let's try to find a solution ...
Structure
To simplify understanding and development, we will depict the general clocking structure as we want it to be:
It consists of only four blocks:
Independent:
- IPower - a user interface that prepares data for writing to registers
- Hardware - writing values ββto controller registers
Hardware-dependent:
- Peripherals - peripherals that are used in the project and tell the interface which devices to turn on or off
- Adapter - transfers values ββto be written to Hardware, indicating in which registers they should be written
IPower interface
Taking into account all the requirements, we will define the methods required in the interface:
template<typename⦠Peripherals>
Enable();
template<typename EnableList, typename ExceptList>
EnableExcept();
template<typename EnableList, typename DisableList>
Keep();
Enable - enables the peripherals specified in the template parameter.
EnableExcept - enable peripherals specified in the EnableList parameter, except for those specified in ExceptList.
Explanation
, :
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
These are complemented by complementary Disable methods that do the opposite.
Keep - enable peripherals from EnableList, disable peripherals from DisableList, while if peripherals are present in both lists, then they retain their state.
Explanation
, :
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
The on / off methods have already been implemented pretty well with fold expression, but what about the rest?
If we restrict ourselves to using 2 types of periphery, as is done in the explanation, then no difficulties will arise. However, when a project uses many different peripheral devices, a problem arises - you cannot explicitly use more than one parameter pack in a template, since the compiler will not be able to determine where one ends and the second begins:
template<typename⦠EnableList, typename⦠ExceptList>
EnableExcept(){β¦};
// EnableList ExceptList
EnableExcept<spi2, pin3, uart1, pin1, i2c3>();
It would be possible to create a separate wrapper class for the periphery and pass it to the method:
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>();
But in this case, the interface will become rigidly tied to the number of registers, therefore, for each type of controller it will become necessary to write its own separate class, with many operations of the same type and without the possibility of division into abstract layers.
Since all the peripherals and clock registers used are known at the compilation stage, the task can be solved using metaprogramming.
β
Metaprogramming
Due to the fact that metaprogramming is based on work not with ordinary types, but with their lists, we will define two entities that will operate with typical and non-typical parameters:
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
Before doing anything useful with these lists, we need to implement some basic operations that will make it possible to perform more complex actions.
1. Retrieving the first item from the list
front
//
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. Removing the first item from the list
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. Adding an item to the beginning of the list
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. Adding a non-standard parameter to the end of the list
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. Checking the list for emptiness
is_empty
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. Finding the number of items in the list
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
Now that all the basic actions are defined, you can move on to writing metafunctions for bitwise operations: or , and , xor , which are required for interface methods.
Since these bit transformations are of the same type, we will try to make the implementation as general as possible in order to avoid code duplication.
A function that performs an abstract operation on a list
lists_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).
Next, we will implement the operation for the previous metafunction, which will perform term-by-term abstract actions on atypical parameters from two lists:
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) , .
Bit operation functions:
bitwise_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;};
It remains to create aliases for easier use:
aliases
( ).
// 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;
( ).
Returning to the implementation of the interface
Since both the controller and the peripherals used are known at the compilation stage, the logical choice for implementing the interface is static polymorphism with the CRTP idiom . As a template parameter, the interface accepts an adapter class of a specific controller, which, in turn, inherits from this interface.
template<typename adapter>
struct IPower{
template<typename... Peripherals>
static void Enable(){
// , βpowerβ
//
using tEnableList = lists_termwise_or_t<typename Peripherals::power...>;
// Valuelist<β¦>, 0,
//
using tDisableList = typename adapter::template fromValues<>::power;
// /
adapter:: template _Set<tEnableList , tDisableList>();
}
template<typename EnableList, typename ExceptList>
static void EnableExcept(){
using tXORedList = lists_termwise_xor_t <
typename EnableList::power, typename ExceptList::power>;
using tEnableList = lists_termwise_and_t <
typename EnableList::power, tXORedList>;
using tDisableList = typename adapter::template fromValues<>::power;
adapter:: template _Set<tEnableList , tDisableList>();
}
template<typename EnableList, typename DisableList>
static void Keep(){
using tXORedList = lists_termwise_xor_t <
typename EnableList::power, typename DisableList::power>;
using tEnableList = lists_termwise_and_t <
typename EnableList::power, tXORedList>;
using tDisableList = lists_termwise_and_t <
typename DisableList::power, tXORedList>;
adapter:: template _Set<tEnableList , tDisableList>();
}
template<typename... PeripheralsList>
struct fromPeripherals{
using power = lists_termwise_or_t<typename PeripheralsList::power...>;
};
};
Also, the interface contains a built-in fromPeripherals class that allows you to combine peripherals into one list, which can then be used in methods:
using listPower = Power::fromPeripherals<spi, uart>;
Power::Enable<listPower>();
Disable methods are similarly implemented.
Controller adapter
In the adapter class, you need to set the addresses of the clocking registers and determine the sequence in which to write to them, and then transfer control directly to the class, which will set or clear the bits of the indicated registers.
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>;
};
};
β
Periphery
We endow the periphery with a power property using the adapter's fromValues structure :
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;
};
β
Writing to registers
The class consists of a recursive template method whose task is to write values ββto the controller registers passed by the adapter.
The method accepts 3 lists of non-typical Valuelist <β¦> parameters as parameters :
- SetList and ResetList - lists of sequences of bit values ββto be set / reset in a register
- AddressesList - a list of register addresses to which the values ββfrom the previous parameters will be written
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>();
}
};
};
The class contains the only line of code that will appear in the assembly listing.
Now that all the blocks of the structure are ready, let's move on to testing.
β
Testing the code
Let us recall the conditions of the last problem:
- Enabling SPI2 and USART1
- Turning off SPI2 before entering "power saving mode"
- Enabling SPI2 after exiting "power saving mode"
//
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>();
β¦
}
Code size: 68 bytes *, as in the case of direct writing to registers.
Listing
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]
* Using GCC 9.2.1, this is 8 bytes more than GCC 10.1.1 . As you can see from the listing , several unnecessary instructions are added, for example, before reading to address ( ldr ) there is an add instruction ( adds ), although these instructions can be replaced with reading with an offset. The new version optimizes these operations. At the same time clang generates the same listings.
Outcome
The goals set at the beginning of the article have been achieved - the execution speed and efficiency have remained at the level of direct writing to the register, the probability of an error in the user code is minimized.
Perhaps the volume of the source code and the complexity of development will seem redundant, however, thanks to such a number of abstractions, the transition to a new controller will take a minimum of effort: 30 lines of understandable adapter code + 5 lines per peripheral unit.
Complete 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