This crate provides a procedural macro for effortless definitions of registers
in embedded device drivers.
Currently, embedded-registers requires the use of #![feature(generic_arg_infer)].
Attribute macro
Registers are defined by adding #[register(...)] to the definition of a bondrewd bitfield.
As a short reminder, bondrewd is another proc macro that allows you to define a bitfield structure,
which is very handy when dealing with registers, where multiple values are often tightly packed bit-on-bit.
The register attribute macro supports the following arguments:
Adding this attribute to a struct Foo will result in two types being defined:
Foo will become the register, essentially a byte array with the correct size that provides
getter and setter functions for the individual fields.
FooBitfield will become the underlying bondrewd bitfield, which may be used to construct
a register from scratch, or can be obtained via Foo::read_all if you want to unpack all values.
This has the advantage that reading a register incurs no additional memory and CPU cost to unpack all
values of the bitfield. You only pay for the members you actually access.
Simple Example
This simple example defines the DeviceId register of an MCP9808. It has the virtual address
0b111 (0x7), uses big endian byte order with the first member of the struct positioned at the
most significant bit, is 2 bytes in size and is read-only. The register definition
automatically work with both sync and async code.
#![feature(generic_arg_infer)]
use embedded_registers::{register, i2c::{I2cDeviceAsync, I2cDeviceSync, codecs::OneByteRegAddrCodec}, RegisterInterfaceAsync, RegisterInterfaceSync, ReadableRegister};
#[register(address = 0b111, mode = "r")]
#[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)]
pub struct DeviceId {
device_id: u8,
revision: u8,
}
# async fn test<I>(mut i2c: I) -> Result<(), I::Error>
# where
# I: embedded_hal::i2c::I2c + embedded_hal::i2c::ErrorType
# {
let mut dev = I2cDeviceSync::<_, _, OneByteRegAddrCodec>::new(i2c , 0x24 );
let reg = dev.read_register::<DeviceId>()?;
# Ok(())
# }
# async fn test_async<I>(mut i2c: I) -> Result<(), I::Error>
# where
# I: embedded_hal_async::i2c::I2c + embedded_hal_async::i2c::ErrorType
# {
let mut dev = I2cDeviceAsync::<_, _, OneByteRegAddrCodec>::new(i2c , 0x24 );
let reg = dev.read_register::<DeviceId>().await?;
# Ok(())
# }
Complex Example
A real-world application may involve describing registers with more complex layouts involving
different data types or even enumerations. Luckily, all of this is fairly simple with bondrewd.
We also make sure to annotate all fields with #[register(default = ...)] to allow
easy reconstruction of the power-up defaults. Have a look at this excerpt
of the Configuration register from the MCP9808:
#![feature(generic_arg_infer)]
# use defmt::{info, Format};
use embedded_registers::{register, i2c::{I2cDeviceAsync, codecs::OneByteRegAddrCodec}, RegisterInterfaceAsync, ReadableRegister, WritableRegister};
use bondrewd::BitfieldEnum;
# #[allow(non_camel_case_types)]
#[derive(BitfieldEnum, Clone, PartialEq, Eq, Debug, Format)]
#[bondrewd_enum(u8)]
pub enum Hysteresis {
Deg_0_0C = 0b00,
Deg_1_5C = 0b01,
Deg_3_0C = 0b10,
Deg_6_0C = 0b11,
}
#[derive(BitfieldEnum, Clone, PartialEq, Eq, Debug, Format)]
#[bondrewd_enum(u8)]
pub enum ShutdownMode {
Continuous = 0,
Shutdown = 1,
}
#[register(address = 0b001, mode = "rw")]
#[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)]
pub struct Config {
#[bondrewd(bit_length = 5, reserve)]
#[allow(dead_code)]
reserved: u8,
#[bondrewd(enum_primitive = "u8", bit_length = 2)]
#[register(default = Hysteresis::Deg_0_0C)]
pub hysteresis: Hysteresis,
#[bondrewd(enum_primitive = "u8", bit_length = 1)]
#[register(default = ShutdownMode::Continuous)]
pub shutdown_mode: ShutdownMode,
# #[bondrewd(bit_length = 8, reserve)]
# #[allow(dead_code)]
# reserved2: u8,
}
# async fn test<I>(mut i2c: I) -> Result<(), I::Error>
# where
# I: embedded_hal_async::i2c::I2c + embedded_hal_async::i2c::ErrorType
# {
# let mut dev = I2cDeviceAsync::<_, _, OneByteRegAddrCodec>::new(i2c, 0x24);
let mut reg = dev.read_register::<Config>().await?;
info!("previous shutdown mode: {}", reg.read_shutdown_mode());
reg.write_shutdown_mode(ShutdownMode::Shutdown);
dev.write_register(®).await?;
let mut bf = reg.read_all();
info!("previous shutdown mode: {}", bf.shutdown_mode);
bf.shutdown_mode = ShutdownMode::Shutdown;
reg.write_all(bf);
# Ok(())
# }