#![no_std] #![no_main] #![deny( clippy::mem_forget, reason = "mem::forget is generally not safe to do with esp_hal types, especially those \ holding buffers for the duration of a data transfer." )] #![deny(clippy::large_stack_frames)] use alloc::boxed::Box; use creaturedex::card::decoder::split_nfc_hex; use creaturedex::card::model::Card; use creaturedex::display::shared_bus::{DualSecondaryDisplay, init_dual_displays}; use creaturedex::display::tertiary_lcd::{TertiaryI2cLcd, init_tertiary_lcd, write_wrapped}; use creaturedex::drivers::nfc_pn532::NfcPn532Driver; use creaturedex::navigation::inputs::Inputs; use creaturedex::navigation::navigation::{self, CARD_DATA}; use creaturedex::navigation::outputs::Outputs; use creaturedex::network::wifi::setup_wifi; use embassy_executor::Spawner; use embassy_time::Instant; use embedded_graphics::pixelcolor::BinaryColor; use embedded_graphics::{ mono_font::{MonoTextStyle, ascii::FONT_6X10}, prelude::*, text::Text, }; use embedded_hal_bus::i2c::AtomicDevice; use embedded_hal_bus::util::AtomicCell; use esp_hal::Blocking; use esp_hal::clock::CpuClock; use esp_hal::gpio::{Input, InputConfig, Level, Output, OutputConfig, Pull}; use esp_hal::i2c::master::{Config as I2cConfig, I2c}; use esp_hal::timer::timg::TimerGroup; use log::error; #[panic_handler] fn panic(panic_info: &core::panic::PanicInfo) -> ! { error!("{}", panic_info); loop {} } extern crate alloc; const HEAP_SIZE: usize = 73744; // This creates a default app-descriptor required by the esp-idf bootloader. // For more information see: esp_bootloader_esp_idf::esp_app_desc!(); #[allow( clippy::large_stack_frames, reason = "it's not unusual to allocate larger buffers etc. in main" )] #[esp_rtos::main] async fn main(spawner: Spawner) { esp_println::logger::init_logger_from_env(); let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let peripherals = esp_hal::init(config); esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: HEAP_SIZE); let sw_interrupt = esp_hal::interrupt::software::SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); let timg0 = TimerGroup::new(peripherals.TIMG0); esp_rtos::start(timg0.timer0, sw_interrupt.software_interrupt0); let (display, mut oled) = init_dual_displays( peripherals.SPI2, peripherals.GPIO36, // sck peripherals.GPIO35, // mosi peripherals.GPIO37, // miso peripherals.GPIO11, // primary cs peripherals.GPIO13, // oled cs peripherals.GPIO10, // primary reset peripherals.GPIO47, // secondary reset peripherals.GPIO12, // shared dc ); let _secondary_oled_cs_2 = Output::new(peripherals.GPIO14, Level::High, OutputConfig::default()); let _secondary_oled_cs_3 = Output::new(peripherals.GPIO21, Level::High, OutputConfig::default()); let config = I2cConfig::default().with_frequency(esp_hal::time::Rate::from_khz(400)); let i2c = match I2c::new(peripherals.I2C0, config) { Ok(bus) => bus .with_sda(peripherals.GPIO17) .with_scl(peripherals.GPIO18), Err(e) => { log::error!("Shared I2C bus initialization failed for GPIO17/GPIO18: {e:?}"); panic!("Shared I2C bus initialization failed"); } }; let shared_i2c = Box::leak(Box::new(AtomicCell::new(i2c))); log::info!("Shared I2C bus ready on GPIO17 (SDA) and GPIO18 (SCL)"); let mut lcd: TertiaryI2cLcd = init_tertiary_lcd(AtomicDevice::new(shared_i2c)); if let Err(e) = write_wrapped(&mut lcd, "Testing more than 16 chars what happens now?") { log::error!( "Tertiary LCD startup text write failed over shared I2C bus; check the shared bus, wiring, and device responses: {e}" ); } setup_wifi(peripherals.WIFI, peripherals.FLASH, &spawner).await; let mut nfc_driver = NfcPn532Driver::new(AtomicDevice::new(shared_i2c)); if nfc_driver.configure_sam().await.is_ok() { log::info!("NFC SAM configuration successful on the shared I2C bus"); } else { log::error!("NFC SAM configuration failed on the shared I2C bus"); } let button_config = InputConfig::default().with_pull(Pull::Up); let inputs = Inputs { up: Input::new(peripherals.GPIO38, button_config), down: Input::new(peripherals.GPIO39, button_config), left: Input::new(peripherals.GPIO40, button_config), right: Input::new(peripherals.GPIO41, button_config), back: Input::new(peripherals.GPIO8, button_config), ok: Input::new(peripherals.GPIO0, button_config), }; display_shit(&mut oled); let outputs = Outputs { primary_display: display, secondary_display_1: oled, // secondary_display_2: todo!(), // secondary_display_3: todo!(), tertiary_display: lcd, _led_a: (), }; log::info!("Setup complete, entering main loop"); spawner.spawn(navigation::run(inputs, outputs).expect("run task failed")); spawner.spawn(nfc_driver_task(nfc_driver).expect("nfc driver task failed")); } #[embassy_executor::task] #[allow( clippy::large_stack_frames, reason = "ignoring this for now because it still works" )] async fn nfc_driver_task( mut nfc_driver: NfcPn532Driver>>, ) { loop { while CARD_DATA.lock().await.is_none() { let start = Instant::now(); match nfc_driver.read_card_data().await { Ok(card_data) => { let Some(split_data) = split_nfc_hex(&card_data) else { log::error!("Failed to split NFC card data"); continue; }; CARD_DATA .lock() .await .replace(Card::try_from(split_data).unwrap()); let elapsed_ms = start.elapsed().as_millis(); log::info!( "NFC read duration: {} ms, card data length: {}", elapsed_ms, card_data.len() ); } Err(_) => { log::info!("No NFC card found"); } } } embassy_time::Timer::after(embassy_time::Duration::from_millis(1000)).await; } } fn display_shit(oled: &mut DualSecondaryDisplay<'static>) { oled.clear().unwrap(); let text_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On); Text::new("Hello OLED", Point::new(10, 20), text_style) .draw(oled) .unwrap(); oled.flush().unwrap(); }