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

Активное ожидание

Чтобы светодиод мигал, между переключениями нужна пауза примерно в полсекунды. Как её сделать?

Что ж, вот самый простой, «глупый» способ. Он плохой, но с него можно начать. Посмотрите 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(). После этого поговорим и о других недостатках нашей мигалки, которые нужно исправить.

Для такой простой программы она довольно сложна. Поэтому мы и начинаем с мигалки.