2 个版本
0.1.1 | 2019年9月24日 |
---|---|
0.1.0 | 2019年9月24日 |
在 硬件支持 中排名第 508
每月下载量 21 次
290KB
656 行
有界寄存器
概述
一个高保障的内存映射寄存器代码生成和交互库。
入门指南
$ git clone [email protected]:auxoncorp/registers.git
$ cd registers && cargo install
使用方法
宏定义
register! {
Status,
u8,
RW,
Fields [
On WIDTH(U1) OFFSET(U0),
Dead WIDTH(U1) OFFSET(U1),
Color WIDTH(U3) OFFSET(U2) [
Red = U1,
Blue = U2,
Green = U3,
Yellow = U4
]
]
}
register!
宏生成用于优雅地访问和操作寄存器的代码。宏的预期输入如下
- 寄存器名称。
- 其数值类型。
- 其模式,可以是
RO
(只读)、RW
(读写) 或WO
(只写)。 - 寄存器的字段,从
Fields [
开始,并以一个结尾的]
结束。
字段由其名称、宽度以及在寄存器内的偏移量组成。可选地,也可以在字段声明中声明类似枚举的键/值对,嵌套在 []
内。
此宏生成的代码是一个嵌套模块的树,其根是一个名为 $register_name
的模块。在 $register_name
中,将包含寄存器本身,作为 $register_name::Register
,以及每个字段的子模块。
在每个字段模块中,可以找到字段本身,作为 $register_name::$field_name::Field
,以及一些有用的别名和常量。
$register_name::$field_name::Read
:为了读取一个字段,必须提供一个该字段的实例以访问其掩码和偏移量。Read
可以用作get_field
的参数,这样在读取时就不必构造一个任意的实例。$register_name::$field_name::Clear
:值为零的字段。将其传递给modify
将清除寄存器中的该字段。$register_name::$field_name::$field_max
:值为$field_max
的字段。将其传递给modify
将设置寄存器中该字段的最大值。这在单比特宽字段的情况下特别有用。$register_name::$field_name::$enum_kvs
:将枚举字段名称映射到值的常量。
与寄存器交互
通过构造函数
fn main() {
let mut reg = Status::Register::new(0);
reg.modify(Status::Dead::Set);
assert_eq!(reg.read(), 2);
}
在这个例子中,我们用一个值为0
的寄存器初始化,然后设置Dead
位——第二个字段——当将这个字大小的寄存器解释为u32
时,应该产生2
的值。
通过寄存器块
这里我们取一个已知的地址,我们可能在开发手册中找到的地址,将这个地址解释为寄存器块。然后我们可以取消引用这个指针,并使用寄存器API来访问块中的寄存器。
register! {
UartRX,
RO,
Fields [
Data WIDTH(U8) OFFSET(U0),
ParityError WIDTH(U1) OFFSET(U10),
Brk WIDTH(U1) OFFSET(U11),
FrameError WIDTH(U1) OFFSET(U12),
Overrrun WIDTH(U1) OFFSET(U13),
Error WIDTH(U1) OFFSET(U14),
ChrRdy WIDTH(U1) OFFSET(U15)
]
}
register! {
UartTX,
WO,
Fields [
Data WIDTH(U8) OFFSET(U0)
]
}
register! {
UartControl1,
RW,
Fields [
Enable WIDTH(U1) OFFSET(U0),
Doze WIDTH(U1) OFFSET(U1),
AgingDMATimerEnable WIDTH(U1) OFFSET(U2),
TxRdyDMAENable WIDTH(U1) OFFSET(U3),
SendBreak WIDTH(U1) OFFSET(U4),
RTSDeltaInterrupt WIDTH(U1) OFFSET(U5),
TxEmptyInterrupt WIDTH(U1) OFFSET(U6),
Infrared WIDTH(U1) OFFSET(U7),
RecvReadyDMA WIDTH(U1) OFFSET(U8),
RecvReadyInterrupt WIDTH(U1) OFFSET(U9),
IdleCondition WIDTH(U2) OFFSET(U10),
IdleInterrupt WIDTH(U1) OFFSET(U12),
TxReadyInterrupt WIDTH(U1) OFFSET(U13),
AutoBaud WIDTH(U1) OFFSET(U14),
AutoBaudInterrupt WIDTH(U1) OFFSET(U15)
]
}
然后,你可以为保持这种寄存器块地址的类型实现Deref
和DerefMut
。这为方法查找(在类型检查期间)填补了空白,这样你就可以舒适地使用这种类型与寄存器块交互。
#[repr(C)]
pub struct UartBlock {
rx: UartRX::Register,
_padding1: [u32; 15],
tx: UartTX::Register,
_padding2: [u32; 15],
control1: UartControl1::Register,
}
pub struct Regs {
addr: usize,
}
impl Deref for Regs {
type Target = UartBlock;
fn deref(&self) -> &UartBlock {
unsafe { &#(self.addr as #const UartBlock) }
}
}
impl DerefMut for Regs {
fn deref_mut(&mut self) -> &mut UartBlock {
unsafe { &mut #(self.addr as #mut UartBlock) }
}
}
fn main() {
// A pretend register block.
let mut x = [0_u32; 33];
let mut regs = Regs {
// Some shenanigans to get at `x` as though it were a
// pointer. Normally you'd be given some address like
// `0xDEADBEEF` over which you'd instantiate a `Regs`.
addr: &mut x as #mut [u32; 33] as usize,
};
assert_eq!(regs.rx.read(), 0);
regs.control1
.modify(UartControl1::Enable::Set + UartControl1::RecvReadyInterrupt::Set);
// The first bit and the 10th bit should be set.
assert_eq!(regs.control1.read(), 0b_10_0000_0001);
}
寄存器API
寄存器API代码带有文档,但您需要为使用bounded-registers
的库构建rustdoc文档才能查看它。为了方便,我在这里进行了扩展。
impl Register {
/// `new` constructs a read-write register around the
/// given pointer.
pub fn new(init: Width) -> Self;
/// `get_field` takes a field and sets the value of that
/// field to its value in the register.
pub fn get_field<M: Unsigned, O: Unsigned, U: Unsigned>(
&self,
f: F<Width, M, O, U, Register>,
) -> Option<F<Width, M, O, U, Register>>
where
U: IsGreater<U0, Output = True> + ReifyTo<Width>,
M: ReifyTo<Width>,
O: ReifyTo<Width>,
U0: ReifyTo<Width>;
/// `read` returns the current state of the register as a `Width`.
pub fn read(&self) -> Width;
/// `extract` pulls the state of a register out into a wrapped
/// read-only register.
pub fn extract(&self) -> ReadOnlyCopy<Width, Register>;
/// `is_set` takes a field and returns true if that field's value
/// is equal to its upper bound or not. This is of particular use
/// in single-bit fields.
pub fn is_set<M: Unsigned, O: Unsigned, U: Unsigned>(
&self,
f: F<Width, M, O, U, Register>,
) -> bool
where
U: IsGreater<U0, Output = True>,
U: ReifyTo<Width>,
M: ReifyTo<Width>,
O: ReifyTo<Width>;
// `Positioned` is a special trait that all fields implement, as
// well as a type used as an accumulator when reading from or
// writing to multiple fields. To use these functions with
// multiple fields, join them together with `+`. An `Add`
// implementation for fields has been provided for this purpose.
/// `matches_any` returns whether or not any of the given fields
/// match those fields values inside the register.
pub fn matches_any<V: Positioned<Width = Width>>(&self, val: V) -> bool;
/// `matches_all` returns whether or not all of the given fields
/// match those fields values inside the register.
pub fn matches_all<V: Positioned<Width = Width>>(&self, val: V) -> bool;
/// `modify` takes one or more fields, joined by `+`, and
/// sets those fields in the register, leaving the others
/// as they were.
pub fn modify<V: Positioned<Width = Width>>(&mut self, val: V);
/// `write` sets the value of the whole register to the
/// given `Width` value.
pub fn write(&mut self, val: Width);
}
理论
bounded-registers
使用值——具体来说是数字——在类型级别上,以便在编译时对与寄存器的交互进行断言。每个字段的宽度用于确定最大值,然后从这些字段读取和写入是通过checked
函数在编译时检查,或者预期/携带/一个证明,该证明使用上述界限在运行时构建一个已知不违反界限的值。
许可
bounded-registers
根据MIT许可证(MIT)许可,除非另有说明。请参阅LICENSE以获取更多详细信息。