外设作为状态机
微控制器的外围设备可以认为是一组状态机。例如,简化的GPIO引脚的配置可以表示为以下状态树:
- 禁用
- 已启用
- 配置为输出
- 输出:高
- 输出:低
- 配置为输入
- 输入:高电阻
- 输入:拉低
- 输入:拉高
- 配置为输出
如果外围设备以“禁用”模式启动,想要转移至“输入:高电阻”模式,我们必须执行以下步骤:
- 禁用模式
- 启用
- 配置为输入
- 输入:高电阻
如果要从“输入:高电阻”转移至“输入:拉低”,则必须执行以下步骤:
- 输入:高阻
- 输入:拉低
同样,如果要将GPIO引脚从“输入:拉低”转移到“输出:高”,则必须执行以下步骤:
- 输入:拉低
- 配置为输入
- 配置为输出
- 输出:高
硬件表示
通常,上面列出的状态是通过将修改映射到GPIO外设的寄存器来设置的。让我们虚拟一个的PIO配置寄存器来说明这一点:
| 名字 | 位号 | 值 | 含义 | 注意事项 |
|---|---|---|---|---|
| 启用 | 0 | 0 | 禁用 | 禁用GPIO |
| 1 | 启用 | 启用GPIO | ||
| 方向 | 1 | 0 | 输入 | 将方向设置为输入 |
| 1 | 输出 | 将方向设置为输出 | ||
| 输入模式 | 2..3 | 00 | 高电阻 | 将输入设置为高阻 |
| 01 | 拉低 | 输入引脚被拉低 | ||
| 10 | 拉高 | 输入引脚被拉高 | ||
| 11 | 状态无效 | 不设 | ||
| 输出模式 | 4 | 0 | 低 | 引脚被驱动为低电平 |
| 1 | 高 | 输出引脚被驱动为高电平 | ||
| 输入状态 | 5 | x | 输入值 | 如果输入<1.5v,则为0;如果输入> = 1.5v,则为1 |
我们可以在Rust中定义以下结构来控制此GPIO:
/// GPIO interface
struct GpioConfig {
/// GPIO Configuration structure generated by svd2rust
periph: GPIO_CONFIG,
}
impl GpioConfig {
pub fn set_enable(&mut self, is_enabled: bool) {
self.periph.modify(|_r, w| {
w.enable().set_bit(is_enabled)
});
}
pub fn set_direction(&mut self, is_output: bool) {
self.periph.modify(|r, w| {
w.direction().set_bit(is_output)
});
}
pub fn set_input_mode(&mut self, variant: InputMode) {
self.periph.modify(|_r, w| {
w.input_mode().variant(variant)
});
}
pub fn set_output_mode(&mut self, is_high: bool) {
self.periph.modify(|_r, w| {
w.output_mode.set_bit(is_high)
});
}
pub fn get_input_status(&self) -> bool {
self.periph.read().input_status().bit_is_set()
}
}
但是,这能使我们对寄存器做没有任何意义的修改。例如在GPIO配置为输入时,修改输出模式(表中位4)会发生什么?
一般来说,这个结构体的定义虽然可以工作,但是让我们很容易进入没有定义的状态,比如输出模式时,输入引脚被配置为拉低;输入模式时,输出引脚被配置为高。对于某些硬件,这可能无所谓,但是在其他硬件上,这可能会导致意外或未定义的行为!
总的来说,尽管编写该接口很方便,但是它并没有严格遵循硬件的设计约定(design contracts)。