Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Использование I2C в экосистеме embedded Rust

В предыдущем разделе мы изучили основы обмена по I2C и то, как работает модель «контроллер — целевое устройство» (ведущий — ведомый). Теперь посмотрим, как эти понятия применяются в экосистеме embedded Rust, где ключевой принцип — модульный и переиспользуемый дизайн.

Роль embedded-hal

Крейт embedded-hal определяет стандартный набор трейтов для абстракции аппаратного обеспечения, в том числе для I2C. Эти трейты позволяют писать драйверы (например, для дисплеев или датчиков) в обобщённом виде, чтобы они могли работать на многих микроконтроллерах без изменений, специфичных для платформы.

Основной трейт I2C выглядит так:

#![allow(unused)]
fn main() {
pub trait I2c<A: AddressMode = SevenBitAddress>: ErrorType {
    // Этот метод должен реализовать автор HAL
    fn transaction(...);
    // Это методы по умолчанию, построенные поверх `transaction`
    fn read(...);
    fn write(...);
    fn write_read(...);
}
}

Единственный метод, который HAL обязан реализовать, — transaction. Трейт предоставляет реализации по умолчанию для read, write и write_read, которые используют этот метод.

Обобщённый параметр типа A задаёт режим адресации и по умолчанию равен SevenBitAddress. Поэтому в большинстве случаев его не нужно указывать вручную. Для 10-битной адресации можно использовать TenBitAddress.

Крейты HAL для конкретных микроконтроллеров (например, esp-hal, stm32-hal или nrf-hal) реализуют этот трейт для своей периферии I2C. Например, крейт esp-hal реализует I2C. Если интересно, можете посмотреть реализацию здесь.

Помимо обычного крейта embedded-hal, есть асинхронная версия под названием embedded-hal-async. Она определяет похожие трейты, но они рассчитаны на работу с асинхронным кодом, что полезно при написании неблокирующих драйверов или задач во встраиваемых системах.

Драйверы, не зависящие от платформы

Представьте, что вы пишете драйвер для датчика или дисплея, который работает по I2C. Вы не хотите привязывать свой код к конкретному микроконтроллеру, например Raspberry Pi Pico или ESP32. Вместо этого можно написать драйвер обобщённо, с помощью трейта embedded-hal.

Пока ваш драйвер зависит только от трейта I2C, он может работать на любой платформе, которая реализует этот трейт, — будь то STM32, nRF или ESP32.

Совместное использование шины I2C

Во многих встраиваемых проектах к одним и тем же линиям SDA и SCL подключено несколько устройств I2C (например, OLED-дисплей, LCD и различные датчики). Однако управлять шиной может только одно устройство за раз.

I2C: микроконтроллер ESP32 и несколько устройств

Рисунок: микроконтроллер (ESP32) и несколько устройств

Если дать исключительный доступ одному драйверу, другие устройства не смогут обмениваться данными. Здесь помогает крейт embedded-hal-bus.

Он предоставляет обёртки, такие как AtomicDevice, CriticalSectionDevice и RefCellDevice, которые позволяют нескольким драйверам безопасно разделять доступ к одной шине I2C. Сами эти обёртки реализуют трейт I2c, поэтому драйверы могут использовать их так же, как исходную шину.

Использовать I2C можно двумя способами:

I2C: один контроллер и несколько устройств
  • Без совместного использования: если ваше приложение общается только с одним устройством I2C, можно передать драйверу экземпляр шины I2C от HAL (который реализует трейт I2c) напрямую.

  • С совместным использованием: если приложению нужно обмениваться данными с несколькими устройствами I2C на одной шине, можно обернуть экземпляр шины (предоставленный HAL) одним из типов совместного использования из крейта embedded-hal-bus, например AtomicDevice или CriticalSectionDevice. Это обеспечивает безопасный, согласованный доступ для нескольких драйверов.

Ресурсы

  • Документация embedded-hal по I2C: подробное описание того, как устроены трейты I2C и как их предполагается использовать на разных платформах.