Peripherals as State Machines - The Embedded Rust Book

# Peripherals as State Machines

The peripherals of a microcontroller can be thought of as set of state machines. For example, the configuration of a simplified GPIO pin could be represented as the following tree of states:

- Disabled
- Enabled
  - Configured as Output
    - Output: High
    - Output: Low
  - Configured as Input
    - Input: High Resistance
    - Input: Pulled Low
    - Input: Pulled High

If the peripheral starts in the `Disabled` mode, to move to the `Input: High Resistance` mode, we must perform the following steps:

1. Disabled
2. Enabled
3. Configured as Input
4. Input: High Resistance

If we wanted to move from `Input: High Resistance` to `Input: Pulled Low`, we must perform the following steps:

1. Input: High Resistance
2. Input: Pulled Low

Similarly, if we want to move a GPIO pin from configured as `Input: Pulled Low` to `Output: High`, we must perform the following steps:

1. Input: Pulled Low
2. Configured as Input
3. Configured as Output
4. Output: High

## Hardware Representation

Typically the states listed above are set by writing values to given registers mapped to a GPIO peripheral. Let's define an imaginary GPIO Configuration Register to illustrate this:

Name | Bit Number(s) | Value | Meaning | Notes
--- | --- | --- | --- | ---
enable | 0 | 0 | disabled | Disables the GPIO
|  | 1 | enabled | Enables the GPIO
direction | 1 | 0 | input | Sets the direction to Input
|  | 1 | output | Sets the direction to Output
input_mode | 2..3 | 00 | hi-z | Sets the input as high resistance
|  | 01 | pull-low | Input pin is pulled low
|  | 10 | pull-high | Input pin is pulled high
|  | 11 | n/a | Invalid state. Do not set
output_mode | 4 | 0 | set-low | Output pin is driven low
|  | 1 | set-high | Output pin is driven high
input_status | 5 | x | in-val | 0 if input is < 1.5v, 1 if input >= 1.5v

We could expose the following structure in Rust to control this 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()
        }
    }

However, this would allow us to modify certain registers that do not make sense. For example, what happens if we set the `output_mode` field when our GPIO is configured as an input?

In general, use of this structure would allow us to reach states not defined by our state machine above: e.g. an output that is pulled low, or an input that is set high. For some hardware, this may not matter. On other hardware, it could cause unexpected or undefined behavior!

Although this interface is convenient to write, it doesn't enforce the design contracts set out by our hardware implementation.