外设作为状态机

微控制器的外设可以被认为是状态机的集合。例如,简化的 GPIO 引脚的配置可以表示为以下状态树

  • 禁用
  • 启用
    • 配置为输出
      • 输出:高电平
      • 输出:低电平
    • 配置为输入
      • 输入:高阻抗
      • 输入:下拉
      • 输入:上拉

如果外设从 禁用 模式开始,要移动到 输入:高阻抗 模式,我们必须执行以下步骤

  1. 禁用
  2. 启用
  3. 配置为输入
  4. 输入:高阻抗

如果我们想从 输入:高阻抗 移动到 输入:下拉,我们必须执行以下步骤

  1. 输入:高阻抗
  2. 输入:下拉

类似地,如果我们想将 GPIO 引脚从配置为 输入:下拉 移动到 输出:高电平,我们必须执行以下步骤

  1. 输入:下拉
  2. 配置为输入
  3. 配置为输出
  4. 输出:高电平

硬件表示

通常,上面列出的状态是通过向映射到 GPIO 外设的给定寄存器写入值来设置的。让我们定义一个虚构的 GPIO 配置寄存器来说明这一点

名称位号含义备注
使能00禁用禁用 GPIO
1启用启用 GPIO
方向10输入将方向设置为输入
1输出将方向设置为输出
输入模式2..300高阻将输入设置为高阻抗
01下拉输入引脚被下拉
10上拉输入引脚被上拉
11不适用无效状态。请勿设置
输出模式40置低输出引脚被驱动为低电平
1置高输出引脚被驱动为高电平
输入状态5x输入值如果输入 < 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 配置为输入时,如果我们设置 output_mode 字段会发生什么?

通常,使用此结构将允许我们达到上面状态机未定义的状态:例如,一个被下拉的输出,或一个被设置为高电平的输入。对于某些硬件,这可能无关紧要。在其他硬件上,它可能会导致意外或未定义的行为!

虽然此接口编写起来很方便,但它并未强制执行我们的硬件实现设置的设计契约。