Периферийные устройства как конечные автоматы
Периферийные устройства микроконтроллера можно рассматривать как набор конечных автоматов. Например, конфигурация упрощенного [GPIO-пина] может быть представлена следующим деревом состояний:
- Отключен
- Включен
- Настроен как выход
- Выход: Высокий
- Выход: Низкий
- Настроен как вход
- Вход: Высокое сопротивление
- Вход: Подтяжка вниз
- Вход: Подтяжка вверх
- Настроен как выход
Если периферийное устройство начинается в режиме Отключен, для перехода в режим Вход: Высокое сопротивление необходимо выполнить следующие шаги:
- Отключен
- Включен
- Настроен как вход
- Вход: Высокое сопротивление
Если мы хотим перейти из Вход: Высокое сопротивление в Вход: Подтяжка вниз, необходимо выполнить следующие шаги:
- Вход: Высокое сопротивление
- Вход: Подтяжка вниз
Аналогично, если мы хотим перевести GPIO-пин из режима Вход: Подтяжка вниз в Выход: Высокий, необходимо выполнить следующие шаги:
- Вход: Подтяжка вниз
- Настроен как вход
- Настроен как выход
- Выход: Высокий
Аппаратное представление
Обычно перечисленные выше состояния устанавливаются путем записи значений в заданные регистры, отображенные на периферийное устройство GPIO. Давайте определим воображаемый регистр конфигурации GPIO для иллюстрации:
| Имя | Бит(ы) | Значение | Значение | Примечания |
|---|---|---|---|---|
| enable | 0 | 0 | отключено | Отключает GPIO |
| 1 | включено | Включает GPIO | ||
| direction | 1 | 0 | вход | Устанавливает направление на вход |
| 1 | выход | Устанавливает направление на выход | ||
| input_mode | 2..3 | 00 | высокое сопротивление | Устанавливает вход как высокое сопротивление |
| 01 | подтяжка вниз | Входной пин подтянут вниз | ||
| 10 | подтяжка вверх | Входной пин подтянут вверх | ||
| 11 | н/д | Недопустимое состояние. Не устанавливать | ||
| output_mode | 4 | 0 | установить низкий | Выходной пин притянут к низкому уровню |
| 1 | установить высокий | Выходной пин притянут к высокому уровню | ||
| input_status | 5 | x | входное значение | 0, если вход < 1.5 В, 1, если вход >= 1.5 В |
Мы могли бы предоставить следующую структуру в Rust для управления этим GPIO:
/// Интерфейс GPIO
struct GpioConfig {
/// Структура конфигурации GPIO, сгенерированная 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()
}
}
Однако это позволило бы нам изменять определенные регистры, что не имеет смысла. Например, что произойдет, если мы установим поле output_mode, когда наш GPIO настроен как вход?
В общем, использование этой структуры позволило бы нам достичь состояний, не определенных в нашем конечном автомате выше: например, выход, который подтянут вниз, или вход, который установлен на высокий уровень. Для некоторого оборудования это может не иметь значения. На другом оборудовании это может вызвать неожиданное или неопределенное поведение!
Хотя этот интерфейс удобен для написания, он не обеспечивает соблюдение контрактов проектирования, установленных нашей аппаратной реализацией.