Seberapa sering, saat mengembangkan firmware untuk mikrokontroler, selama debugging, saat byte tidak berjalan di UART, Anda berseru: “Ahh, tepatnya! Tidak mengaktifkan pencatatan jam kerja! " Atau, saat Anda mengganti kaki LED, apakah Anda lupa "memberikan daya" ke port baru? Cukup sering, menurut saya. Saya, setidaknya - pasti.
Pada pandangan pertama, tampaknya mengontrol waktu periferal itu sepele: tulis 1 - diaktifkan, 0 - nonaktif.
Tetapi "sederhana" tidak selalu efektif ...
Rumusan masalah
Sebelum menulis kode, perlu untuk menentukan kriteria yang dapat dievaluasi. Dalam kasus sistem pencatatan jam kerja periferal pengontrol, daftarnya mungkin terlihat seperti ini:
- Dalam sistem tertanam, salah satu kriteria terpenting adalah kode yang dihasilkan sekecil mungkin, dijalankan dalam waktu sesingkat mungkin.
- . - code review , /
- , ,
- ( )
Setelah mengklarifikasi kriteria evaluasi, kami akan menetapkan tugas khusus, di sepanjang jalan menentukan kondisi dan "lingkungan" untuk implementasi:
Compiler: GCC 10.1.1 + Make
Language: C ++ 17
Environment: Visual Studio Code
Controller: stm32f103c8t6 (cortex-m3)
Tugas: aktifkan clocking SPI2, USART1 (kedua antarmuka menggunakan DMA)
Pilihan pengontrol ini tentu saja disebabkan oleh prevalensinya, terutama berkat salah satu kerajinan rakyat Tiongkok - produksi papan Pil Biru.
Dari sudut pandang ideologis, tidak masalah pengontrol mana yang dipilih: stmf1, stmf4 atau lpc, karena bekerja dengan sistem jam periferal direduksi hanya untuk menulis ke bit tertentu baik 0 untuk mati, atau 1 untuk hidup.
Di stm32f103c8t6 ada 3 register yang bertanggung jawab untuk mengaktifkan pencatatan jam kerja periferal: AHBENR, APB1ENR, APB2ENR.
Antarmuka perangkat keras untuk transfer data SPI2 dan USART1 tidak dipilih secara kebetulan, karena agar berfungsi penuh, bit jam perlu diaktifkan di semua register yang terdaftar - bit antarmuka itu sendiri, DMA1, serta bit port input-output (GPIOB untuk SPI2 dan GPIOA untuk USART1).
Perlu dicatat bahwa untuk kinerja yang optimal dengan pencatatan jam kerja, perlu dipertimbangkan - AHBENR berisi sumber daya bersama yang digunakan untuk memfungsikan SPI2 dan USART1. Artinya, menonaktifkan DMA akan segera menyebabkan ketidakmampuan kedua antarmuka, pada saat yang sama, efisiensi penutupan bahkan tidak akan menjadi nol, tetapi negatif, karena operasi ini akan mengambil memori program dan akan menyebabkan konsumsi jam tambahan untuk membaca-mengubah-menulis register volatil.
Setelah membahas tujuan, kondisi, dan fitur masalah, mari kita lanjutkan untuk mencari solusi.
Pendekatan dasar
Bagian ini berisi cara-cara umum untuk mengaktifkan pencatatan jam kerja periferal yang saya temukan dan, tentunya, Anda juga telah melihat dan / atau menggunakannya. Dari yang lebih sederhana, diimplementasikan di C, hingga ekspresi lipat dari C ++ 17. Keuntungan dan kerugian yang melekat dipertimbangkan.
Jika Anda ingin langsung membuka metaprogramming, Anda dapat melewati bagian ini dan melanjutkan ke bagian berikutnya .
Menulis langsung ke register
Cara klasik, "tersedia di luar kotak" untuk C dan C ++. Vendor, paling sering, menyediakan file header untuk pengontrol, di mana semua register dan bitnya disetel default, yang memungkinkan untuk segera mulai bekerja dengan periferal:
int main(){
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN
| RCC_APB2ENR_IOPBEN
| RCC_APB2ENR_USART1EN;
RCC->APB2ENR |= RCC_APB1ENR_SPI2EN;
…
}
Daftar
// 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]
Ukuran kode: 36 byte. Lihat
Pro:
- Ukuran kode minimum dan kecepatan eksekusi
- Cara termudah dan paling jelas
Minus:
- Penting untuk mengingat nama register dan nama bit, atau selalu mengacu pada manual
- Sangat mudah untuk membuat kesalahan dalam kode Anda. Pembaca pasti telah memperhatikan bahwa alih-alih SPI2, USART1 diaktifkan kembali.
- Agar beberapa unit periferal berfungsi, Anda juga perlu mengaktifkan periferal lain, seperti GPIO dan DMA untuk antarmuka
- Kurangnya portabilitas. Saat memilih pengontrol yang berbeda, kode ini kehilangan artinya
Terlepas dari semua kekurangannya, metode ini tetap sangat populer, setidaknya saat Anda perlu "merasakan" pengontrol baru dengan
Fungsi inisialisasi
Mari kita mencoba mengabstraksi dan menyembunyikan pekerjaan dengan register dari pengguna. Dan fungsi C biasa akan membantu kita dalam hal ini:
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();
…
}
Ukuran kode: 72 byte. Lihat
Daftar
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()
Kelebihan:
- Anda tidak perlu melihat manual untuk setiap kesempatan.
- Kesalahan dilokalkan pada tahap penulisan driver periferal
- Kode kustom mudah dibaca
Minus:
- Jumlah instruksi yang dibutuhkan telah meningkat dalam kelipatan dari jumlah periferal yang terlibat
- Banyak duplikasi kode - untuk setiap nomor UART dan SPI akan hampir identik
Meskipun kami menyingkirkan penulisan langsung ke register dalam kode pengguna, berapa biayanya? Ukuran memori yang diperlukan dan waktu eksekusi untuk menyalakan menjadi dua kali lipat dan akan terus bertambah, dengan lebih banyak periferal yang terlibat.
Fungsi pengaktifan jam
Mari kita bungkus modifikasi jam dalam fungsi terpisah, dengan asumsi bahwa ini akan mengurangi jumlah memori yang dibutuhkan. Pada saat yang sama, kami akan memperkenalkan parameter pengenal untuk periferal - untuk mengurangi kode driver:
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);
…
}
Ukuran kode: 92 byte. Lihat
Daftar
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)
Kelebihan:
- Berhasil mempersingkat kode deskripsi untuk driver mikrokontroler
- Jumlah instruksi yang dihasilkan berkurang *
Minus:
- Peningkatan waktu eksekusi
* Ya, dalam hal ini ukuran kode yang dapat dieksekusi telah meningkat dibandingkan dengan versi sebelumnya, tetapi ini disebabkan oleh munculnya operator bersyarat, pengaruhnya dapat dinetralkan jika setidaknya 2 salinan dari setiap jenis perangkat digunakan.
Karena fungsi include mengambil parameter, kemudian operasi tumpukan muncul di assembler, yang juga berdampak negatif pada kinerja.
Pada titik ini, saya pikir
Nilai properti dan template
Mulai mempertimbangkan pendekatan positif, kita akan segera melewatkan opsi untuk memasukkan pencatatan jam kerja dalam konstruktor kelas, karena metode ini sebenarnya tidak berbeda dengan fungsi inisialisasi gaya C.
Karena pada waktu kompilasi kita mengetahui semua nilai yang perlu ditulis ke register, kita akan menyingkirkan operasi stack. Untuk melakukan ini, kita akan membuat kelas terpisah dengan metode templat, dan kita akan memberikan kelas periferal dengan properti (sifat nilai) yang akan menyimpan nilai untuk register yang sesuai.
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
>();
…
}
Ukuran kode: 36 byte. Lihat
Daftar
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]
Kelebihan:
- Ukuran dan waktu eksekusi ternyata sama dengan versi referensi dengan penulisan langsung ke register
- Sangat mudah untuk menskalakan proyek - cukup tambahkan
air yangsesuai dengan nilai properti dari pinggiran
Minus:
- Mungkin saja membuat kesalahan dengan meletakkan properti nilai di parameter yang salah
- Seperti dalam kasus penulisan langsung ke register - portabilitas menderita
- Konstruksi berlebih
Kami dapat mencapai beberapa tujuan yang ditetapkan, tetapi apakah nyaman untuk menggunakannya? Saya kira tidak, karena untuk menambahkan blok periferal lain, perlu untuk mengontrol pengaturan properti kelas yang benar dalam parameter template metode.
Ideal ... hampir
Untuk mengurangi jumlah kode khusus dan peluang kesalahan, kami akan menggunakan paket parameter, yang akan menghapus akses ke properti kelas periferal di kode khusus. Ini hanya akan mengubah metode untuk mengaktifkan pencatatan jam kerja:
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>();
…
}
Ukuran kode: 36 byte. Lihat
Daftar
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]
Dibandingkan dengan versi sebelumnya, kesederhanaan kode pengguna telah meningkat secara signifikan, kemungkinan kesalahan menjadi minimal, dan konsumsi memori tetap pada level yang sama.
Dan, tampaknya, Anda bisa berhenti di sini, tapi ...
Memperluas fungsionalitas
Mari beralih ke salah satu tujuan kita:
Selain kemampuan dasar untuk mengaktifkan dan menonaktifkan pencatatan jam kerja periferal, diperlukan fungsionalitas lanjutan
Misalkan tugasnya adalah membuat perangkat berdaya rendah, dan untuk ini, tentu saja, diperlukan untuk mematikan semua periferal yang tidak digunakan pengontrol untuk keluar dari mode hemat daya.
Dalam konteks kondisi yang disuarakan di awal artikel, kami akan mengasumsikan bahwa USART1 akan menjadi generator acara bangun, dan SPI2 serta port GPIOB yang sesuai harus dinonaktifkan. Dalam kasus ini, sumber daya bersama DMA1 harus tetap diaktifkan.
Menggunakan opsi apa pun dari bagian sebelumnya, tidak mungkin menyelesaikan masalah ini secara efisien dan optimal, dan, pada saat yang sama, tanpa menggunakan kontrol manual dari blok yang terlibat.
Misalnya, ambil cara terakhir:
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>();
…
}
Ukuran kode: 100 byte. Lihat
Daftar
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]
Pada saat yang sama, kode referensi dalam register membutuhkan 68 byte. Lihat
Jelas, untuk tugas-tugas seperti itu, batu sandungan akan berbagi sumber daya seperti DMA. Selain itu, dalam kasus khusus ini, akan ada momen ketika kedua antarmuka menjadi tidak dapat beroperasi dan, pada kenyataannya, situasi darurat akan terjadi.
Mari coba cari solusinya ...
Struktur
Untuk menyederhanakan pemahaman dan pengembangan, kami akan menggambarkan struktur pencatatan jam kerja umum seperti yang kami inginkan:
Ini hanya terdiri dari empat blok:
Independen:
- IPower - antarmuka pengguna yang menyiapkan data untuk menulis ke register
- Perangkat keras - menulis nilai ke register pengontrol
Bergantung pada perangkat keras:
- Periferal - periferal yang digunakan dalam proyek dan memberi tahu antarmuka perangkat mana yang harus dihidupkan atau dimatikan
- Adaptor - mentransfer nilai yang akan ditulis ke Perangkat Keras, menunjukkan di register mana nilai tersebut harus ditulis
Antarmuka IPower
Dengan mempertimbangkan semua persyaratan, kami akan menentukan metode yang diperlukan di antarmuka:
template<typename… Peripherals>
Enable();
template<typename EnableList, typename ExceptList>
EnableExcept();
template<typename EnableList, typename DisableList>
Keep();
Aktif - mengaktifkan periferal yang ditentukan dalam parameter template.
EnableExcept - aktifkan periferal yang ditentukan dalam parameter EnableList, kecuali yang ditentukan di ExcualList.
Penjelasan
, :
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
Ini dilengkapi dengan metode Disable komplementer yang melakukan hal sebaliknya.
Keep - mengaktifkan periferal dari EnableList, menonaktifkan periferal dari DisableList, dan jika periferal ada di kedua daftar, statusnya tetap dipertahankan.
Penjelasan
, :
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
Metode on / off telah diterapkan dengan cukup baik dengan ekspresi lipatan, tapi bagaimana dengan yang lainnya?
Jika kita membatasi diri untuk menggunakan 2 jenis pinggiran, seperti yang dilakukan dalam penjelasan, maka tidak akan ada kesulitan yang muncul. Namun, ketika banyak perangkat periferal yang berbeda digunakan dalam sebuah proyek, masalah muncul - Anda tidak dapat secara eksplisit menggunakan lebih dari satu paket parameter dalam sebuah templat, karena kompilator tidak akan dapat menentukan di mana yang satu berakhir dan yang kedua dimulai:
template<typename… EnableList, typename… ExceptList>
EnableExcept(){…};
// EnableList ExceptList
EnableExcept<spi2, pin3, uart1, pin1, i2c3>();
Dimungkinkan untuk membuat kelas pembungkus terpisah untuk pinggiran dan meneruskannya ke metode:
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>();
Tetapi dalam kasus ini, antarmuka akan menjadi terikat secara kaku dengan jumlah register, oleh karena itu, untuk setiap jenis pengontrol akan menjadi perlu untuk menulis kelasnya sendiri yang terpisah, dengan banyak operasi dari jenis yang sama dan tanpa kemungkinan membagi menjadi lapisan abstrak.
Karena semua periferal dan register jam yang digunakan diketahui pada tahap kompilasi, tugas tersebut dapat diselesaikan menggunakan metaprogramming.
Metaprogramming
Karena fakta bahwa metaprogramming didasarkan pada pekerjaan bukan dengan tipe biasa, tetapi dengan daftarnya, kami akan mendefinisikan dua entitas yang akan beroperasi dengan parameter tipikal dan non-tipikal:
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
Sebelum melakukan sesuatu yang berguna dengan daftar ini, kita perlu menerapkan beberapa operasi dasar yang memungkinkan dilakukannya tindakan yang lebih kompleks.
1. Mengambil item pertama dari daftar
depan
//
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. Menghapus item pertama dari daftar
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. Menambahkan item ke awal daftar
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. Menambahkan parameter non-standar ke akhir daftar
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. Memeriksa kekosongan daftar
kosong
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. Menemukan jumlah item dalam daftar
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
Sekarang semua tindakan dasar telah ditentukan, Anda dapat melanjutkan ke penulisan metafungsi untuk operasi bitwise: atau , dan , xor , yang diperlukan untuk metode antarmuka.
Karena transformasi bit ini berjenis sama, kami akan mencoba membuat penerapannya seumum mungkin untuk menghindari duplikasi kode.
Fungsi yang melakukan operasi abstrak pada daftar
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).
Selanjutnya, kami akan menerapkan operasi untuk metafungsi sebelumnya, yang akan melakukan tindakan abstrak istilah per istilah pada parameter atipikal dari dua daftar:
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) , .
Fungsi operasi bit:
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;};
Tetap membuat alias agar lebih mudah digunakan:
alias
( ).
// 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;
( ).
Kembali ke implementasi antarmuka
Karena pengontrol dan periferal yang digunakan sudah diketahui pada tahap kompilasi, pilihan logis untuk mengimplementasikan antarmuka adalah polimorfisme statis dengan idiom CRTP . Sebagai parameter template, antarmuka menerima kelas adaptor dari pengontrol tertentu, yang, pada gilirannya, mewarisi dari antarmuka ini.
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...>;
};
};
Selain itu, antarmuka berisi kelas fromPeripherals bawaan yang memungkinkan Anda menggabungkan periferal menjadi satu daftar, yang kemudian dapat digunakan dalam metode:
using listPower = Power::fromPeripherals<spi, uart>;
Power::Enable<listPower>();
Metode Nonaktifkan diterapkan serupa.
Adaptor pengontrol
Di kelas adaptor, Anda perlu menyetel alamat register jam dan menentukan urutan penulisannya, lalu mentransfer kontrol langsung ke kelas tersebut, yang akan menyetel atau menghapus bit dari register yang ditunjukkan.
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>;
};
};
Keliling
Kami memberikan periferal dengan properti daya menggunakan struktur fromValues adaptor:
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;
};
Menulis ke register
Kelas terdiri dari metode templat rekursif yang tugasnya adalah menulis nilai ke register pengontrol yang diteruskan oleh adaptor.
Metode ini menerima 3 daftar parameter <…> Valuelist non-tipikal sebagai parameter :
- SetList dan ResetList - daftar urutan nilai bit yang akan diatur / diatur ulang dalam register
- AddressesList - daftar alamat register yang nilai-nilai dari parameter sebelumnya akan ditulis
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>();
}
};
};
Kelas berisi satu-satunya baris kode yang akan muncul di daftar perakitan.
Sekarang semua blok struktur sudah siap, mari kita lanjutkan ke pengujian.
Menguji kode
Mari kita mengingat kembali kondisi masalah terakhir:
- Mengaktifkan SPI2 dan USART1
- Mematikan SPI2 sebelum memasuki "mode hemat daya"
- Mengaktifkan SPI2 setelah keluar dari "mode hemat daya"
//
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>();
…
}
Ukuran kode: 68 byte *, seperti dalam kasus penulisan langsung ke register.
Daftar
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]
* Menggunakan GCC 9.2.1, ini 8 byte lebih banyak dari GCC 10.1.1 . Seperti yang Anda lihat dari daftar , beberapa instruksi yang tidak perlu ditambahkan, misalnya, sebelum membaca ke alamat ( ldr ) ada instruksi tambah ( tambah ), meskipun instruksi ini dapat diganti dengan membaca dengan offset. Versi baru mengoptimalkan operasi ini. Pada saat yang sama, dentang menghasilkan daftar yang sama.
Hasil
Tujuan yang ditetapkan di awal artikel telah tercapai - kecepatan dan efisiensi eksekusi tetap pada tingkat penulisan langsung ke register, kemungkinan kesalahan dalam kode pengguna diminimalkan.
Mungkin volume kode sumber dan kompleksitas pengembangan akan tampak berlebihan, namun, berkat sejumlah abstraksi, transisi ke pengontrol baru akan membutuhkan upaya minimal: 30 baris kode adaptor yang jelas + 5 baris per unit periferal.
Kode lengkap
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