From a5850ab6d6c508fc3351fd86646bcf3fb1b69103 Mon Sep 17 00:00:00 2001 From: Ian McIntyre Date: Thu, 22 Sep 2022 13:49:23 -0400 Subject: Add macro support for register arrays I'm experimenting with a RAL code generator that collapses contiguous register arrays. The generated code would resemble pub struct RegisterBlock { pub MY_ARRAY: [RWRegister; 3], } and an individual register would be addressed like ral::read_reg!(ral::my_mod, my_inst, MY_ARRAY[1]); This commit extends the four macros so that we can specify an array offset. We simply need to match zero or more `[N]` patterns, where `N` is some expression that produces an array offset. The included test case shows that the approach should support multi-dimensional arrays. --- src/lib.rs | 40 ++++++++++++++--------------- tests/pathological.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 tests/pathological.rs diff --git a/src/lib.rs b/src/lib.rs index 52401d6..c65e94b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -275,11 +275,11 @@ impl UnsafeWORegister { /// and the macro brings such constants into scope and then dereferences the provided reference. #[macro_export] macro_rules! write_reg { - ( $periph:path, $instance:expr, $reg:ident, $( $field:ident : $value:expr ),+ ) => {{ + ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $( $field:ident : $value:expr ),+ ) => {{ #[allow(unused_imports)] use $periph::{*}; #[allow(unused_imports)] - (*$instance).$reg.write( + (*$instance).$reg $([$offset])*.write( $({ use $periph::{$reg::$field::{W::*, RW::*}}; ($value << { use $periph::{$reg::$field::offset}; offset }) @@ -287,10 +287,10 @@ macro_rules! write_reg { }) | * ); }}; - ( $periph:path, $instance:expr, $reg:ident, $value:expr ) => {{ + ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $value:expr ) => {{ #[allow(unused_imports)] use $periph::{*}; - (*$instance).$reg.write($value); + (*$instance).$reg $([$offset])*.write($value); }}; } @@ -408,12 +408,12 @@ macro_rules! write_reg { /// and the macro brings such constants into scope and then dereferences the provided reference. #[macro_export] macro_rules! modify_reg { - ( $periph:path, $instance:expr, $reg:ident, $( $field:ident : $value:expr ),+ ) => {{ + ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $( $field:ident : $value:expr ),+ ) => {{ #[allow(unused_imports)] use $periph::{*}; #[allow(unused_imports)] - (*$instance).$reg.write( - ((*$instance).$reg.read() & !( $({ use $periph::{$reg::$field::mask}; mask }) | * )) + (*$instance).$reg $([$offset])*.write( + ((*$instance).$reg $([$offset])*.read() & !( $({ use $periph::{$reg::$field::mask}; mask }) | * )) | $({ use $periph::{$reg::$field::{W::*, RW::*}}; ($value << { use $periph::{$reg::$field::offset}; offset }) @@ -421,10 +421,10 @@ macro_rules! modify_reg { }) | * ); }}; - ( $periph:path, $instance:expr, $reg:ident, $fn:expr ) => {{ + ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $fn:expr ) => {{ #[allow(unused_imports)] use $periph::{*}; - (*$instance).$reg.write($fn((*$instance).$reg.read())); + (*$instance).$reg $([$offset])*.write($fn((*$instance).$reg $([$offset])*.read())); }}; } @@ -527,27 +527,27 @@ macro_rules! modify_reg { /// and the macro brings such constants into scope and then dereferences the provided reference. #[macro_export] macro_rules! read_reg { - ( $periph:path, $instance:expr, $reg:ident, $( $field:ident ),+ ) => {{ + ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $( $field:ident ),+ ) => {{ #[allow(unused_imports)] use $periph::{*}; - let val = ((*$instance).$reg.read()); + let val = ((*$instance).$reg $([$offset])*.read()); ( $({ #[allow(unused_imports)] use $periph::{$reg::$field::{mask, offset, R::*, RW::*}}; (val & mask) >> offset }) , *) }}; - ( $periph:path, $instance:expr, $reg:ident, $field:ident $($cmp:tt)* ) => {{ + ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $field:ident $($cmp:tt)* ) => {{ #[allow(unused_imports)] use $periph::{*}; #[allow(unused_imports)] use $periph::{$reg::$field::{mask, offset, R::*, RW::*}}; - (((*$instance).$reg.read() & mask) >> offset) $($cmp)* + (((*$instance).$reg $([$offset])*.read() & mask) >> offset) $($cmp)* }}; - ( $periph:path, $instance:expr, $reg:ident ) => {{ + ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])* ) => {{ #[allow(unused_imports)] use $periph::{*}; - ((*$instance).$reg.read()) + ((*$instance).$reg $([$offset])*.read()) }}; } @@ -624,20 +624,20 @@ macro_rules! read_reg { /// `GPIOA` they are not the same thing. #[macro_export] macro_rules! reset_reg { - ( $periph:path, $instance:expr, $instancemod:path, $reg:ident, $( $field:ident ),+ ) => {{ + ( $periph:path, $instance:expr, $instancemod:path, $reg:ident $([$offset:expr])*, $( $field:ident ),+ ) => {{ #[allow(unused_imports)] use $periph::{*}; use $periph::{$instancemod::{reset}}; #[allow(unused_imports)] - (*$instance).$reg.write({ + (*$instance).$reg $([$offset])*.write({ let resetmask: u32 = $({ use $periph::{$reg::$field::mask}; mask }) | *; - ((*$instance).$reg.read() & !resetmask) | (reset.$reg & resetmask) + ((*$instance).$reg $([$offset])*.read() & !resetmask) | (reset.$reg & resetmask) }); }}; - ( $periph:path, $instance:expr, $instancemod:path, $reg:ident ) => {{ + ( $periph:path, $instance:expr, $instancemod:path, $reg:ident $([$offset:expr])*) => {{ #[allow(unused_imports)] use $periph::{*}; use $periph::{$instancemod::{reset}}; - (*$instance).$reg.write(reset.$reg); + (*$instance).$reg $([$offset])*.write(reset.$reg); }}; } diff --git a/tests/pathological.rs b/tests/pathological.rs new file mode 100644 index 0000000..d463e29 --- /dev/null +++ b/tests/pathological.rs @@ -0,0 +1,70 @@ +//! Testing corner cases. + +#![allow(non_upper_case_globals, non_snake_case)] // Macro conventions. + +use ral_registers as ral; + +mod periph { + #[repr(C)] + pub struct RegisterBlock { + /// Multi-dimensional arrays. + #[allow(clippy::type_complexity)] // Intentionally complex type. + pub DEEP_LEARNING: [[[[[[[[ral_registers::RWRegister; 1]; 2]; 3]; 4]; 5]; 6]; 7]; 8], + } + + pub mod DEEP_LEARNING { + pub mod GRADIENT { + pub const offset: u32 = 3; + pub const mask: u32 = 0x1F << offset; + pub mod R {} + pub mod W {} + pub mod RW {} + } + } + + pub struct ResetValues { + pub DEEP_LEARNING: u32, + } + + pub mod INST { + pub const reset: super::ResetValues = super::ResetValues { DEEP_LEARNING: 42 }; + } +} + +fn register_block() -> periph::RegisterBlock { + // Safety: bitpattern of zero is fine. + use std::mem::MaybeUninit; + unsafe { MaybeUninit::zeroed().assume_init() } +} + +#[test] +fn read_deep_array() { + let rb = register_block(); + rb.DEEP_LEARNING[7][6][5][4][3][2][1][0].write(u32::MAX); + let gradient = ral::read_reg!(periph, &rb, DEEP_LEARNING[7][6][5][4][3][2][1][0], GRADIENT); + assert_eq!(gradient, 0x1F); +} + +#[test] +fn write_deep_array() { + let rb = register_block(); + ral::write_reg!(periph, &rb, DEEP_LEARNING[7][6][5][4][3][2][1][0], 23); + assert_eq!(rb.DEEP_LEARNING[7][6][5][4][3][2][1][0].read(), 23); +} + +#[test] +fn modify_deep_array() { + let rb = register_block(); + ral::modify_reg!(periph, &rb, DEEP_LEARNING[7][6][5][4][3][2][1][0], GRADIENT: 42); + assert_eq!( + rb.DEEP_LEARNING[7][6][5][4][3][2][1][0].read(), + (42 & 0x1F) << 3 + ); +} + +#[test] +fn reset_deep_array() { + let rb = register_block(); + ral::reset_reg!(periph, &rb, INST, DEEP_LEARNING[7][6][5][4][3][2][1][0]); + assert_eq!(rb.DEEP_LEARNING[7][6][5][4][3][2][1][0].read(), 42); +} -- cgit v1.2.3 From 1290bae4efb487e2b51ae6176a7211d84cbb447e Mon Sep 17 00:00:00 2001 From: Ian McIntyre Date: Thu, 3 Nov 2022 11:44:43 -0400 Subject: Expand macros testing for register scalars, arrays The parent commit extends the macros to support both scalar and array register access. This commit extends the macro test suite to test both scalar and array register access. It also tests that you can use things that deref to a register block, including normal references, deref smart pointers, and raw pointers. The tests cases carry over the shadowed variable tests. --- tests/macros.rs | 462 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 382 insertions(+), 80 deletions(-) diff --git a/tests/macros.rs b/tests/macros.rs index 8ff36ac..ae33599 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -1,102 +1,404 @@ -//! Test the read, write, modify macros. +//! Tests that read, write, modify, and reset macros +//! work with +//! +//! - scalar register syntax +//! - array register syntax +//! +//! when supplied with +//! +//! - a reference to a register block. +//! - an "instance" type that derefs to a register block. +//! - a pointer to a register block. + +#![allow(non_upper_case_globals, non_snake_case)] // Macro conventions. use ral_registers as ral; -struct DummyRegisterBlock { - register: ral::RWRegister, +/// A peripheral module. +mod periph { + #[repr(C)] + pub struct RegisterBlock { + pub MY_SCALAR: ral_registers::RWRegister, + pub MY_ARRAY: [ral_registers::RWRegister; 3], + } + + pub mod MY_SCALAR { + pub mod FIELD_A { + pub const offset: u32 = 0; + pub const mask: u32 = 0x7F << offset; + pub mod R {} + pub mod W {} + pub mod RW {} + } + pub mod FIELD_B { + pub const offset: u32 = 27; + pub const mask: u32 = 0b11 << offset; + pub mod R {} + pub mod W {} + pub mod RW {} + } + } + + /// The register array module resembles the register module for + /// a scalar register. The macros distinguish the index + /// operator from the register name to look up items in + /// this module. + pub mod MY_ARRAY { + // For testing convenience, we're pretending that MY_ARRAY + // has the same fields as MY_SCALAR. + pub use super::MY_SCALAR::*; + } + + /// Reset values are always expressed as a scalar, no matter + /// the register plurality. + pub struct ResetValues { + pub MY_SCALAR: u32, + pub MY_ARRAY: u32, + } + + pub mod INST { + pub const reset: super::ResetValues = super::ResetValues { + MY_SCALAR: 42, + MY_ARRAY: 42, + }; + } +} + +fn register_block() -> periph::RegisterBlock { + // Safety: bitpattern of zero is fine. + use std::mem::MaybeUninit; + unsafe { MaybeUninit::zeroed().assume_init() } +} + +struct Instance<'a> { + rb: &'a periph::RegisterBlock, +} + +impl<'a> Instance<'a> { + fn new(rb: &'a periph::RegisterBlock) -> Self { + Self { rb } + } +} + +impl std::ops::Deref for Instance<'_> { + type Target = periph::RegisterBlock; + fn deref(&self) -> &Self::Target { + self.rb + } +} + +/// Defines the base cases for read_reg. +/// +/// The goal is to have one macro path that supports both scalar +/// and array tests cases. To achieve that, there's always a register +/// identifier. There's optionally a bracket that has an offset. +macro_rules! read_reg_test_cases { + ($instance:expr, $register:ident $([$offset:expr])*) => { + // Setup: + (*$instance).$register $([$offset])*.write(u32::MAX); + + assert_eq!(ral::read_reg!(periph, $instance, $register $([$offset])*), u32::MAX, "Direct read"); + + assert_eq!(ral::read_reg!(periph, $instance, $register $([$offset])*, FIELD_A), 0x7F, "Individual field read (A)"); + assert_eq!(ral::read_reg!(periph, $instance, $register $([$offset])*, FIELD_B), 0b11, "Individual field read (B)"); + + assert_eq!( + ral::read_reg!(periph, $instance, $register $([$offset])*, FIELD_A, FIELD_B), + (0x7F, 0b11), + "Tuple field reads" + ); + + assert!(ral::read_reg!( + periph, + $instance, + $register $([$offset])*, + FIELD_A == 0x7F + ), "Boolean expressions"); + }; +} + +#[test] +fn read_scalar_ref() { + let rb = register_block(); + read_reg_test_cases!(&rb, MY_SCALAR); +} + +#[test] +fn read_scalar_deref() { + let rb = register_block(); + let inst = Instance::new(&rb); + read_reg_test_cases!(inst, MY_SCALAR); +} + +#[test] +fn read_scalar_ptr() { + let ptr: *const _ = ®ister_block(); + unsafe { + read_reg_test_cases!(ptr, MY_SCALAR); + } +} + +#[test] +fn read_array_ref() { + let rb = register_block(); + read_reg_test_cases!(&rb, MY_ARRAY[1]); +} + +#[test] +fn read_array_deref() { + let rb = register_block(); + let inst = Instance::new(&rb); + read_reg_test_cases!(inst, MY_ARRAY[1]); +} + +#[test] +fn read_array_ptr() { + let ptr: *const _ = ®ister_block(); + unsafe { + read_reg_test_cases!(ptr, MY_ARRAY[1]); + } +} + +#[should_panic] +#[test] +fn read_array_out_of_bounds() { + let rb = register_block(); + ral::read_reg!(periph, &rb, MY_ARRAY[42]); +} + +/// Base test cases for write_reg. +/// +/// See [read_reg_test_cases] for more information. +macro_rules! write_reg_test_cases { + ($instance:expr, $register:ident $([$offset:expr])*) => { + ral::write_reg!(periph, $instance, $register $([$offset])*, FIELD_A: u32::MAX); + assert_eq!((*$instance).$register $([$offset])*.read(), 0x7F, "1:1 write:field (A)"); + ral::write_reg!(periph, $instance, $register $([$offset])*, FIELD_B: u32::MAX); + assert_eq!((*$instance).$register $([$offset])*.read(), 0b11 << 27, "1:1 write:field (B)"); + + ral::write_reg!( + periph, + $instance, + $register $([$offset])*, + FIELD_A: u32::MAX, + FIELD_B: u32::MAX + ); + assert_eq!((*$instance).$register $([$offset])*.read(), (0b11 << 27) | 0x7F, "1:N write:field"); + + ral::write_reg!(periph, $instance, $register $([$offset])*, 0xAAAAAAAA); + assert_eq!((*$instance).$register $([$offset])*.read(), 0xAAAAAAAA, "Direct write"); + + // Make sure that local variables mask, offset don't shadow macro details. + #[deny(warnings)] + { + let mask = 7; + let offset = 8; + ral::write_reg!(periph, $instance, $register $([$offset])*, FIELD_A: mask, FIELD_B: offset); + } + }; +} + +#[test] +fn write_scalar_ref() { + let rb = register_block(); + write_reg_test_cases!(&rb, MY_SCALAR); +} + +#[test] +fn write_scalar_deref() { + let rb = register_block(); + let inst = Instance::new(&rb); + write_reg_test_cases!(inst, MY_SCALAR); } -mod register { - #![allow(non_upper_case_globals, non_snake_case)] // Macro conventions. +#[test] +fn write_scalar_ptr() { + let ptr: *const _ = ®ister_block(); + unsafe { + write_reg_test_cases!(ptr, MY_SCALAR); + } +} - pub mod field_foo { - pub const offset: u32 = 10; - pub const mask: u32 = 0x7 << offset; - pub mod RW {} - pub mod W {} - pub mod R {} +#[test] +fn write_array_ref() { + let rb = register_block(); + write_reg_test_cases!(&rb, MY_ARRAY[1]); +} + +#[test] +fn write_array_deref() { + let rb = register_block(); + let inst = Instance::new(&rb); + write_reg_test_cases!(inst, MY_ARRAY[1]); +} + +#[test] +fn write_array_ptr() { + let ptr: *const _ = ®ister_block(); + unsafe { + write_reg_test_cases!(ptr, MY_ARRAY[1]); } } -fn zeroed_register_block() -> DummyRegisterBlock { - use core::mem::MaybeUninit; - let register_block = MaybeUninit::zeroed(); - // Safety: 0 is a safe bitpattern - unsafe { register_block.assume_init() } +#[should_panic] +#[test] +fn write_array_out_of_bounds() { + let rb = register_block(); + ral::write_reg!(periph, &rb, MY_ARRAY[42], 7); +} + +/// Base test cases for modify_reg. +/// +/// See [read_reg_test_cases] for more information. +macro_rules! modify_reg_test_cases { + ($instance:expr, $register:ident $([$offset:expr])*) => { + ral::modify_reg!(periph, $instance, $register $([$offset])*, FIELD_A: u32::MAX); + assert_eq!((*$instance).$register $([$offset])*.read(), 0x7F, "RMW individual fields (A)"); + ral::modify_reg!(periph, $instance, $register $([$offset])*, FIELD_B: u32::MAX); + assert_eq!((*$instance).$register $([$offset])*.read(), 0x7F | (0b11 << 27), "RMW individual fields (B)"); + + ral::modify_reg!(periph, $instance, $register $([$offset])*, FIELD_A: 2, FIELD_B: 2); + assert_eq!((*$instance).$register $([$offset])*.read(), 2 | (2 << 27), "RMW multiple fields"); + + ral::modify_reg!(periph, $instance, $register $([$offset])*, |reg| { + assert_eq!(reg, 2 | (2 << 27)); + reg | u32::MAX + }); + assert_eq!((*$instance).$register $([$offset])*.read(), u32::MAX, "RMW whole register"); + + // Make sure that local variables mask, offset don't shadow macro details. + #[deny(warnings)] + { + let mask = 7; + let offset = 8; + ral::modify_reg!(periph, $instance, $register $([$offset])*, FIELD_A: mask, FIELD_B: offset); + } + }; } #[test] -fn register_read() { - let register_block = zeroed_register_block(); - register_block.register.write(0b111 << 10); - assert_eq!( - 0x7, - ral::read_reg!(self, ®ister_block, register, field_foo) - ); +fn modify_scalar_ref() { + let rb = register_block(); + modify_reg_test_cases!(&rb, MY_SCALAR); } #[test] -fn register_write() { - let register_block = zeroed_register_block(); - ral::write_reg!(self, ®ister_block, register, field_foo: 5); - assert_eq!(5 << 10, register_block.register.read()); +fn modify_scalar_deref() { + let rb = register_block(); + let inst = Instance::new(&rb); + modify_reg_test_cases!(inst, MY_SCALAR); } #[test] -fn register_modify() { - let register_block = zeroed_register_block(); - register_block.register.write(1 << 10); - ral::modify_reg!(self, ®ister_block, register, field_foo: 6); - assert_eq!(6 << 10, register_block.register.read()); +fn modify_scalar_ptr() { + let ptr: *const _ = ®ister_block(); + unsafe { + modify_reg_test_cases!(ptr, MY_SCALAR); + } } -/// Demonstrates that a local variable, 'mask' -/// doesn't affect the macro's behavior. +#[test] +fn modify_array_ref() { + let rb = register_block(); + modify_reg_test_cases!(&rb, MY_ARRAY[1]); +} + +#[test] +fn modify_array_deref() { + let rb = register_block(); + let inst = Instance::new(&rb); + modify_reg_test_cases!(inst, MY_ARRAY[1]); +} + +#[test] +fn modify_array_ptr() { + let ptr: *const _ = ®ister_block(); + unsafe { + modify_reg_test_cases!(ptr, MY_ARRAY[1]); + } +} + +#[should_panic] +#[test] +fn modify_array_out_of_bounds() { + let rb = register_block(); + ral::modify_reg!(periph, &rb, MY_ARRAY[42], |_| 7); +} + +/// Base test cases for reset_reg. /// -/// This is the same test as register_modify(), but -/// with the number '6' in a variable called 'mask'. -#[deny(warnings)] // Promotes "unused variable: mask" into an error. Should no longer happen. -#[test] -fn register_unused_mask_modify() { - let register_block = zeroed_register_block(); - register_block.register.write(1 << 10); - let mask = 6; - ral::modify_reg!(self, ®ister_block, register, field_foo: mask); - assert_eq!(6 << 10, register_block.register.read()); -} - -/// Same test as above, but with a variable called -/// 'offset' instead of 'mask'. -#[deny(warnings)] -#[test] -fn register_unused_offset_modify() { - let register_block = zeroed_register_block(); - register_block.register.write(1 << 10); - let offset = 6; - ral::modify_reg!(self, ®ister_block, register, field_foo: offset); - assert_eq!(6 << 10, register_block.register.read()); -} - -/// Same as above test, but using the 'write' macro -/// instead of 'modify'. -#[deny(warnings)] -#[test] -fn register_unused_mask_write() { - let register_block = zeroed_register_block(); - register_block.register.write(1 << 10); - let mask = 6; - ral::write_reg!(self, ®ister_block, register, field_foo: mask); - assert_eq!(6 << 10, register_block.register.read()); -} - -/// Same test as above, but with a variable called -/// 'offset' instead of 'mask'. -#[deny(warnings)] -#[test] -fn register_unused_offset_write() { - let register_block = zeroed_register_block(); - register_block.register.write(1 << 10); - let offset = 6; - ral::write_reg!(self, ®ister_block, register, field_foo: offset); - assert_eq!(6 << 10, register_block.register.read()); +/// See [read_reg_test_cases] for more information. +macro_rules! reset_reg_test_cases { + ($instance:expr, $register:ident $([$offset:expr])*) => { + (*$instance).$register $([$offset])*.write(u32::MAX); + ral::reset_reg!(periph, $instance, INST, $register $([$offset])*); + assert_eq!((*$instance).$register $([$offset])*.read(), 42, "Entire register"); + + (*$instance).$register $([$offset])*.write(u32::MAX); + ral::reset_reg!(periph, $instance, INST, $register $([$offset])*, FIELD_B); + assert_eq!((*$instance).$register $([$offset])*.read(), u32::MAX & !(0b11 << 27), "Field in register (B)"); + ral::reset_reg!(periph, $instance, INST, $register $([$offset])*, FIELD_A); + assert_eq!( + (*$instance).$register $([$offset])*.read(), + u32::MAX & !(0b11 << 27) & !0x7F | 42, + "Field in register (A)" + ); + + (*$instance).$register $([$offset])*.write(u32::MAX); + ral::reset_reg!(periph, $instance, INST, $register $([$offset])*, FIELD_B, FIELD_A); + assert_eq!( + (*$instance).$register $([$offset])*.read(), + u32::MAX & !(0b11 << 27) & !0x7F | 42, + "Fields in register" + ); + }; +} + +#[test] +fn reset_scalar_ref() { + let rb = register_block(); + reset_reg_test_cases!(&rb, MY_SCALAR); +} + +#[test] +fn reset_scalar_deref() { + let rb = register_block(); + let inst = Instance::new(&rb); + reset_reg_test_cases!(inst, MY_SCALAR); +} + +#[test] +fn reset_scalar_ptr() { + let ptr: *const _ = ®ister_block(); + unsafe { + reset_reg_test_cases!(ptr, MY_SCALAR); + } +} + +#[test] +fn reset_array_ref() { + let rb = register_block(); + reset_reg_test_cases!(&rb, MY_ARRAY[1]); +} + +#[test] +fn reset_array_deref() { + let rb = register_block(); + let inst = Instance::new(&rb); + reset_reg_test_cases!(inst, MY_ARRAY[1]); +} + +#[test] +fn reset_array_ptr() { + let ptr: *const _ = ®ister_block(); + unsafe { + reset_reg_test_cases!(ptr, MY_ARRAY[1]); + } +} + +#[should_panic] +#[test] +fn reset_array_out_of_bounds() { + let rb = register_block(); + ral::reset_reg!(periph, &rb, INST, MY_ARRAY[42]); } -- cgit v1.2.3 From 5e7603467c24abb8bdaac95928c05ed74898c9a2 Mon Sep 17 00:00:00 2001 From: Ian McIntyre Date: Wed, 7 Dec 2022 09:56:44 -0500 Subject: Document support for register arrays in each macro Since stm32ral doesn't have register arrays, I'm using prose and pseudo-code to document each macro's support for register arrays. This seemed to be today's simplest way to document the features for users. I have another commit that implements a hidden module with a RAL-like API and register arrays. Documentation examples then reference this hidden module to demonstrate the register array feature. But, it adds more code (could be shared with the tests), and it resulted in inconsistent documentation examples when compared to the STM examples. --- src/lib.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c65e94b..bcf0370 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -194,6 +194,10 @@ impl UnsafeWORegister { /// # } /// ``` /// +/// To support register arrays, each macro form also supports one or more array indices after the +/// register. For example, `write_reg!(stm32ral::gpio, gpioa, ODR[2], 42);` writes the value 42 to +/// the third register in an `ODR` register array. +/// /// # Usage /// Like `modify_reg!`, this macro can be used in two ways, either with a single value to write to /// the whole register, or with multiple fields each with their own value. @@ -203,7 +207,8 @@ impl UnsafeWORegister { /// * a reference to the instance of that peripheral: 'gpioa' (anything which dereferences to /// `RegisterBlock`, such as `Instance`, `&Instance`, `&RegisterBlock`, or /// `*const RegisterBlock`), -/// * the register you wish you access: `MODER` (a field on the `RegisterBlock`). +/// * the register (and offset, for arrays) you wish you access: `MODER` (a field on the +/// `RegisterBlock`). /// /// In the single-value usage, the final argument is just the value to write: /// ```rust,no_run @@ -313,6 +318,10 @@ macro_rules! write_reg { /// # } /// ``` /// +/// To support register arrays, each macro form also supports one or more array indices after the +/// register. For example, `modify_reg!(stm32ral::gpio, gpioa, ODR[2], |reg| reg | (1<<3));` sets +/// a high bit in the third register of an `ODR` register array. +/// /// # Usage /// Like `write_reg!`, this macro can be used in two ways, either with a modification of the entire /// register, or by specifying which fields to change and what value to change them to. @@ -322,7 +331,8 @@ macro_rules! write_reg { /// * a reference to the instance of that peripheral: 'gpioa' (anything which dereferences to /// `RegisterBlock`, such as `Instance`, `&Instance`, `&RegisterBlock`, or /// `*const RegisterBlock`), -/// * the register you wish you access: `MODER` (a field on the `RegisterBlock`). +/// * the register (and offset, for arrays) you wish you access: `MODER` (a field on the +/// `RegisterBlock`). /// /// In the whole-register usage, the final argument is a closure that accepts the current value /// of the register and returns the new value to write: @@ -453,6 +463,10 @@ macro_rules! modify_reg { /// # } /// ``` /// +/// To support register arrays, each macro form also supports one or more array indices after the +/// register. For example, `read_reg!(stm32ral::gpio, gpioa, ODR[2]);` reads from the third +/// register of an `ODR` register array. +/// /// # Usage /// Like `write_reg!`, this macro can be used multiple ways, either reading the entire register or /// reading a one or more fields from it and potentially performing a comparison with one field. @@ -462,7 +476,8 @@ macro_rules! modify_reg { /// * a reference to the instance of that peripheral: 'gpioa' (anything which dereferences to /// `RegisterBlock`, such as `Instance`, `&Instance`, `&RegisterBlock`, or /// `*const RegisterBlock`), -/// * the register you wish to access: `IDR` (a field on the `RegisterBlock`). +/// * the register (and offset, for arrays) you wish to access: `IDR` (a field on the +/// `RegisterBlock`). /// /// In the whole-register usage, the macro simply returns the register's value: /// ```rust,no_run @@ -567,6 +582,10 @@ macro_rules! read_reg { /// # } /// ``` /// +/// To support register arrays, each macro form also supports one or more array indices after +/// the register. For example, `reset_reg!(stm32ral::gpio, gpioa, GPIOA, ODR[2]);` resets the +/// third register in an `ODR` register array. +/// /// # Usage /// Like `write_reg!`, this macro can be used in two ways, either resetting the entire register /// or just resetting specific fields within in. The register or fields are written with their @@ -578,7 +597,8 @@ macro_rules! read_reg { /// `RegisterBlock`, such as `Instance`, `&Instance`, `&RegisterBlock`, or /// `*const RegisterBlock`), /// * the module for the instance of that peripheral: `GPIOA`, -/// * the register you wish to access: `MODER` (a field on the `RegisterBlock`). +/// * the register (and offset, for arrays) you wish to access: `MODER` (a field on the +/// `RegisterBlock`). /// /// In the whole-register usage, that's it: /// ```rust,no_run -- cgit v1.2.3