Периферія Lilka
Після Lilka::init ми отримуємо вже налаштовані частини консолі. Не треба створювати другий SPI для дисплея або вручну переводити кнопки в input. Але SDK не приховує самі драйвери, тому для складніших речей можна користуватись їхнім API.
Дисплей
Поле display має тип LilkaDisplay, який залежить від features. У blocking це mipidsi, в async — lcd-async.
Штатний поворот дає 280×240, точка (0, 0) зверху зліва. Для blocking звичайний текст і примітиви малюються через embedded-graphics. Координата тексту залежить від baseline: з Baseline::Top це верх рядка, а не нижня лінія символів.
Повний приклад дисплея й кнопок можна зібрати без дописування ініціалізації. Для async є окрема програма, яка малює в маленький RGB565-буфер і передає його на екран через .await.
Не очищуйте весь дисплей на кожному проході циклу без потреби. Запам'ятайте зміну стану, поставте redraw, а вже після малювання скиньте його. Це зменшує передачу по SPI і мерехтіння.
Кнопки
controller має поля up, down, left, right, a, b, c, d, select, start. Це GPIO з pull-up, тому is_low() означає натиснуто.
Це перевірка утримання, а не подія. Щоб отримати одне натискання, порівнюйте з попереднім станом. Для debounce приймайте новий стан тільки після того, як він певний час не змінювався. Для автоповтору потрібні ще затримка після першого натискання й інтервал повторів.
Не робіть довгий delay всередині обробки A, якщо одночасно очікуєте реакцію на B. Краще зберігати час наступної дії та далі опитувати решту кнопок.
microSD
sd — це Option. Відсутня або неініціалізована карта дає None, а дисплей може працювати далі. Не робіть lilka.sd.unwrap(), якщо програма має запускатись і без карти.
Драйвер працює з секторами по 512 байтів. Для файлів потрібен FAT-рівень; alias SdVolMgr у SDK попри назву не монтує файлову систему. Самодостатній приклад показує монтування, відкриття, читання і закриття файла; адаптер BlockDevice винесено в окремий модуль.
Дисплей і карта ділять SPI. Спроби звертатись до тієї ж blocking-шини з переривання під час активної позики — не спосіб пришвидшити читання.
Батарея
adc_battery вже налаштований під дільник напруги на платі:
let mut adc = lilka.adc_battery;
let millivolts = adc.read_voltage_mv();
esp_println::println!("Battery: {} mV", millivolts);
Це оцінка напруги, а не готовий точний відсоток заряду. ADC має похибку, напруга змінюється під навантаженням, а батарея не розряджається лінійно. Для індикатора корисно усереднювати вимірювання й не оновлювати цифру на кожному проході циклу.
Зумер
За замовчуванням у BuzzerConfig duty 50%, тому в навчальних прикладах до ініціалізації ставимо duty_pct = 0. Інакше можна несподівано почути постійний тон.
Короткий сигнал у blocking-коді після ініціалізації:
use lilkaoxide::hal::ledc::channel::ChannelIFace;
let mut buzzer = lilka.buzzer;
buzzer.channel.set_duty_hw(128);
lilka.delay.delay_millis(80);
buzzer.channel.set_duty_hw(0);
Значення 128 тут приблизно відповідає 50% при стандартному Duty8Bit. Після іншого timer_duty діапазон буде інший. Частота задається в buzzer_config.frequency, це окрема річ від гучності через duty.
В async замініть очікування на Timer::after(...).await, щоби не блокувати executor на час сигналу.
I2S та звук
i2s_tx — передавач I2S0 з DMA. Це не те саме, що зумер: він передає цифрові аудіосемпли зовнішньому DAC. Стандартна конфігурація — 44100 Гц, стерео, 16-бітні семпли, Philips I2S.
i2s_tx_config дає змогу змінити ці параметри до Lilka::init. Дані повинні відповідати обраному формату: WAV-заголовок або стиснений MP3 не можна просто передати як PCM. Декодування, черга буферів і повторне заповнення DMA залишаються на застосунку. Blocking і async режими мають різні методи передачі; конкретні сигнатури дивіться в Rustdoc типу I2sTx та використовуваної версії esp-hal.
RTC, Wi-Fi та BLE
Для збереженого часу є окремий приклад keira-rtc. Правдоподібний timestamp не доводить, що годинник синхронізований саме сьогодні; SDK не вміє визначити це без зовнішнього джерела.
Для Wi-Fi/BLE потрібен feature radio. Після ініціалізації доступні відповідні поля у peripherals, але мережеві з'єднання й протоколи треба створювати окремо. Не викликайте повторно esp_hal::init: використовуйте периферію, яку повернув SDK, і не забирайте GPIO, вже зайняті дисплеєм, SD чи звуком.