5 Commits
Author SHA1 Message Date
rhetenor 6b8d076b3c working on main menu 2026-08-14 20:02:11 +00:00
rhetenor 88dce365ad implement basic embassy framework 2026-08-14 19:14:44 +00:00
lukas 7ce3a60a08 Added updated NFC implementation into refactored structure 2026-08-02 16:40:40 +02:00
lukas 78765ce595 Added NFC implementation into refactored structure 2026-08-02 16:26:13 +02:00
lukas 5f76802f92 Activated Wifi 2026-08-02 16:05:48 +02:00
11 changed files with 317 additions and 70 deletions
Generated
+52 -8
View File
@@ -306,6 +306,7 @@ dependencies = [
"critical-section",
"display-interface-spi",
"embassy-executor",
"embassy-futures",
"embassy-time",
"embedded-graphics",
"embedded-graphics-core",
@@ -320,6 +321,8 @@ dependencies = [
"esp-rtos",
"ili9341",
"log",
"ndef",
"pn532",
]
[[package]]
@@ -568,6 +571,26 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "derive_more"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "digest"
version = "0.10.7"
@@ -1508,27 +1531,27 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures-core"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
[[package]]
name = "futures-sink"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
[[package]]
name = "futures-task"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
[[package]]
name = "futures-util"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
dependencies = [
"futures-core",
"futures-task",
@@ -1980,6 +2003,17 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
[[package]]
name = "ndef"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39081400340c14a9fcbb0122c311aca4e34ec2d19e3136613638ae7e2a362dbc"
dependencies = [
"derive_more",
"heapless 0.8.0",
"rustversion",
]
[[package]]
name = "nonmax"
version = "0.5.5"
@@ -2584,6 +2618,16 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pn532"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce656b2f9335bedbd8806597f6f78e644a955366673cd9280a0fb67a0d7b35a1"
dependencies = [
"embedded-hal 1.0.0",
"nb 1.1.0",
]
[[package]]
name = "portable-atomic"
version = "1.14.0"
+7 -4
View File
@@ -9,7 +9,7 @@ name = "creaturedex"
path = "./src/bin/main.rs"
[dependencies]
esp-hal = { version = "~1.1.0", features = ["esp32s3", "log-04", "unstable"] }
esp-hal = { version = "~1.1.0", features = ["esp32s3", "log-04", "unstable"] } #, "embassy", "embassy-time-timg0", "embassy-executor-thread"] }
esp-rtos = { version = "0.3.0", features = [
"embassy",
@@ -24,8 +24,10 @@ log = "0.4.27"
critical-section = "1.2.0"
embedded-io = "0.7.1"
embassy-executor = { version = "0.10.0", default-features = false }
embassy-executor = { version = "0.10.0", features = [
] }
embassy-time = { version = "0.5.1", default-features = false }
embassy-futures = "0.1.2"
esp-alloc = "0.10.0"
esp-hal-wifimanager = { git = "https://github.com/InvisibleUSA/esp-hal-wifimanager", default-features = false, features = [
"ap",
@@ -39,7 +41,8 @@ embedded-hal-bus = "0.3.0"
embedded-graphics = "0.8.2"
embedded-graphics-core = "0.4.1"
base64 = { version = "0.22", default-features = false, features = ["alloc"] }
pn532 = "0.5.0"
ndef = "0.5.0"
# For fine tuning these settings, please refer to https://doc.rust-lang.org/cargo/reference/profiles.html
[profile.dev]
@@ -55,4 +58,4 @@ opt-level = 's'
[features]
default = []
wokwi = []
wokwi = []
-2
View File
@@ -30,8 +30,6 @@
[ "lcd1:MOSI", "esp1:35", "violet", [ "v105.6", "h-124.8", "v-144" ] ],
[ "lcd1:SCK", "esp1:36", "white", [ "v115.2", "h-144", "v-86.4" ] ],
[ "esp1:37", "lcd1:MISO", "magenta", [ "h57.6", "v105.6", "h172.8" ] ],
[ "esp1:22", "lcd1:SCL", "yellow", [ "h48", "v163.2", "h192" ] ],
[ "esp1:21", "lcd1:SDA", "cyan", [ "h38.4", "v144", "h211.2" ] ],
[ "btn1:1.l", "esp1:GND.2", "green", [ "h-9.6", "v-48" ] ],
[ "btn1:2.l", "esp1:38", "green", [ "h-9.6", "v19.4" ] ]
],
+35 -31
View File
@@ -10,7 +10,9 @@
use creaturedex::card::mock::{MOCK_PAYLOAD, MOCK_PAYLOAD2};
use creaturedex::display::primary_lcd::init_primary_lcd;
use creaturedex::display::views::card_view::render_nfc_payload;
use creaturedex::navigation::input::init_navigation_button;
use creaturedex::drivers::nfc_pn532::NfcPn532Driver;
use creaturedex::navigation::inputs::Inputs;
use creaturedex::navigation::navigation;
use creaturedex::navigation::state::NavigationState;
use creaturedex::network::wifi::setup_wifi;
use embassy_executor::Spawner;
@@ -20,6 +22,8 @@ use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::RgbColor;
use esp_hal::clock::CpuClock;
use esp_hal::delay::Delay;
use esp_hal::gpio::{Input, InputConfig, Pull};
use esp_hal::time::Duration;
use esp_hal::timer::timg::TimerGroup;
use log::error;
@@ -31,6 +35,8 @@ fn panic(panic_info: &core::panic::PanicInfo) -> ! {
extern crate alloc;
const HEAP_SIZE: usize = 73744;
// This creates a default app-descriptor required by the esp-idf bootloader.
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
esp_bootloader_esp_idf::esp_app_desc!();
@@ -46,26 +52,15 @@ async fn main(spawner: Spawner) {
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
let peripherals = esp_hal::init(config);
// The following GPIO pins are in use by some feature of the module and should not be used.
let _ = peripherals.GPIO27;
let _ = peripherals.GPIO28;
let _ = peripherals.GPIO29;
let _ = peripherals.GPIO30;
let _ = peripherals.GPIO31;
let _ = peripherals.GPIO32;
let _ = peripherals.GPIO33;
let _ = peripherals.GPIO34;
esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: HEAP_SIZE);
esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 73744);
let mut delay = Delay::new();
let button1 = init_navigation_button(peripherals.GPIO38);
let timg0 = TimerGroup::new(peripherals.TIMG0);
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);
log::info!("Setup complete, entering main loop");
let mut display = init_primary_lcd(
peripherals.SPI2,
peripherals.GPIO36,
@@ -74,27 +69,36 @@ async fn main(spawner: Spawner) {
peripherals.GPIO11,
peripherals.GPIO10,
peripherals.GPIO12,
&mut delay,
);
// setup_wifi(peripherals.WIFI, peripherals.FLASH, &spawner).await;
setup_wifi(peripherals.WIFI, peripherals.FLASH, &spawner).await;
log::info!("Setup complete, entering main loop");
let mut nav_state = NavigationState::new();
loop {
if button1.is_low() {
log::info!("Button pressed!");
display.clear(Rgb565::BLACK).unwrap();
let payload = if nav_state.toggle_payload() {
&MOCK_PAYLOAD
} else {
&MOCK_PAYLOAD2
};
render_nfc_payload(&mut display, payload);
}
Timer::after_millis(10).await;
let mut nfc_driver =
NfcPn532Driver::new(peripherals.I2C0, peripherals.GPIO17, peripherals.GPIO18);
let timeout = Duration::from_millis(1000);
if nfc_driver.configure_sam(timeout).is_ok() {
log::info!("NFC SAM configuration successful");
} else {
log::error!("NFC SAM configuration failed");
}
log::info!("Setup complete, entering main loop");
let mut nav_state = NavigationState::new();
let button_config = InputConfig::default().with_pull(Pull::Up);
let mut inputs = Inputs {
up: Input::new(peripherals.GPIO38, button_config.clone()),
down: Input::new(peripherals.GPIO0, button_config.clone()),
left: Input::new(peripherals.GPIO1, button_config.clone()),
right: Input::new(peripherals.GPIO2, button_config.clone()),
back: Input::new(peripherals.GPIO3, button_config.clone()),
ok: Input::new(peripherals.GPIO4, button_config.clone()),
};
spawner.spawn(navigation::run(inputs).expect("run task failed"));
}
+9 -8
View File
@@ -6,15 +6,15 @@ use embedded_hal_bus::spi::ExclusiveDevice;
use esp_hal::delay::Delay;
use esp_hal::gpio::{Level, Output, OutputConfig};
use esp_hal::peripherals::{GPIO10, GPIO11, GPIO12, GPIO35, GPIO36, GPIO37, SPI2};
use esp_hal::spi::{master::{Config, Spi}, Mode};
use esp_hal::spi::{
Mode,
master::{Config, Spi},
};
use esp_hal::time::Rate;
use ili9341::{DisplaySize240x320, Ili9341, Orientation};
pub type PrimaryLcdDisplay<'a> = Ili9341<
SPIInterface<
ExclusiveDevice<Spi<'a, esp_hal::Blocking>, Output<'a>, Delay>,
Output<'a>,
>,
SPIInterface<ExclusiveDevice<Spi<'a, esp_hal::Blocking>, Output<'a>, Delay>, Output<'a>>,
Output<'a>,
>;
@@ -26,7 +26,6 @@ pub fn init_primary_lcd(
cs: GPIO11<'static>,
reset: GPIO10<'static>,
dc: GPIO12<'static>,
delay: &mut Delay,
) -> PrimaryLcdDisplay<'static> {
let freq = Rate::from_khz(60_000);
log::info!("Display freq: {freq}");
@@ -40,8 +39,10 @@ pub fn init_primary_lcd(
.with_mosi(mosi)
.with_miso(miso);
let mut delay = Delay::new();
let cs_main = Output::new(cs, Level::High, OutputConfig::default());
let spi_dev = ExclusiveDevice::new(spi_bus, cs_main, *delay).unwrap();
let spi_dev = ExclusiveDevice::new(spi_bus, cs_main, delay).unwrap();
let reset_main = Output::new(reset, Level::High, OutputConfig::default());
let spi_dc = Output::new(dc, Level::High, OutputConfig::default());
let iface = SPIInterface::new(spi_dev, spi_dc);
@@ -49,7 +50,7 @@ pub fn init_primary_lcd(
let mut display = Ili9341::new(
iface,
reset_main,
delay,
&mut delay,
Orientation::Portrait,
DisplaySize240x320,
)
+146 -1
View File
@@ -1 +1,146 @@
// Drivers nfc_pn532 module
use alloc::vec::Vec;
use core::convert::Infallible;
use esp_hal::gpio::interconnect::{PeripheralInput, PeripheralOutput};
use esp_hal::i2c::master::{Config as I2cConfig, I2c};
use esp_hal::peripherals::I2C0;
use esp_hal::time::{Duration, Instant, Rate};
use log::{error, info};
use pn532::i2c::I2CInterface;
use pn532::requests::SAMMode;
use pn532::{nb, Pn532, Request};
/// Timer implementation required by `pn532::CountDown`.
pub struct TimerWrapper {
start: Instant,
duration: Duration,
counter: u32,
}
impl TimerWrapper {
pub fn new() -> Self {
Self {
start: Instant::now(),
duration: Duration::ZERO,
counter: 0,
}
}
}
impl Default for TimerWrapper {
fn default() -> Self {
Self::new()
}
}
impl pn532::CountDown for TimerWrapper {
type Time = Duration;
fn start<T>(&mut self, timeout: T)
where
T: Into<Self::Time>,
{
self.start = Instant::now();
self.duration = timeout.into();
self.counter = 0;
}
fn wait(&mut self) -> nb::Result<(), Infallible> {
let elapsed = self.start.elapsed();
self.counter += 1;
if elapsed >= self.duration {
Ok(())
} else {
Err(nb::Error::WouldBlock)
}
}
}
pub type Pn532Device<'a> = Pn532<I2CInterface<I2c<'a, esp_hal::Blocking>>, TimerWrapper, 32>;
/// PN532 NFC reader driver.
pub struct NfcPn532Driver<'a> {
pn532: Pn532Device<'a>,
}
impl<'a> NfcPn532Driver<'a> {
pub fn new<SDA, SCL>(i2c0: I2C0<'static>, sda: SDA, scl: SCL) -> Self
where
SDA: PeripheralInput<'static> + PeripheralOutput<'static>,
SCL: PeripheralInput<'static> + PeripheralOutput<'static>,
{
let config = I2cConfig::default().with_frequency(Rate::from_khz(20));
let i2c = I2c::new(i2c0, config)
.expect("Failed to init I2C")
.with_sda(sda)
.with_scl(scl);
let pn532 = Pn532::new(I2CInterface { i2c }, TimerWrapper::new());
Self { pn532 }
}
pub fn configure_sam(&mut self, timeout: Duration) -> Result<(), ()> {
if let Err(e) = self.pn532.process(
&Request::sam_configuration(SAMMode::Normal, false),
0,
timeout,
) {
error!("Could not initialize PN532: {e:?}");
Err(())
} else {
info!("Successfully init'ed PN532");
Ok(())
}
}
pub fn poll_target(&mut self, timeout: Duration) -> Result<(), ()> {
match self.pn532.process(&Request::INLIST_ONE_ISO_A_TARGET, 23, timeout) {
Ok(uid) => {
info!("uid = {uid:?}");
Ok(())
}
Err(e) => {
info!("No NFC card found: {e:?}");
Err(())
}
}
}
pub fn parse_nfc_page(iteration: u8, bytes: &[u8]) -> Result<[u8; 4], ()> {
let success = bytes.first().is_some_and(|x| *x == 0);
if success {
let mut arr = [0; 4];
arr.copy_from_slice(&bytes[1..5]);
Ok(arr)
} else {
info!("err {iteration}: {:02x?}", bytes);
Err(())
}
}
pub fn read_card_data(&mut self) -> Result<Vec<u8>, ()> {
let ms_1000 = Duration::from_millis(1000);
self.poll_target(Duration::from_secs(10))?;
let mut data: Vec<u8> = Vec::with_capacity(858);
for i in 0x04..=230 {
let mut parse_result = Err(());
while parse_result.is_err() {
let Ok(result) = self.pn532.process(&Request::ntag_read(i), 17, ms_1000) else {
continue;
};
parse_result = Self::parse_nfc_page(i, result);
}
let bytes = parse_result.unwrap();
data.extend(bytes);
}
info!("Full data: {data:02x?}");
Ok(data)
}
pub fn device_mut(&mut self) -> &mut Pn532Device<'a> {
&mut self.pn532
}
}
+3 -1
View File
@@ -1,2 +1,4 @@
pub mod input;
pub mod inputs;
pub mod main_menu;
pub mod navigation;
pub mod state;
-15
View File
@@ -1,15 +0,0 @@
use esp_hal::gpio::{Input, InputConfig, Pull};
use esp_hal::peripherals::GPIO38;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NavCommand {
NextCard,
PrevCard,
Select,
ScanTag,
}
pub fn init_navigation_button(pin: GPIO38<'static>) -> Input<'static> {
let button_config = InputConfig::default().with_pull(Pull::Up);
Input::new(pin, button_config)
}
+19
View File
@@ -0,0 +1,19 @@
use esp_hal::gpio::{Input, InputConfig, Pull};
use esp_hal::peripherals::Peripherals;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NavCommand {
NextCard,
PrevCard,
Select,
ScanTag,
}
pub struct Inputs {
pub up: Input<'static>,
pub down: Input<'static>,
pub left: Input<'static>,
pub right: Input<'static>,
pub back: Input<'static>,
pub ok: Input<'static>,
}
+11
View File
@@ -0,0 +1,11 @@
use crate::navigation::inputs::Inputs;
pub const MAX_SELECTED: i8 = 3;
pub struct MainMenu {
pub selected: i8,
}
impl MainMenu {
pub async fn display(self: &Self) {}
}
+35
View File
@@ -0,0 +1,35 @@
use crate::navigation::inputs::Inputs;
use crate::navigation::main_menu::{MAX_SELECTED, MainMenu};
use embassy_futures::select::Either6;
use embassy_futures::select::select6;
#[embassy_executor::task]
pub async fn run(mut inputs: Inputs) {
let mut state = MainMenu { selected: 0 };
loop {
let selection = select6(
inputs.up.wait_for_low(),
inputs.down.wait_for_low(),
inputs.left.wait_for_low(),
inputs.right.wait_for_low(),
inputs.ok.wait_for_low(),
inputs.back.wait_for_low(),
)
.await;
let value = match selection {
Either6::First(_) => (state.selected.wrapping_sub(1)) % MAX_SELECTED,
Either6::Second(_) => (state.selected.wrapping_add(1)) % MAX_SELECTED,
_ => continue, // Either6::Third(_) => ,
// Either6::Fourth(_) => (),
// Either6::Fifth(_) => (),
// Either6::Sixth(_) => (),
};
state.selected = value;
state.display().await
}
}