1 stable release
Uses new Rust 2024
| 1.0.0 | Jul 31, 2025 |
|---|
#1231 in Embedded development
255 downloads per month
Used in 16 crates
54KB
1K
SLoC
st-mem-bank-macro
This crate simplifies implementing memory state management for sensors. Some sensors have a register (called MemBank) that can change the address space, enabling access on special registers while restricting access to others during that state. Additionally, it streamlines register access over communication buses such as I2C or SPI.
mem_bank Macro
The mem_bank macro is applied to an enum representing the sensor's memory states. It uses annotations to provide metadata about each state.
Variant Annotations
#[main]: Marks the default (main) state.#[state(StructName, fn_name = "...")]: For other states, specifies:StructName: A struct representing the state (used internally).fn_name: A function is generate with that name, it handles the state transition via a closure. The closure moves into the specified state and then returns to the main state.
Enum Annotation
Annotate the entire enum as:
#[mem_bank(SensorName, generics = N)]
SensorName: This is a link to the struct that represent the main sensor lib.- 'generics': Number of generic parameters (1 or 2). The sensor must be compatible, typically including a
BusOperationtrait (the bus crate) and optionally aDelayNstrait from embedded_hal.
Generated Code
- Creates a struct holding a mutable reference to the sensor instance.
- Generates functions to instantiate this struct, enabling state transitions.
- Implements a state mechanism controlling register access per state.
Note: The fn_name closure wraps all errors. If an error occurs during the clousure execution a return to the main MemBank is tried, if fails the error returned in the MemBank, it takes precedence over others.
register macro
The register macro works alongside the bitfield-struct crate. The sensor must implement these two functions:
pub fn write_to_register(&mut self, reg: u8, buf: &[u8]) -> Result<(), Error<B::Error>> {
self.bus.write_to_register(reg, buf).map_err(Error::Bus)
}
pub fn read_from_register(&mut self, reg: u8, buf: &mut [u8]) -> Result<(), Error<B::Error>> {
self.bus.read_from_register(reg, buf).map_err(Error::Bus)
}
Suggested implementation considers an attribute (like the bus in the example) that is generic over the BusOperation trait, as it already provides default implementations for write_to_register and read_from_register.
Usage
Annotate a struct representing a register with: #[register(address = ..., access_type = ..., generics = ...)]
All three attributes are mandatory:
- address: The register address used in read/write calls.
- access_type: The scope where the register is accessible: either the sensor struct or a
mem_bankstate. - generics: Must match the generics count used in the
mem_bankmacro.
Example:
#[register(address = Reg::Ctrl, access_type = Lsm6dsv16x, generics = 2)]
#[cfg_attr(feature = "bit_order_msb", bitfield(u8, order = Msb))]
#[cfg_attr(not(feature = "bit_order_msb"), bitfield(u8, order = Lsb))]
pub struct Ctrl {
#[bits(1)]
pub feature1: bool,
...
}
Using the register in sensor code:
impl<B, T> Lsm6dsv16x<B, T> where B: BusOperation, T: DelayNs {
pub fn activate_feature1(&mut self) -> Result<(), Error> {
let mut reg = Ctrl::read(self)?;
reg.set_feature1(true); // Generated by bitfield-struct
Ctrl::write(self)
}
}
Additional Attributes
- init_fn: Specifies a function to initialize non-primitive types.
- override_type: Overrides the type used for to_le_bytes() and from_le_bytes() conversions.
- order: Defines byte order when the struct spans multiple registers (e.g., use to_be_bytes instead of to_le_bytes).
Advanced use cases
Some edge uses cases are covered, for example:
- Values defined on more reigsters with 3 axis: X, Y, Z axis over i16:
#[register(address = Reg::OutxLG, access_type = Lsm6dsv16x, generics = 2)]
pub struct OutXYZG(pub [i16; 3]);
- Values defined on more reigsters and accessed with named fields:
#[named_register(address = Reg::OutxLG, access_type = Lsm6dsv16x, generics = 2)]
pub struct OutXYZ {
pub x: i16,
pub y: i16,
pub z: i16
}
- custom struct: Each byte is converted into the custom struct.
#[register(address = EmbReg::FsmOuts1, access_type = EmbedFuncState, init_fn = FsmOutsElement::new, generics = 2)]
pub struct FsmOut(pub [FsmOutsElement; 8]);
#[register(address = EmbReg::FsmOuts1, access_type = EmbedFuncState, generics = 2)]
#[cfg_attr(feature = "bit_order_msb", bitfield(u8, order = Msb))]
#[cfg_attr(not(feature = "bit_order_msb"), bitfield(u8, order = Lsb))]
pub struct FsmOutsElement {
#[bits(1)]
pub fsm_n_v: u8,
...
}
impl FsmOutsElement {
pub fn from_le_bytes(val: [u8; 1]) -> Self {
FsmOutsElement(val[0])
}
pub fn to_le_bytes(&self) -> [u8; 1] {
[self.0]
}
}
- bitfield over more bytes: The bitfield accepts only u16, but inner type could be signed, conversion is handled.
order = Reverseparameter could be supplied to reverse how registers are read or write.
#[register(address = Reg::OutXL, access_type = Lsm6dsv16x, generics = 2)]
#[cfg_attr(feature = "bit_order_msb", bitfield(u16, order = Msb))]
#[cfg_attr(not(feature = "bit_order_msb"), bitfield(u16, order = Lsb))]
pub struct OutX {
#[bits(16, access = RO)]
pub outx: i16,
}
- Last parameters are applied inside the struct. Allowing the offset for the register that provide only msb/lsb part:
- offset_before(#bit_number): the conversion for multibyte type like (u16/i16/u32/i32) has an offset corresponding to the bit_number. For example having #bit_number = 8 and i16 the conversion happens as: i16::from_le_bytes([0, reg]);
- offset_after(#bit_number): Do the same but apply the offset on the opposite part of the array
adv_register
This macro is equivalent to register and supports the same attributes, with the addition of a required base_address attribute that specifies the base page of embedded advanced functions. In this case, reads and writes are performed using ln_pg_write and ln_pg_read methods. It is recommended to implement the EmbAdvFunctions trait from the bus crate to support this functionality.
An example is:
#[adv_register(base_address = AdvPage::_0, address = EmbAdv0Reg::SflpGameGbiasxL, access_type = Lsm6dsv16x, generics = 2)]
pub struct SflpGameGbiasXYZ(pub [u16; 3]);
Dependencies
~165–580KB
~14K SLoC