diff --git a/Cargo.toml b/Cargo.toml index 2d23c28..3684968 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ opt-level = "z" esp-idf-svc = { version = "0.51", features = ["std"] } anyhow = "1" log = "0.4" +hd44780-driver = "0.4" [build-dependencies] embuild = "0.33" diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 2d610c9..02bb101 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -2,11 +2,12 @@ CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096 CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=4096 -# You might have to increase this further if you spawn your own Rust threads -# that allocate large stack variables; or better yet - use +# You might have to increase this further if you spawn your own Rust threads +# that allocate large stack variables; or better yet - use # `std::thread::Builder::new().stack_size(XXX)` for spawning CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=4096 CONFIG_ETH_SPI_ETHERNET_W5500=y CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000 CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 +CONFIG_LWIP_SNTP_MAX_SERVERS=1 diff --git a/src/main.rs b/src/main.rs index a139f09..c9d2e71 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,47 @@ -use std::ffi::CString; -use std::thread; -use std::time::Duration; - use esp_idf_svc::eth::{BlockingEth, EspEth, EthDriver, SpiEthChipset}; use esp_idf_svc::eventloop::EspSystemEventLoop; +use esp_idf_svc::hal::delay::Ets; +use esp_idf_svc::hal::i2c::{I2cConfig, I2cDriver}; use esp_idf_svc::hal::peripherals::Peripherals; use esp_idf_svc::hal::prelude::*; use esp_idf_svc::hal::spi::{config::DriverConfig, Dma, SpiDriver}; -use esp_idf_svc::handle::RawHandle; // brings .handle() into scope for EspNetif +use esp_idf_svc::handle::RawHandle; +use esp_idf_svc::sntp::{EspSntp, SyncStatus}; +use hd44780_driver::HD44780; use log::info; +use std::ffi::CString; +use std::thread; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +// Row start addresses for 24x4 HD44780-compatible displays. +// Common split, but NOT universal — check your display's datasheet. +const ROW_ADDR: [u8; 4] = [0x00, 0x40, 0x18, 0x58]; + +fn lcd_set_cursor( + lcd: &mut HD44780, + row: u8, + col: u8, + delay: &mut Ets, +) { + let addr = ROW_ADDR[row as usize] + col; + lcd.set_cursor_pos(addr, delay).ok(); +} + +fn lcd_print_line( + lcd: &mut HD44780, + row: u8, + text: &str, + delay: &mut Ets, +) { + lcd_set_cursor(lcd, row, 0, delay); + // pad/truncate to 24 chars so stale characters don't linger + let mut line = text.to_string(); + line.truncate(24); + while line.len() < 24 { + line.push(' '); + } + lcd.write_str(&line, delay).ok(); +} fn main() -> anyhow::Result<()> { esp_idf_svc::sys::link_patches(); @@ -20,49 +53,93 @@ fn main() -> anyhow::Result<()> { let spi_driver = SpiDriver::new( peripherals.spi2, - pins.gpio13, // SCLK - pins.gpio11, // MOSI - Some(pins.gpio12), // MISO + pins.gpio13, + pins.gpio11, + Some(pins.gpio12), &DriverConfig { - dma: Dma::Auto(4096), // required — default (no DMA) can't move full ethernet frames + dma: Dma::Auto(4096), ..Default::default() }, )?; let eth_driver = EthDriver::new_spi( spi_driver, - pins.gpio10, // INT - Some(pins.gpio14), // CS - Some(pins.gpio9), // RST + pins.gpio10, + Some(pins.gpio14), + Some(pins.gpio9), SpiEthChipset::W5500, 20.MHz().into(), - Some(&[0x02, 0x00, 0x00, 0x12, 0x34, 0x56]), // locally-administered MAC - None, // default PHY address + Some(&[0x02, 0x00, 0x00, 0x12, 0x34, 0x56]), + None, sysloop.clone(), )?; - let eth = EspEth::wrap(eth_driver)?; - // Set hostname BEFORE starting — esp-idf-svc's safe NetifConfiguration has no - // hostname field, so this goes through the raw ESP-IDF binding. It must happen - // before eth.start(), otherwise it won't be included in the DHCP request and - // your router/OS will show a generic name instead (max 32 bytes). let hostname = CString::new("esp32-eth-device")?; unsafe { esp_idf_svc::sys::esp_netif_set_hostname(eth.netif().handle(), hostname.as_ptr()); } let mut eth = BlockingEth::wrap(eth, sysloop)?; - info!("Starting eth..."); eth.start()?; info!("Waiting for DHCP lease..."); eth.wait_netif_up()?; - let ip_info = eth.eth().netif().get_ip_info()?; info!("Got IP: {:?}", ip_info); + // --- SNTP time sync --- + info!("Starting SNTP sync..."); + let sntp = EspSntp::new_default()?; + while sntp.get_sync_status() != SyncStatus::Completed { + thread::sleep(Duration::from_millis(200)); + } + info!("Time synced."); + + // --- I2C LCD setup (SDA=GPIO17, SCL=GPIO16, adjust addr if needed) --- + let i2c_config = I2cConfig::new().baudrate(100.kHz().into()); + let i2c = I2cDriver::new(peripherals.i2c0, pins.gpio17, pins.gpio16, &i2c_config)?; + let mut delay = Ets; + let mut lcd = HD44780::new_i2c(i2c, 0x27, &mut delay) // 0x27 is common PCF8574 default; some boards use 0x3F + .map_err(|_| anyhow::anyhow!("LCD init failed"))?; + lcd.reset(&mut delay).ok(); + lcd.clear(&mut delay).ok(); + loop { + let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); + let (date_str, time_str) = format_utc(now); + + lcd_print_line(&mut lcd, 0, "ESP32-S3-ETH", &mut delay); + lcd_print_line(&mut lcd, 1, &date_str, &mut delay); + lcd_print_line(&mut lcd, 2, &time_str, &mut delay); + lcd_print_line(&mut lcd, 3, "", &mut delay); + thread::sleep(Duration::from_secs(1)); } } + +// Minimal UTC breakdown without pulling in chrono — good enough for display. +fn format_utc(unix_secs: u64) -> (String, String) { + let days_since_epoch = unix_secs / 86400; + let secs_of_day = unix_secs % 86400; + let h = secs_of_day / 3600; + let m = (secs_of_day % 3600) / 60; + let s = secs_of_day % 60; + + // Civil-from-days algorithm (Howard Hinnant), epoch = 1970-01-01 + let z = days_since_epoch as i64 + 719468; + let era = if z >= 0 { z } else { z - 146096 } / 146097; + let doe = (z - era * 146097) as u64; + let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; + let y = yoe as i64 + era * 400; + let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); + let mp = (5 * doy + 2) / 153; + let d = doy - (153 * mp + 2) / 5 + 1; + let mth = if mp < 10 { mp + 3 } else { mp - 9 }; + let y = if mth <= 2 { y + 1 } else { y }; + + ( + format!("{:04}-{:02}-{:02} UTC", y, mth, d), + format!("{:02}:{:02}:{:02}", h, m, s), + ) +}