Fig. diambil dari www.extremetech.com/wp-content/uploads/2016/07/MegaProcessor-Feature.jpgKesehatan yang baik untuk semua!
Dalam artikel sebelumnya, saya memeriksa masalah akses ke register mikrokontroler dengan inti CortexM di C ++ dan menunjukkan solusi sederhana untuk beberapa masalah.
Hari ini saya ingin menunjukkan ide bagaimana membuat akses ke register dan bidangnya aman tanpa mengorbankan efisiensi, menggunakan kelas C ++ yang dihasilkan dari file SVD.
Semua orang yang Anda minati, selamat datang di kucing. Akan ada banyak kode.
Pendahuluan
Dalam artikel
C ++ Hardware Register Access Redux , Ken Smith menunjukkan cara bekerja secara aman dan efisien dengan register, dan bahkan menunjukkannya dengan
github.com/kensmith/cppmmio sebagai contoh.
Kemudian beberapa orang mengembangkan ide ini, misalnya,
Niklas Hauser membuat ulasan yang bagus dan menyarankan beberapa cara untuk mengakses register dengan aman.
Beberapa ide ini telah diimplementasikan di berbagai perpustakaan, khususnya di
modm . Demi kebaikan, Anda dapat menggunakan semua kalimat ini dalam kehidupan nyata. Tetapi pada saat pengembangan perpustakaan ini, deskripsi periferal dan register baru saja mulai distandarisasi, dan oleh karena itu beberapa hal dilakukan dengan tujuan agar pekerjaan utama dalam mendeskripsikan register adalah dengan programmer. Juga, beberapa solusi tidak efektif dalam hal sumber daya kode dan mikrokontroler.
Saat ini, setiap produsen mikrokontroler ARM menyediakan deskripsi semua register dalam format SVD. File header dapat dihasilkan dari deskripsi ini, oleh karena itu, dimungkinkan untuk membuat bukan deskripsi sederhana dari register, tetapi yang lebih kompleks, tetapi pada saat yang sama, yang akan meningkatkan keandalan kode Anda. Dan sangat bagus bahwa file keluaran bisa dalam bahasa apa pun, C, C ++, atau bahkan
DTetapi mari kita ambil agar akses yang umumnya aman ke register, dan mengapa itu perlu sama sekali. Penjelasan dapat ditunjukkan pada contoh-contoh sintetik sederhana, kemungkinan besar tidak mungkin, tetapi sangat mungkin:
int main(void) {
Semua kasus ini dimungkinkan dalam praktik, dan saya pasti menyaksikan sesuatu seperti itu dari siswa saya. Alangkah baiknya jika Anda bisa melarang melakukan kesalahan seperti itu.
Menurut saya juga jauh lebih menyenangkan ketika kode terlihat rapi dan tidak perlu komentar. Misalnya, meskipun Anda mengenal mikrokontroler STM32F411 dengan baik, tidak selalu mungkin untuk memahami apa yang terjadi dalam kode ini:
int main() { uint32 temp = GPIOA->OSPEEDR ; temp &=~ GPIO_OSPEEDR_OSPEED0_Msk ; temp = (GPIO_OSPEEDR_OSPEED0_0 | GPIO_OSPEEDR_OSPEED0_1) ; GPIOA->OSPEEDR = temp; }
Tidak ada komentar di sini yang tidak bisa dilakukan. Kode mengatur frekuensi operasi port GPIOA.0 ke maksimum (klarifikasi dari
mctMaks : pada kenyataannya, parameter ini mempengaruhi waktu naik depan (mis., Kecuramannya), dan berarti bahwa port dapat memproses sinyal digital secara normal pada yang diberikan (VeryLow \ Low \ Medium \ Tinggi) frekuensi).
Mari kita coba singkirkan kekurangan ini.
Daftarkan abstraksi
Pertama-tama, Anda perlu mencari tahu apa register itu dari sudut pandang programmer dan program.
Register memiliki alamat, panjang atau ukuran, mode akses: beberapa register dapat ditulis, beberapa hanya dapat dibaca, dan sebagian besar dapat dibaca dan ditulis.
Selain itu, register dapat direpresentasikan sebagai seperangkat bidang. Bidang dapat terdiri dari satu bit atau beberapa bit dan terletak di mana saja di register.
Oleh karena itu, karakteristik bidang berikut ini penting bagi kami: panjang atau ukuran (
lebar atau
ukuran ), offset dari awal register (
offset ), dan nilai.
Nilai bidang adalah ruang dari semua jumlah yang mungkin yang bisa diambil bidang dan tergantung pada panjang bidang. Yaitu jika bidang memiliki panjang 2, maka ada 4 nilai bidang yang memungkinkan (0,1,2,3). Seperti register, bidang dan nilai bidang memiliki mode akses (baca, tulis, baca dan tulis)
Untuk membuatnya lebih jelas, mari kita ambil register TIM1 CR1 dari mikrokontroler STM32F411. Secara skematis, tampilannya seperti ini:

- Bit 0 CEN: Aktifkan Penghitung
0: Penghitung Diaktifkan: Nonaktifkan
1: Counter off: Aktifkan
- UDIS Bit 1: Aktifkan / Nonaktifkan Acara UEV
0: UEV Event Diaktifkan: Diaktifkan
1: UEV event off: Disable
- URS Bit 2: Pilih Sumber Generasi Acara UEV
0: UEV dihasilkan saat meluap atau saat mengatur UG: Bit apa pun
1: UEV hanya dihasilkan pada overflow: Overflow
- Bit 3 OPM: Operasi Satu Kali
0: Penghitung waktu akan terus menghitung lebih lanjut setelah UEV: acara ContinueAfterUEV
1: Timer berhenti setelah acara UEV: StopAfterUEV
- Bit 4 DIR: Hitung Arah
0: Akun Langsung: Upcounter
1: Count Down : Downcounter
- Bit 6: 5 CMS: Mode Alignment
0: Mode Penyelarasan 0: CenterAlignedMode0
1: Mode Penyelarasan 1: CenterAlignedMode1
2: Alignment Mode 2: CenterAlignedMode2
3: Alignment Mode 3: CenterAlignedMode3
- Bit 7 APRE: Mode Preload untuk Registrasi ARR
0: register TIMx_ARR tidak di-buffer: ARRNotBuffered
1: register TIMx_ARR tidak di-buffer: ARRBuffered
- Bit 8: 9 CKD: Clock Divider
0: tDTS = tCK_INT: ClockDevidedBy1
1: tDTS = 2 * tCK_INT: ClockDevidedBy2
2: tDTS = 4 * tCK_INT: ClockDevidedBy4
3: Dicadangkan: Dicadangkan
Di sini, misalnya, CEN adalah bidang 1-bit dengan offset 0 relatif terhadap awal register. Dan
Aktifkan (1) dan
Nonaktifkan (0) adalah nilai yang mungkin.
Kami tidak akan berkonsentrasi pada apa yang masing-masing bidang register ini bertanggung jawab secara khusus, penting bagi kami bahwa setiap bidang dan nilai bidang memiliki nama yang membawa muatan semantik dan dari mana kita dapat memahami secara prinsip apa fungsinya.
Kita harus memiliki akses ke register dan bidang dan nilainya. Oleh karena itu, dalam bentuk yang sangat perkiraan, register abstraksi dapat diwakili oleh kelas berikut:

Selain kelas, penting juga bagi kami bahwa register dan bidang individual memiliki properti tertentu, register memiliki alamat, ukuran, mode akses (hanya-baca, hanya-tulis, atau keduanya).
Bidang ini memiliki ukuran, offset, dan juga mode akses. Selain itu, bidang tersebut harus berisi tautan ke register yang menjadi miliknya.
Nilai bidang harus memiliki tautan ke bidang dan atribut tambahan - nilainya.
Oleh karena itu, dalam versi yang lebih rinci, abstraksi kami akan terlihat seperti ini:

Selain atribut, abstraksi kami harus memiliki metode modifikasi dan akses. Untuk kesederhanaan, kami membatasi diri pada instalasi / penulisan dan metode membaca.

Ketika kami memutuskan abstraksi kasus, kami perlu memeriksa bagaimana abstraksi ini sesuai dengan apa yang dijelaskan dalam file SVD.
File Deskripsi Tampilan Sistem (SVD)
Format deskripsi presentasi sistem CMSIS (CMSIS-SVD) adalah deskripsi formal register mikrokontroler berdasarkan pada prosesor ARM Cortex-M. Informasi yang terkandung dalam deskripsi representasi sistem, praktis sesuai dengan data dalam manual referensi untuk perangkat. Deskripsi register dalam file seperti itu dapat berisi informasi tingkat tinggi dan tujuan dari sedikit bidang dalam register.
Secara skematis, tingkat rincian informasi dalam file seperti itu dapat dijelaskan dengan skema berikut, yang
diambil di situs web Keil :

Deskripsi File SVD disediakan oleh pabrikan dan digunakan selama debugging untuk menampilkan informasi tentang mikrokontroler dan register. Sebagai contoh, IAR menggunakannya untuk menampilkan informasi di panel View-> Register. File-file itu sendiri ada di folder Program Files (x86) \ IAR Systems \ Embedded Workbench 8.3 \ arm \ config \ debugger.
Clion from JetBrains juga menggunakan file svd untuk menampilkan informasi register saat debugging.
Anda selalu dapat mengunduh deskripsi dari situs pabrikan.
Di sini Anda dapat mengambil file SVD untuk mikrokontroler STM32F411Secara umum, format SVD adalah standar yang didukung pabrikan. Mari kita lihat apa level deskripsi dalam SVD.
Secara total, 5 level dibedakan, Level perangkat, level mikrokontroler, level register, level lapangan, level nilai yang disebutkan.
- Tingkat perangkat : Deskripsi tingkat atas dari tampilan sistem adalah perangkat. Pada level ini, properti yang terkait dengan perangkat secara keseluruhan dijelaskan. Misalnya, nama perangkat, deskripsi, atau versi. Unit addressable minimum, serta kedalaman bit bus data. Nilai default untuk atribut register, seperti ukuran register, nilai reset, dan izin akses, dapat ditetapkan untuk seluruh perangkat pada level ini dan secara implisit diwarisi oleh level deskripsi yang lebih rendah.
- Level Mikrokontroler: Bagian CPU menjelaskan inti mikrokontroler dan fitur-fiturnya. Bagian ini diperlukan jika file SVD digunakan untuk membuat file header perangkat.
- Lapisan Periferal : Perangkat periferal adalah kumpulan register yang bernama. Perangkat periferal dipetakan ke alamat basis tertentu di ruang alamat perangkat.
- Level Pendaftaran : Daftar adalah sumber daya yang dapat diprogram bernama milik perangkat periferal. Register dipetakan ke alamat tertentu di ruang alamat perangkat. Alamat tersebut relatif terhadap alamat periferal dasar. Juga, untuk register, mode akses (baca / tulis) ditunjukkan.
- Level bidang : seperti yang disebutkan di atas, register dapat dibagi menjadi beberapa bagian dari fungsionalitas yang berbeda - bidang. Level ini berisi nama-nama bidang yang unik dalam register yang sama, ukurannya, offset relatif terhadap awal register, serta mode akses.
- Tingkat nilai bidang yang disebutkan : sebenarnya, ini disebut nilai bidang yang dapat digunakan untuk kenyamanan dalam C, C ++, D, dan sebagainya.
Faktanya, file SVD adalah file xml biasa dengan deskripsi lengkap sistem. Ada konverter file svd ke kode C, misalnya di
sini , yang menghasilkan header dan struktur yang ramah-C untuk setiap perangkat dan mendaftar.
Ada juga parser
cmsis-svd dari file SVD yang ditulis dalam Phyton, yang melakukan sesuatu seperti penghilangan data dari file ke objek kelas di Phython, yang kemudian dengan mudah digunakan dalam program pembuatan kode Anda.
Contoh deskripsi register mikrokontroler STM32F411 dapat dilihat di bawah spoiler:
Contoh mendaftar timer CR1 TIM1 <peripheral> <name>TIM1</name> <description>Advanced-timers</description> <groupName>TIM</groupName> <baseAddress>0x40010000</baseAddress> <addressBlock> <offset>0x0</offset> <size>0x400</size> <usage>registers</usage> </addressBlock> <registers> <register> <name>CR1</name> <displayName>CR1</displayName> <description>control register 1</description> <addressOffset>0x0</addressOffset> <size>0x20</size> <access>read-write</access> <resetValue>0x0000</resetValue> <fields> <field> <name>CKD</name> <description>Clock division</description> <bitOffset>8</bitOffset> <bitWidth>2</bitWidth> </field> <field> <name>ARPE</name> <description>Auto-reload preload enable</description> <bitOffset>7</bitOffset> <bitWidth>1</bitWidth> </field> <field> <name>CMS</name> <description>Center-aligned mode selection</description> <bitOffset>5</bitOffset> <bitWidth>2</bitWidth> </field> <field> <name>DIR</name> <description>Direction</description> <bitOffset>4</bitOffset> <bitWidth>1</bitWidth> </field> <field> <name>OPM</name> <description>One-pulse mode</description> <bitOffset>3</bitOffset> <bitWidth>1</bitWidth> </field> <field> <name>URS</name> <description>Update request source</description> <bitOffset>2</bitOffset> <bitWidth>1</bitWidth> </field> <field> <name>UDIS</name> <description>Update disable</description> <bitOffset>1</bitOffset> <bitWidth>1</bitWidth> </field> <field> <name>CEN</name> <description>Counter enable</description> <bitOffset>0</bitOffset> <bitWidth>1</bitWidth> </field> </fields> </register> <register>
Seperti yang Anda lihat, ada semua informasi yang diperlukan untuk abstraksi kami, kecuali untuk deskripsi nilai bit spesifik bidang.
Tidak semua pabrikan ingin menghabiskan waktu untuk deskripsi lengkap dari sistem mereka, sehingga seperti yang Anda lihat, ST tidak ingin menggambarkan nilai-nilai bidang dan mentransfer beban ini ke pemrogram pelanggan. Tetapi TI menjaga pelanggannya dan menjelaskan sistem sepenuhnya, termasuk deskripsi nilai-nilai lapangan.
Di atas menunjukkan bahwa format deskripsi SVD sangat konsisten dengan abstraksi kasus kami. File tersebut berisi semua informasi yang diperlukan untuk menggambarkan register secara lengkap.
Implementasi
Daftar
Sekarang kami telah membuat abstraksi dari register dan kami memiliki deskripsi register dalam bentuk svd dari pabrikan yang sangat cocok untuk abstraksi ini, kami dapat langsung menuju implementasi.
Implementasi kami harus seefektif kode C dan ramah pengguna. Saya ingin akses ke register terlihat sejelas mungkin, misalnya, seperti ini:
if (TIM1::CR1::CKD::DividedBy2::IsSet()) { TIM1::ARR::Set(10_ms) ; TIM1::CR1::CEN::Enable::Set() ; }
Ingatlah bahwa untuk mengakses alamat register bilangan bulat, Anda perlu menggunakan reinterpret_cast:
*reinterpret_cast<volatile uint32_t *>(0x40010000) = (1U << 5U) ;
Kelas register telah dijelaskan di atas, ia harus memiliki alamat, ukuran dan mode akses, serta dua metode
Get()
dan
Set()
:
Kami meneruskan alamat, panjang register, dan mode akses ke parameter templat (ini juga kelas). Menggunakan mekanisme
SFINAE , yaitu metafunction
enable_if
, kita akan "membuang" fungsi akses
Set()
atau
Get()
untuk register yang seharusnya tidak mendukungnya. Sebagai contoh, jika register adalah read-only, maka kita akan
ReadMode
tipe
ReadMode
ke parameter template,
enable_if
akan memeriksa apakah akses adalah penerus dari
ReadMode
dan jika tidak, itu akan membuat kesalahan terkontrol (tipe T tidak dapat ditampilkan), dan kompiler tidak akan menyertakan metode
Set()
untuk register semacam itu. Hal yang sama berlaku untuk register yang dimaksudkan hanya untuk menulis.
Untuk kontrol akses kami akan menggunakan kelas-kelas:
Register datang dalam berbagai ukuran: 8, 16, 32, 64 bit. Untuk masing-masing, kami menetapkan tipe kami:
Jenis register tergantung pada ukuran template <uint32_t size> struct RegisterType {} ; template<> struct RegisterType<8> { using Type = uint8_t ; } ; template<> struct RegisterType<16> { using Type = uint16_t ; } ; template<> struct RegisterType<32> { using Type = uint32_t ; } ; template<> struct RegisterType<64> { using Type = uint64_t ; } ;
Setelah itu, untuk penghitung waktu TIM1, Anda dapat menentukan register CR1 dan, misalnya, register EGR dengan cara ini:
struct TIM1 { struct CR1 : public RegisterBase<0x40010000, 32, ReadWriteMode> { } struct EGR : public RegisterBase<0x40010014, 32, WriteMode> { } } int main() { TIM1::CR1::Set(10) ; auto reg = TIM1::CR1::Get() ;
Karena kompilator menampilkan metode
Get()
hanya untuk register di mana mode akses diwarisi dari
ReadMode
, dan metode
Set()
untuk register di mana mode akses diwarisi dari
WriteMode
, dalam kasus penggunaan metode akses yang salah, Anda akan menerima kesalahan pada tahap kompilasi. Dan jika Anda menggunakan alat pengembangan modern, seperti Clion, maka bahkan pada tahap pengkodean Anda akan melihat peringatan dari penganalisa kode:

Nah, akses ke register kini menjadi lebih aman, kode kami tidak memungkinkan Anda untuk melakukan hal-hal yang tidak dapat diterima untuk register ini, tetapi kami ingin melangkah lebih jauh dan merujuk tidak ke seluruh register, tetapi ke bidangnya.
Bidang
Bidang alih-alih alamat memiliki nilai pergeseran relatif terhadap awal register. Selain itu, untuk mengetahui alamat atau tipe yang nilai bidangnya harus dibawa, ia harus memiliki tautan ke register:
Setelah itu sudah dimungkinkan untuk melakukan hal-hal berikut:
struct TIM1 { struct CR1 : public RegisterBase<0x40010000, 32, ReadWriteMode> { using CKD = RegisterField<TIM1::CR1, 8, 2, ReadWriteMode> ; using ARPE = RegisterField<TIM1::CR1, 7, 1, ReadWriteMode> ; using CMS = RegisterField<TIM1::CR1, 5, 2, ReadWriteMode> ; using DIR = RegisterField<TIM1::CR1, 4, 1, ReadWriteMode> ; using OPM = RegisterField<TIM1::CR1, 3, 1, ReadWriteMode> ; using URS = RegisterField<TIM1::CR1, 2, 1, ReadWriteMode> ; using UDIS = RegisterField<TIM1::CR1, 1, 1, ReadWriteMode> ; using CEN = RegisterField<TIM1::CR1, 0, 1, ReadWriteMode> ; } } int main() {
Meskipun semuanya terlihat cukup bagus secara keseluruhan, masih belum sepenuhnya jelas apa arti
TIM1::CR1::CKD::Set(2)
, apa arti dua sihir yang diteruskan ke fungsi
Set()
? Dan apa arti angka yang dikembalikan oleh metode
TIM1::CR1::CEN::Get()
?
Pindah ke nilai bidang dengan mulus.
Nilai bidang
Abstraksi nilai bidang pada dasarnya juga merupakan bidang, tetapi hanya mampu menerima satu negara. Atribut ditambahkan ke abstraksi bidang - nilai aktual dan tautan ke bidang.
Set()
metode pengaturan nilai bidang identik dengan
Set()
metode pengaturan lapangan, kecuali bahwa nilai itu sendiri tidak perlu diteruskan ke metode, diketahui sebelumnya, itu hanya perlu diatur. Tetapi metode
Get()
tidak masuk akal, sebagai gantinya, lebih baik untuk memeriksa apakah nilai ini disetel atau tidak, ganti metode ini dengan metode
IsSet()
.
Bidang register sekarang dapat dijelaskan oleh satu set nilainya:
Nilai bidang register CR1 dari timer TIM1 template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_CKD_Values: public RegisterField<Reg, offset, size, AccessMode> { using DividedBy1 = FieldValue<TIM_CR_CKD_Values, 0U> ; using DividedBy2 = FieldValue<TIM_CR_CKD_Values, 1U> ; using DividedBy4 = FieldValue<TIM_CR_CKD_Values, 2U> ; using Reserved = FieldValue<TIM_CR_CKD_Values, 3U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_ARPE_Values: public RegisterField<Reg, offset, size, AccessMode> { using ARRNotBuffered = FieldValue<TIM_CR_ARPE_Values, 0U> ; using ARRBuffered = FieldValue<TIM_CR_ARPE_Values, 1U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_CMS_Values: public RegisterField<Reg, offset, size, AccessMode> { using CenterAlignedMode0 = FieldValue<TIM_CR_CMS_Values, 0U> ; using CenterAlignedMode1 = FieldValue<TIM_CR_CMS_Values, 1U> ; using CenterAlignedMode2 = FieldValue<TIM_CR_CMS_Values, 2U> ; using CenterAlignedMode3 = FieldValue<TIM_CR_CMS_Values, 3U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_DIR_Values: public RegisterField<Reg, offset, size, AccessMode> { using Upcounter = FieldValue<TIM_CR_DIR_Values, 0U> ; using Downcounter = FieldValue<TIM_CR_DIR_Values, 1U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_OPM_Values: public RegisterField<Reg, offset, size, AccessMode> { using ContinueAfterUEV = FieldValue<TIM_CR_OPM_Values, 0U> ; using StopAfterUEV = FieldValue<TIM_CR_OPM_Values, 1U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_URS_Values: public RegisterField<Reg, offset, size, AccessMode> { using Any = FieldValue<TIM_CR_URS_Values, 0U> ; using Overflow = FieldValue<TIM_CR_URS_Values, 1U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_UDIS_Values: public RegisterField<Reg, offset, size, AccessMode> { using Enable = FieldValue<TIM_CR_UDIS_Values, 0U> ; using Disable = FieldValue<TIM_CR_UDIS_Values, 1U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_CEN_Values: public RegisterField<Reg, offset, size, AccessMode> { using Disable = FieldValue<TIM_CR_CEN_Values, 0U> ; using Enable = FieldValue<TIM_CR_CEN_Values, 1U> ; } ;
Kemudian register CR1 itu sendiri akan dijelaskan sebagai berikut:
struct TIM1 { struct CR1 : public RegisterBase<0x40010000, 32, ReadWriteMode> { using CKD = TIM_CR1_CKD_Values<TIM1::CR1, 8, 2, ReadWriteMode> ; using ARPE = TIM_CR1_ARPE_Values<TIM1::CR1, 7, 1, ReadWriteMode> ; using CMS = TIM_CR1_CMS_Values<TIM1::CR1, 5, 2, ReadWriteMode> ; using DIR = TIM_CR1_DIR_Values<TIM1::CR1, 4, 1, ReadWriteMode> ; using OPM = TIM_CR1_OPM_Values<TIM1::CR1, 3, 1, ReadWriteMode> ; using URS = TIM_CR1_URS_Values<TIM1::CR1, 2, 1, ReadWriteMode> ; using UDIS = TIM_CR1_UDIS_Values<TIM1::CR1, 1, 1, ReadWriteMode> ; using CEN = TIM_CR1_CEN_Values<TIM1::CR1, 0, 1, ReadWriteMode> ; } ; }
Sekarang Anda dapat mengatur dan membaca langsung nilai bidang register: Misalnya, jika Anda perlu mengaktifkan timer pada akun, cukup panggil metode
Set()
pada nilai
Enable
pada bidang CEN register CR1 dari timer TIM1:
TIM1::CR1::CEN::Enable::Set() ;
. Dalam kode tersebut, akan terlihat seperti ini:
int main() { if (TIM1::CR1::CKD::DividedBy2::IsSet()) { TIM1::ARR::Set(100U) ; TIM1::CR1::CEN::Enable::Set() ; } }
Sebagai perbandingan, hal yang sama menggunakan header C: int main() { if((TIM1->CR1 & TIM_CR1_CKD_Msk) == TIM_CR1_CKD_0) { TIM1->ARR = 100U ; regValue = TIM1->CR1 ; regValue &=~(TIM_CR1_CEN_Msk) ; regValue |= TIM_CR1_CEN ; TIM1->CR1 = regValue ; } }
Jadi, perbaikan utama dilakukan, kita dapat memiliki akses yang sederhana dan mudah dipahami ke register, bidang dan nilainya.
Akses dikendalikan pada tingkat kompilasi, dan jika register, bidang atau nilai tidak memungkinkan penulisan atau membaca, ini akan menjadi jelas bahkan sebelum kode tersebut dimasukkan ke dalam mikrokontroler.Namun, masih ada satu kelemahan, tidak mungkin untuk memasukkan beberapa nilai bidang dalam register pada saat yang bersamaan. Bayangkan apa yang perlu Anda lakukan ini: int main() { uint32_t regValue = TIM1->CR1 ; regValue &=~(TIM_CR1_CKD_Msk | TIM_CR1_DIR) ; regValue |= (TIM_CR1_CEN | TIM_CR1_CKD_0 | TIM_CR1_CKD_0) ; TIM1->CR1 = regValue ; }
Untuk melakukan ini, kita perlu membuat metode pada register Set(...)
dengan sejumlah variabel argumen, atau mencoba menentukan nilai-nilai bidang yang perlu diatur dalam template. Yaitu
mengimplementasikan salah satu opsi berikut: int main() {
, , , , .
. :
, :
- , .
- .
constexpr , :
Tetap menetapkan hanya metode publik Set()
dan IsSet()
:
Hampir semuanya, satu masalah kecil tetap, kita dapat melakukan kebodohan seperti itu: int main() {
Tentunya, Anda perlu memeriksa apakah set nilai kami peka terhadap huruf besar-kecil, cukup sederhana untuk melakukan ini, cukup tambahkan jenis tambahan ke parameter templat, sebut saja FieldValueBaseType
. Sekarang register dan nilai-nilai bidang yang dapat diatur dalam register ini harus dari FieldValueBaseType
jenis yang sama :Tambahkan cek untuk kepemilikan nilai bidang ke register ini template<uint32_t address, size_t size, typename AccessMode, typename FieldValueBaseType, typename ...Args> class Register { private:
, SFINAE , , , , , .
CR1 TIM1, :
struct TIM1 { struct TIM1CR1Base {} ; struct CR1 : public RegisterBase<0x40010000, 32, ReadWriteMode> { using CKD = TIM_CR_CKD_Values<TIM1::CR1, 8, 2, ReadWriteMode, TIM1CR1Base> ; using ARPE = TIM_CR_ARPE_Values<TIM1::CR1, 7, 1, ReadWriteMode, TIM1CR1Base> ; using CMS = TIM_CR_CMS_Values<TIM1::CR1, 5, 2, ReadWriteMode, TIM1CR1Base> ; using DIR = TIM_CR_DIR_Values<TIM1::CR1, 4, 1, ReadWriteMode, TIM1CR1Base> ; using OPM = TIM_CR_OPM_Values<TIM1::CR1, 3, 1, ReadWriteMode, TIM1CR1Base> ; using URS = TIM_CR_URS_Values<TIM1::CR1, 2, 1, ReadWriteMode, TIM1CR1Base> ; using UDIS = TIM_CR_UDIS_Values<TIM1::CR1, 1, 1, ReadWriteMode, TIM1CR1Base> ; using CEN = TIM_CR_CEN_Values<TIM1::CR1, 0, 1, ReadWriteMode, TIM1CR1Base> ; } ; }
, , . , , , .
, :
:
int main(void) {
, , .
, , , ?
, ++, , 1 :
: int main() { uint32_t res = RCC->AHB2ENR; res &=~ RCC_AHB1ENR_GPIOAEN_Msk ; res |= RCC_AHB1ENR_GPIOAEN ; RCC->AHB2ENR = res ; res = GPIOA->MODER ; res &=~ (GPIO_MODER_MODER5 | GPIO_MODER_MODER4 | GPIO_MODER_MODER1) ; res |= (GPIO_MODER_MODER5_0 | GPIO_MODER_MODER4_0 | GPIO_MODER_MODER1_0) ; GPIOA->MODER = res ; GPIOA->BSRR = (GPIO_BSRR_BS5 | GPIO_BSRR_BS4 | GPIO_BSRR_BS1) ; return 0 ; }
++: int main() { RCC::AHB1ENR::GPIOAEN::Enable::Set() ; GPIOA::MODERPack< GPIOA::MODER::MODER5::Output, GPIOA::MODER::MODER4::Output, GPIOA::MODER::MODER1::Output>::Set() ; GPIOA::BSRRPack< GPIOA::BSRR::BS5::Set, GPIOA::BSRR::BS4::Set, GPIOA::BSRR::BS1::Set>::Write() ; return 0 ; }
IAR. : :
:

C++ :

18 , , .
, :

13 .
++ :

: , .
, . , ?
Kami mendapat akses yang andal, mudah, dan cepat ke register. Masih ada satu pertanyaan. Cara menggambarkan semua register, ada juga yang di bawah seratus untuk mikrokontroler. Ini adalah berapa banyak waktu yang diperlukan untuk menggambarkan semua register, karena Anda dapat membuat banyak kesalahan dalam pekerjaan rutin seperti itu. Ya, Anda tidak perlu melakukan ini secara manual. Sebagai gantinya, kita akan menggunakan pembuat kode dari file SVD, yang, seperti yang saya sebutkan di atas di awal artikel, sepenuhnya mencakup abstraksi register yang saya terima.Saya menyelesaikan skrip kolega, yang, berdasarkan ide ini, melakukan hal yang sama, tetapi sedikit lebih mudah menggunakan enum daripada kelas untuk nilai bidang. Skrip dibuat hanya untuk menguji dan memeriksa ide, oleh karena itu tidak optimal, tetapi memungkinkan Anda untuk menghasilkan sesuatu seperti ini.
Siapa yang peduli dengan skrip ada di siniRingkasan
Akibatnya, tugas programmer hanya untuk menghubungkan file yang dihasilkan dengan benar. Jika Anda perlu menggunakan register, katakanlah modul gpioa atau rcc, Anda hanya perlu memasukkan file header yang diinginkan: #include "gpioaregisters.hpp"
Saya ulangi, file SVD dapat diunduh dari situs web pabrikan, Anda dapat menariknya keluar dari lingkungan pengembangan, mengirimkannya ke input skrip dan itu saja.Tetapi, seperti yang saya katakan di atas, tidak semua produsen peduli dengan konsumen mereka, oleh karena itu, tidak semua orang memiliki transfer dalam file SVD, karena itu, semua transfer untuk mikrokontroler ST merawat generasi seperti ini: template <typename Reg, size_t offset, size_t size, typename AccessMode, typename BaseType> struct GPIOA_MODER_MODER_Values: public RegisterField<Reg, offset, size, AccessMode> { using Value0 = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 0U> ; using Value1 = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 1U> ; using Value2 = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 2U> ; using Value3 = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 3U> ; } ;
Pada saat itu ketika Anda perlu menggunakannya, Anda dapat melihat ke dalam dokumentasi dan mengubah kata Value, untuk sesuatu yang lebih dimengerti: template <typename Reg, size_t offset, size_t size, typename AccessMode, typename BaseType> struct GPIOA_MODER_MODER_Values: public RegisterField<Reg, offset, size, AccessMode> { using Input = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 0U> ; using Output = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 1U> ; using Alternate = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 2U> ; using Analog = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 3U> ; } ;
.
, ST , 0.
, , enum .
, .
IAR 8.40.1
ยซ Online GDBยปPS:
putyavka RegisterField::Get()
Ryppka assert.
Typesafe Register Access in C++One Approach to Using Hardware Registers in C++SVD Description (*.svd) Format