Активное ожидание
Чтобы светодиод мигал, между переключениями нужна пауза примерно в полсекунды. Как её сделать?
Что ж, вот самый простой, «глупый» способ. Он плохой, но с него можно начать. Посмотрите examples/spin-wait.rs.
#![no_main]
#![no_std]
use cortex_m::asm::nop;
use cortex_m_rt::entry;
use embedded_hal::digital::OutputPin;
use nrf52833_hal::{gpio, pac};
use panic_halt as _;
fn wait() {
for _ in 0..4_000_000 {
nop();
}
}
#[entry]
fn main() -> ! {
let peripherals = pac::Peripherals::take().unwrap();
let p0 = gpio::p0::Parts::new(peripherals.P0);
let mut row1 = p0.p0_21.into_push_pull_output(gpio::Level::High);
let _col1 = p0.p0_28.into_push_pull_output(gpio::Level::Low);
loop {
wait();
row1.set_high().unwrap();
wait();
row1.set_low().unwrap();
}
}
Запустите её командой cargo run --release --example spin-wait — флаг --release здесь очень важен — и вы увидите, что светодиод на MB2 мигает примерно раз в секунду.
Возможно, у вас возникнут вопросы:
-
Что за символы
_в числе? Rust разрешает их в числах и просто игнорирует. Это удобно, чтобы сделать большие числа читаемее. Здесь они выполняют роль разделителей разрядов (запятых или того, что принято в вашей стране для групп из трёх цифр). -
Если nRF52833 работает на 64 МГц, почему цикл ожидания выполняется всего 4 млн раз? Разве не должно быть 32 млн? На каждом проходе цикл выполняет несколько инструкций:
nop(см. следующий раздел), служебные операции и переход назад к началу цикла. Для первого вызоваwait()сгенерированный код примерно такой:.LBB1_4: adds r3, #1 nop cmp r3, r2 bne .LBB1_4а для второго:
.LBB1_6: subs r3, #1 nop bne .LBB1_6Это всего три-четыре инструкции, но обратный переход может стоить ещё немного времени. Обратите внимание, что они не одинаковы: компилятор выбрал разные инструкции для первого и второго циклов ожидания. См. «зависит от обстоятельств» ниже.
Тем не менее на каждой итерации выполняется около 4 инструкций. Значит, на нашем процессоре с частотой 64 МГц полусекундное ожидание должно занимать 64M/2/4 = 8 млн итераций. Получается, что что-то замедляет нас вдвое. Что именно? Честно говоря, не знаю. Всё это ужасно.
-
Почему
--releaseтак важен? Попробуйте запустить без него. Светодиод всё так же будет мигать, но период будет составлять много секунд. Цикл ожидания теперь не оптимизирован и на каждом проходе выполняет много инструкций. -
Что за вызов
nop()и зачем он нужен? Ответим на этот вопрос в следующем разделе. -
Почему вы называете это «глупым способом»?
-
Он неточный. Подогнать цикл так, чтобы он надёжно укладывался ровно в 0,5 секунды, — не реальная задача.
-
Он зависит от обстоятельств. Другой процессор? Другие флаги компиляции? Да что угодно? И время уже другое.
-
Он расходует энергию. Процессор выполняет инструкции на полной скорости просто ради того, чтобы «стоять на месте». Если делать больше нечего, ему лучше тихо уснуть до тех пор, пока он снова не понадобится. При питании от USB это не так важно. Но если подключить MB2 к аккумуляторному блоку, вы это почувствуете.
-
В следующем разделе мы разберём nop(). После этого поговорим и о других недостатках нашей мигалки, которые нужно исправить.
Для такой простой программы она довольно сложна. Поэтому мы и начинаем с мигалки.