#register #bit-fields #embedded-devices #derive #sensor

无std embedded-registers-derive

用于在嵌入式设备驱动程序中轻松定义寄存器的过程宏

9个版本

新增 0.9.9 2024年8月26日
0.9.8 2024年8月21日
0.9.6 2023年12月13日
0.9.3 2023年11月7日

#7 in #registers

Download history 5/week @ 2024-05-19 1/week @ 2024-05-26 1/week @ 2024-06-09 1/week @ 2024-06-16 4/week @ 2024-06-30 1/week @ 2024-07-28 140/week @ 2024-08-18

141 每月下载次数
embedded-registers 中使用

MIT/Apache

18KB
228 代码行数(不包括注释)

该软件包提供了一种过程宏,用于在嵌入式设备驱动程序中轻松定义寄存器。

目前,embedded-registers需要您使用 #![feature(generic_arg_infer)]

属性宏

通过在bondrewd位域的定义中添加 #[register(...)] 来定义寄存器。作为一个简短的提醒,bondrewd 是另一个过程宏,允许您定义位域结构,这在处理寄存器时非常有用,因为多个值通常紧密地按位排列。

寄存器属性宏支持以下参数

address 与寄存器关联的虚拟地址。
read 如果寄存器应该是可读的,则添加此参数
write 如果寄存器应该是可写的,则添加此参数

将此属性添加到bondrewd结构 Foo 将导致定义两个类型

  • Foo 将成为寄存器,本质上是一个具有正确大小的字节数组,为单个字段提供获取和设置函数。
  • FooBitfield 将成为底层的bondrewd位域,可以用来从头开始构建寄存器,或者可以通过 Foo::read_all 获取,如果您想解包所有值。

这的好处是读取寄存器不会产生额外的内存和CPU成本来解包位域的所有值。您只为实际访问的成员付费。

简单示例

这个简单的例子定义了MCP9808的DeviceId寄存器。它具有虚拟地址 0b111 (0x7),使用大端字节序,结构体成员的第一个字节位于最高位,大小为2字节,且为只读。

#![feature(generic_arg_infer)]
use embedded_registers::{register, i2c::{I2cDevice, codecs::OneByteRegAddrCodec}, RegisterInterface, ReadableRegister};

#[register(address = 0b111, mode = "r")]
#[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)]
pub struct DeviceId {
    device_id: u8,
    revision: u8,
}

// The register may now be read from an I2C bus using sync or async operations:
// Create new I2cDevice with bus, address, codec
let mut dev = I2cDevice::new(i2c, 0x24, OneByteRegAddrCodec::default());
let reg = dev.read_register::<DeviceId>().await?;
// sync: let reg = DeviceId::read_i2c_blocking(&mut i2c, address);

复杂示例

更复杂的示例可能涉及添加自己的Bondrewd支持枚举。我们还确保用 #[default] 标注正确的字段,以便轻松重建上电默认值。请看MCP9808配置寄存器的这个摘录。

#![feature(generic_arg_infer)]
use embedded_registers::{register, i2c::{I2cDevice, codecs::OneByteRegAddrCodec}, RegisterInterface, ReadableRegister, WritableRegister};
use bondrewd::BitfieldEnum;

#[derive(BitfieldEnum, Clone, Default, PartialEq, Eq, Debug, Format)]
#[bondrewd_enum(u8)]
pub enum Hysteresis {
    #[default]
    Deg_0_0C = 0b00,
    Deg_1_5C = 0b01,
    Deg_3_0C = 0b10,
    Deg_6_0C = 0b11,
}

#[derive(BitfieldEnum, Clone, Default, PartialEq, Eq, Debug, Format)]
#[bondrewd_enum(u8)]
pub enum ShutdownMode {
    #[default]
    Continuous = 0,
    Shutdown = 1,
}

#[register(address = 0b001, mode = "rw")]
#[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)]
pub struct Config {
    // padding
    #[bondrewd(bit_length = 5, reserve)]
    #[allow(dead_code)]
    reserved: u8,

    #[bondrewd(enum_primitive = "u8", bit_length = 2)]
    pub hysteresis: Hysteresis,
    #[bondrewd(enum_primitive = "u8", bit_length = 1)]
    pub shutdown_mode: ShutdownMode,

    // ... all 16 bits must be filled
    # #[bondrewd(bit_length = 8, reserve)]
    # #[allow(dead_code)]
    # reserved2: u8,
}

// This now allows us to read and write the register, while only
// unpacking the fields we require:
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(&reg).await?;

// If you want to get the full decoded bitfield, you can use either `read_all`
// or `.into()`. If you need to unpack all fields anyway, this might be
// more convenient as it allows you to access the bitfield members more ergonomically.
//let bf: ConfigBitfield = reg.into();
let mut bf = reg.read_all();
info!("previous shutdown mode: {}", bf.shutdown_mode);
bf.shutdown_mode = ShutdownMode::Shutdown;
reg.write_all(bf);

依赖项

~1–1.5MB
~32K SLoC