embedded_registers/lib.rs
1//! Defines traits for embedded-registers-derive.
2//! For Derive Docs see [embedded-registers-derive](https://docs.rs/embedded-registers-derive/latest/embedded_registers_derive/).
3//!
4//! This crate provides a procedural macro for effortless definitions of registers
5//! in embedded device drivers. This is automatically generates functions to read/write
6//! the register over I2C and SPI, although it isn't limited to those buses. The
7//! resulting struct may be trivially extended to work with any other similar communication bus.
8//!
9//! - Allows defintion of read-only, write-only and read-write registers
10//! - Generates I2C and SPI read/write functions
11//! - Registers are defined as bitfields via [bondrewd](https://github.com/Devlyn-Nelson/Bondrewd).
12//! - Only the accessed bitfield members are decoded, conserving memory and saving on CPU time.
13//! - Supports both async and blocking operation modes simultaneously by generating two versions of
14//! each driver using [maybe_async_cfg](https://docs.rs/maybe-async-cfg/latest/maybe_async_cfg)
15//!
16//! This crate was made primarily for [embedded-devices](https://github.com/oddlama/embedded-devices),
17//! which is a collection of drivers for a variety of different embedded sensors and devices.
18//!
19//! ## Defining a register
20//!
21//! Registers are defined simply by annotating a bondrewd struct with `#[register(address = 0x42, mode = "rw")]`.
22//! The necessary derive attribute for Bondrewd is added automatically.
23//! Take for example this 2-byte read-write register at device addresses `0x42,0x43`, which contains two `u8` values:
24//!
25//! ```
26//! #![feature(generic_arg_infer)]
27//! use embedded_registers::register;
28//!
29//! #[register(address = 0x42, mode = "rw")]
30//! #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)]
31//! pub struct ValueRegister {
32//! pub width: u8,
33//! pub height: u8,
34//! }
35//! ```
36//!
37//! This will create two structs called `ValueRegister` and `ValueRegisterBitfield`.
38//! The first will only contain a byte array `[u8; 2]` to store the packed register contents,
39//! and the latter will contain the unpacked actual members as defined above.
40//! You will always interface with a device using the packed data, which can be transferred over the bus as-is.
41//!
42//! The packed data contains methods to directly read/write the underlying storage array, which
43//! means you can only unpack what you need, saving resources.
44//!
45//! > I find it a bit misleading that the members written in `ValueRegister` end up in `ValueRegisterBitfield`.
46//! > So this might change in the future, but I currently cannot think of another design that is as simple
47//! > to use as this one right now. The issue is that we need a struct for the packed data and one for
48//! > the unpacked data. Since we usually deal with the packed data, and want to allow direct read/write
49//! > operations on the packed data for performance, the naming gets confusing quite quickly.
50//!
51//! ### Accessing a register (async)
52//!
53//! To access such a register, you need to obtain an interface that implements the `RegisterInterfaceAsync` trait.
54//! This crate already comes with an implementation of that trait for I2C and SPI devices called [`i2c::I2cDeviceAsync`] and [`spi::SpiDeviceAsync`] respectively.
55//! You may then read the register simply by calling [`read_register`](RegisterInterfaceAsync::read_register) or [`write_register`](RegisterInterfaceAsync::write_register) on that interface.
56//!
57//! ```rust, only_if(async)
58//! # #![feature(generic_arg_infer)]
59//! # use embedded_registers::register;
60//! # #[register(address = 0x42, mode = "rw")]
61//! # #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)]
62//! # pub struct ValueRegister {
63//! # pub width: u8,
64//! # pub height: u8,
65//! # }
66//! use embedded_registers::{i2c::{I2cDeviceAsync, codecs::OneByteRegAddrCodec}, RegisterInterfaceAsync};
67//!
68//! async fn init<I>(mut i2c_bus: I) -> Result<(), I::Error>
69//! where
70//! I: embedded_hal_async::i2c::I2c + embedded_hal_async::i2c::ErrorType,
71//! {
72//! // Imagine we already have constructed a device using
73//! // the i2c bus from your controller, a device address and default codec:
74//! let mut dev = I2cDeviceAsync::<_, _, OneByteRegAddrCodec>::new(i2c_bus, 0x12);
75//! // We can now retrieve the register
76//! let mut reg = dev.read_register::<ValueRegister>().await?;
77//!
78//! // Unpack a specific field from the register and print it
79//! println!("{}", reg.read_width());
80//! // If you need all fields (or are not bound to tight resource constraints),
81//! // you can also unpack all fields and access them more conveniently
82//! let data = reg.read_all();
83//! // All bitfields implement Debug and defmt::Format, so you can conveniently
84//! // print the contents
85//! println!("{:?}", data);
86//!
87//! // We can also change a single value
88//! reg.write_height(190);
89//! // Or pack a bitfield and replace everything
90//! reg.write_all(data); // same as reg = ValueRegister::new(data);
91//!
92//! // Which we can now write back to the device, given that the register is writable.
93//! dev.write_register(reg).await?;
94//! Ok(())
95//! }
96//! ```
97
98#![cfg_attr(not(feature = "std"), no_std)]
99#[cfg(not(any(feature = "sync", feature = "async")))]
100compile_error!("You must enable at least one of the create features `sync` or `async`");
101
102pub mod i2c;
103pub mod spi;
104
105// Re-exports for derive macro
106pub use bondrewd;
107pub use bytemuck;
108
109/// The basis trait for all registers. A register is a type
110/// that maps to a specific register on an embedded device and
111/// should own the raw data required for this register.
112///
113/// Additionally, a register knows the virtual address ossociated
114/// to the embedded device, and a bitfield representation of the
115/// data content.
116pub trait Register: Default + Clone + bytemuck::Pod {
117 /// The size of the register in bytes
118 const REGISTER_SIZE: usize;
119 /// The virtual address of this register
120 const ADDRESS: u64;
121
122 /// The associated bondrewd bitfield type
123 type Bitfield;
124 /// The SPI codec that should be used for this register when no
125 /// codec is specified by the user. Setting this to `spi::codecs::NoCodec`
126 /// will automatically cause accesses to use the device's default codec.
127 /// If the device doesn't support SPI communication, this can be ignored.
128 #[cfg(all(feature = "sync", not(feature = "async")))]
129 type SpiCodec: spi::CodecSync;
130 #[cfg(all(not(feature = "sync"), feature = "async"))]
131 type SpiCodec: spi::CodecAsync;
132 #[cfg(all(feature = "sync", feature = "async"))]
133 type SpiCodec: spi::CodecSync + spi::CodecAsync;
134 /// The I2C codec that should be used for this register when no
135 /// codec is specified by the user. Setting this to `i2c::codecs::NoCodec`
136 /// will automatically cause accesses to use the device's default codec.
137 /// If the device doesn't support I2C communication, this can be ignored.
138 #[cfg(all(feature = "sync", not(feature = "async")))]
139 type I2cCodec: i2c::CodecSync;
140 #[cfg(all(not(feature = "sync"), feature = "async"))]
141 type I2cCodec: i2c::CodecAsync;
142 #[cfg(all(feature = "sync", feature = "async"))]
143 type I2cCodec: i2c::CodecSync + i2c::CodecAsync;
144
145 /// Provides immutable access to the raw data.
146 fn data(&self) -> &[u8];
147 /// Provides mutable access to the raw data.
148 fn data_mut(&mut self) -> &mut [u8];
149}
150
151/// This trait is a marker trait implemented by any register that can be read via a specific bus interface.
152pub trait ReadableRegister: Register {}
153
154/// This trait is a marker trait implemented by any register that can be written via a specific bus interface.
155pub trait WritableRegister: Register {}
156
157/// A trait that is implemented by any bus interface and allows
158/// devices with registers to share register read/write implementations
159/// independent of the actual interface in use.
160#[allow(async_fn_in_trait)]
161#[maybe_async_cfg::maybe(sync(feature = "sync"), async(feature = "async"))]
162pub trait RegisterInterface {
163 type Error;
164
165 /// Reads the given register via this interface
166 async fn read_register<R>(&mut self) -> Result<R, Self::Error>
167 where
168 R: ReadableRegister;
169
170 /// Writes the given register via this interface
171 async fn write_register<R>(&mut self, register: impl AsRef<R>) -> Result<(), Self::Error>
172 where
173 R: WritableRegister;
174}
175
176// re-export the derive stuff
177#[cfg(feature = "derive")]
178#[doc(hidden)]
179pub use embedded_registers_derive::*;