Использование прерываний с Embassy
В предыдущей главе мы рассмотрели, что такое прерывания и как в эту картину вписывается NVIC. Теперь посмотрим, как прерывания на самом деле используются в Embassy.
В Embassy обычно не нужно писать обработчики прерываний самостоятельно. Асинхронные драйверы используют прерывания внутри себя, чтобы ждать аппаратных событий и пробуждать задачи, когда эти события происходят. Ваш код просто ожидает операцию (await) и продолжается, когда она готова.
Для некоторой периферии Embassy нужна небольшая настройка, чтобы знать, какое аппаратное прерывание относится к какому драйверу. Здесь на помощь приходит bind_interrupts!.
Зачем нужен bind_interrupts!
Асинхронная периферия, такая как I2C и SPI, не завершает свою работу за один шаг. Пока операция выполняется, задача засыпает, а аппаратура генерирует прерывания по мере продвижения работы.
Обработчики прерываний для такой периферии Embassy уже предоставляет. От вас требуется только связь между аппаратным прерыванием и обработчиком, который должен его обслуживать. Именно эту связь и создаёт макрос bind_interrupts!.
Здесь вы не пишете обработчик прерывания. Вы просто подключаете всё так, чтобы асинхронный драйвер мог работать.
Привязка прерывания для I2C
Вот простой пример для I2C:
#![allow(unused)]
fn main() {
use embassy_rp::{bind_interrupts, i2c};
use embassy_rp::peripherals::I2C0;
bind_interrupts!(struct Irqs {
I2C0_IRQ => i2c::InterruptHandler<I2C0>;
});
}
Это говорит Embassy, что прерывание I2C0_IRQ должен обрабатывать драйвер I2C для I2C0. Когда это настроено, асинхронные операции I2C могут корректно засыпать и просыпаться.
Использование асинхронного I2C
После привязки прерывания использование асинхронного I2C выглядит привычно:
#![allow(unused)]
fn main() {
use embassy_rp::i2c::{I2c, Config as I2cConfig};
use embassy_rp::peripherals::I2C0;
let sda = p.PIN_16;
let scl = p.PIN_17;
let mut i2c = I2c::new_async(
p.I2C0,
scl,
sda,
Irqs,
I2cConfig::default(),
);
}
Когда позже вы вызываете асинхронную операцию, например:
#![allow(unused)]
fn main() {
i2c.write(0x3C, &[0x00]).await;
}
ваша задача приостанавливается и даёт выполниться другому коду. Тем временем аппаратура I2C делает свою работу. Когда аппаратура заканчивает, срабатывает прерывание, и Embassy пробуждает вашу задачу. Прерывание происходит за кулисами: вы видите только, что код продолжается после .await.