13个稳定版本
1.3.2 | 2024年3月2日 |
---|---|
1.2.2 | 2023年7月11日 |
1.1.3 | 2023年3月10日 |
1.1.2 | 2022年12月6日 |
1.1.1 | 2022年9月28日 |
在嵌入式开发类别中排名第209
每月下载量1,500次
用于5个crate(其中3个直接使用)
78KB
1.5K SLoC
bitbybit: 位字段和位枚举
此crate提供创建位字段和位枚举的宏,这些宏在位打包代码(例如在驱动程序或网络代码中)中非常有用。
一些亮点
- 高效且100%安全的代码,其性能与手写的位移和掩码一样好
- 完全兼容const上下文
- 可在no-std环境中使用
- 强大的编译时保证(例如,从一个位字段中取出5位放入另一个字段中甚至不需要编译边界检查)
- 自动创建位枚举,允许将枚举转换为数字或从数字转换为枚举
- 在位字段内支持数组以表示重复的位模式
基本声明
位字段创建方式类似于常规Rust结构。注解定义了结构的布局。以下是一个示例定义,它指定了一个位字段
#[bitfield(u32)]
struct GICD_TYPER {
#[bits(11..=15, r)]
lspi: u5,
#[bit(10, r)]
security_extn: bool,
#[bits(5..=7, r)]
cpu_number: u3,
#[bits(0..=4, r)]
itlines_number: u5,
}
这是如何工作的
- #[bitfield(u32)]指定这是一个位字段,其中u32是底层数据类型。这意味着位字段中的所有位都必须适合32位。支持内置Rust类型(u8、u16、u32、u64、u128)以及任意整数(u17、u48等)。
- 每个字段都注解了字段使用的位范围。数据类型必须匹配位数:与u8匹配的0..=8的范围会导致编译错误,因为u9是与0..=8匹配的数据类型。
- 单比特字段声明为"bit",其他所有字段声明为"bits"
- 字段的合法数据类型是基本类型u8、u16、u32、u64、u128、bool以及枚举(见下文)或类似u1、u2、u3的类型的arbitrary-int
- 位编号是LSB0,这意味着从底部开始计数位:bit(0)的值为0x1,bit(1)是0x2,bit(2)是0x4,bit(15)是0x8000,依此类推。如果需求,可以稍后添加MSB0模式(以匹配某些大端设备的文档)。
- 字段声明为“r”表示只读,为“w”表示只写,或为“rw”表示读写。在上面的示例中,所有字段都是只读的,因为该特定寄存器仅用于读取值。
枚举
很多时候,字段不仅仅是数字,而是真正的枚举。这可以通过首先定义一个bitenum,然后在位域中使用它来实现。
#[bitenum(u2, exhaustive = false)]
enum NonExhaustiveEnum {
Zero = 0b00,
One = 0b01,
Two = 0b10,
}
#[bitenum(u2, exhaustive = true)]
enum ExhaustiveEnum {
Zero = 0b00,
One = 0b01,
Two = 0b10,
Three = 0b11,
}
#[bitfield(u64, default = 0)]
struct BitfieldWithEnum {
#[bits(2..=3, rw)]
e2: Option<NonExhaustiveEnum>,
#[bits(0..=1, rw)]
e1: ExhaustiveEnum,
}
- bitenum宏将枚举转换为可以在位域中使用的枚举。第一个参数是基本数据类型,它指定了枚举占用多少位。这可以是u1到u64之间的任何值。
- 当枚举在位域中使用时,位数的匹配是必须的 - 如果不匹配,将会有编译器错误。
- exhaustive参数指定是否包含枚举中所有可能的位组合。上面的示例中有一个既是exhaustive又是non-exhaustive的枚举。注意,non-exhaustive枚举必须被Option包裹以处理e2不是定义的枚举值的情况。
数组
有时,位域内的位会被重复。为此,此crate允许指定位向量数组。例如,以下struct提供了对u64中每个单独的半字节(十六进制字符)的读写访问。
#[bitfield(u64, default = 0)]
struct Nibble64 {
#[bits(0..=3, rw)]
nibble: [u4; 16],
}
数组还可以有步长。这在多个较小值重复的情况下很有用。例如,以下定义提供了对每个半字节中每个位的访问。
#[bitfield(u64, default = 0)]
struct NibbleBits64 {
#[bit(0, rw, stride = 4)]
nibble_bit0: [bool; 16],
#[bit(1, rw, stride = 4)]
nibble_bit1: [bool; 16],
#[bit(2, rw, stride = 4)]
nibble_bit2: [bool; 16],
#[bit(3, rw, stride = 4)]
nibble_bit3: [bool; 16],
}
默认值
位域始终可以通过new_with_raw_value()创建。
let a = NibbleBits64::new_with_raw_value(0x43218765);
然而,通常特定的值可以被认为是默认值(例如0)。这可以这样指定。
#[bitfield(u32, default = 0x1234)]
struct Bitfield1 {
#[bits(0..=3, rw)]
nibble: [u4; 4],
}
如果指定了默认值,位域可以很容易地使用此特定值创建。
let a = Bitfield1::Default;
const A: Bitfield1 = Bitfield1::DEFAULT;
默认值按原样使用,即使它们影响了位域中未定义的位。
使用构建器语法一次设置所有字段
可以一次设置所有字段,如下所示
const T: Test = Test::builder()
.with_baudrate(0x12)
.with_some_other_bits(u4::new(0x2))
.with_array([1, 2, 3, 4])
.build();
使用builder()
,不可能忘记设置任何字段。这是在编译时检查的:如果任何字段未设置,则不能调用build()
。
目前,必须按照指定的顺序设置所有字段。随着Rust的const generics变得更加强大,这个限制可能会被取消。
要使builder()
可用,以下必须为真
- 位域必须完全填充可写字段(无间隙)或必须指定默认值。
- 没有可写字段重叠。
非连续位范围
有时,将位范围设置为非连续可能很有用。例如,RISC-V按某种方式定义了一些立即数,它们必须被重新组装。这可以这样实现。
#[bitfield(u32)]
struct SBFormat {
#[bits([8..=11, 25..=30, 7, 31], rw)]
imm_half: u12,
}
依赖关系
在Rust中目前不存在像u5或u67这样的任意位宽。因此,需要以下依赖项。
arbitrary-int = "1.2.7"
用法
尽管位域在某种程度上类似于结构体,但它们在内部作为像u32这样的简单数据类型实现。因此,它们提供了一个不可变接口:而不是改变字段的值,任何更改操作都将返回一个具有修改后字段的新的位域。
let a = NibbleBits64::new_with_raw_value(0x12345678_ABCDEFFF);
// Read a value
assert_eq!(u4::new(0xE), a.nibble(3));
// Change a value
let b = a.with_nibble(0, u4::new(0x3))
assert_eq!(0x12345678_ABCDEFF3, b.raw_value());
依赖关系
~335–780KB
~18K SLoC