### Install espflash Source: https://github.com/infinition/waveshare-watch-rs/blob/main/README.md Command to install the espflash utility for flashing the device. ```bash cargo install espflash ``` -------------------------------- ### Install ESP Rust Toolchain Source: https://github.com/infinition/waveshare-watch-rs/blob/main/README.md Commands to install the required Xtensa ESP Rust toolchain using espup. ```bash cargo install espup espup install ``` -------------------------------- ### Initialize and Read Real-Time Clock (PCF85063A) Source: https://context7.com/infinition/waveshare-watch-rs/llms.txt Demonstrates how to initialize the PCF85063A RTC driver, read the current time, and set a new time. Requires I2C communication setup. ```rust use crate::peripherals::rtc::{Pcf85063aRtc, DateTime}; use embedded_hal_bus::i2c::RefCellDevice; // Create RTC driver let mut rtc = Pcf85063aRtc::new(RefCellDevice::new(&i2c_ref)); // Initialize (start oscillator, 24h mode) rtc.init()?; // Read current time let dt = rtc.get_time()?; println!("{:02}:{:02}:{:02} {:02}/{:02}/20{:02}", dt.hours, dt.minutes, dt.seconds, dt.day, dt.month, dt.year); // Set time (year is 0-99 for 2000-2099) let new_time = DateTime::new( 26, // year (2026) 4, // month 6, // day 14, // hours 30, // minutes 0, // seconds ); rtc.set_time(&new_time)?; ``` -------------------------------- ### Initialize and Read IMU Sensor (QMI8658) Source: https://context7.com/infinition/waveshare-watch-rs/llms.txt Shows how to initialize the QMI8658 6-axis IMU, read accelerometer and gyroscope data, temperature, and chip ID. Includes power management functions. Requires I2C communication setup. ```rust use crate::peripherals::imu::{Qmi8658Imu, AccelData, GyroData}; use embedded_hal_bus::i2c::RefCellDevice; // Create IMU driver let mut imu = Qmi8658Imu::new(RefCellDevice::new(&i2c_ref)); // Initialize (returns true if chip ID matches) let ok = imu.init()?; // Read accelerometer data in g units let accel: AccelData = imu.read_accel()?; println!("Accel: X={:.2}g Y={:.2}g Z={:.2}g", accel.x, accel.y, accel.z); // Read gyroscope data in degrees per second let gyro: GyroData = imu.read_gyro()?; println!("Gyro: X={:.1}°/s Y={:.1}°/s Z={:.1}°/s", gyro.x, gyro.y, gyro.z); // Read chip temperature in Celsius let temp = imu.read_temperature()?; println!("Temperature: {:.1}°C", temp); // Power management (gyro draws ~1.5mA when active) imu.power_down()?; imu.power_up()?; // Read chip ID for verification let chip_id = imu.read_chip_id()?; ``` -------------------------------- ### Initialize Audio Codec (ES8311) and Play Beep Source: https://context7.com/infinition/waveshare-watch-rs/llms.txt Configures the ES8311 audio codec for 16kHz 16-bit I2S playback, mutes/unmutes, sets volume, and plays a generated beep tone. Requires I2S and I2C setup, along with GPIO control for power amplifier. ```rust use crate::peripherals::audio::{Es8311, fill_beep_buffer}; use embedded_hal_bus::i2c::RefCellDevice; use esp_hal::i2s::master::{I2s, Config as I2sConfig, DataFormat}; use esp_hal::clock::ClockControl; use esp_hal::peripherals::Peripherals; use esp_hal::gpio::{Output, Level, OutputConfig}; use esp_hal::delay::Delay; use esp_hal::system::SystemControl; use esp_hal::timer::TimerGroup; use esp_hal::Rng; use esp_hal::clock::ClockControl; use esp_hal::i2s::Rate; // Assume i2c_ref, peripherals, i2s_tx, and delay are already initialized // Create codec driver let mut audio_codec = Es8311::new(RefCellDevice::new(&i2c_ref)); // Initialize codec (16kHz, 16-bit I2S) audio_codec.init()?; // Mute immediately after init to prevent noise audio_codec.mute()?; // Set up I2S for playback let i2s_config = I2sConfig::default() .with_sample_rate(Rate::from_hz(16000)) .with_data_format(DataFormat::Data16Channel16); let i2s = I2s::new(peripherals.I2S0, peripherals.DMA_CH1, i2s_config) .with_mclk(peripherals.GPIO16) .with_bclk(peripherals.GPIO41) .with_ws(peripherals.GPIO45) .with_dout(peripherals.GPIO40); // Generate a beep sound (800Hz, 50ms duration) let mut beep_buf = [0u8; 4000]; let beep_len = fill_beep_buffer(&mut beep_buf, 800, 16000, 50); // Play audio with proper power amplifier control let mut pa_en = Output::new(peripherals.GPIO46, Level::Low, OutputConfig::default()); audio_codec.unmute()?; delay.delay_millis(2); // Let codec stabilize pa_en.set_high(); // Enable amplifier // Write audio via I2S DMA let transfer = i2s_tx.write_dma(&beep_buf[..beep_len])?; transfer.wait(); pa_en.set_low(); // Disable amplifier first audio_codec.mute()?; // Adjust volume (0-255) audio_codec.set_volume(0xD0)?; ``` -------------------------------- ### Perform HTTP GET and POST Requests Source: https://context7.com/infinition/waveshare-watch-rs/llms.txt Utilizes a minimal HTTP/1.1 client to send GET and POST requests over TCP sockets. Supports automatic URL parsing, connection timeouts, and response parsing. Requires embassy-net stack initialization. ```rust use crate::peripherals::http::{http_get, http_post, HttpResponse}; use embassy_net::Stack; // HTTP GET request let response: HttpResponse = http_get(stack, "http://192.168.1.100:8080/api/status").await?; println!("Status: {}", response.status); println!("Body: {:?}", &response.body[..response.body_len]); // HTTP POST request with JSON body let response = http_post( stack, "http://192.168.1.100:8080/api/control", r#"{"device":"light","action":"on"}"#.to_string(), ).await?; if response.status == 200 { println!("Success!"); } ``` -------------------------------- ### Implement App Trait for Custom Apps Source: https://context7.com/infinition/waveshare-watch-rs/llms.txt Implement the App trait to define custom applications. Handle input, manage state, and define rendering logic within the setup, update, and render methods. Return AppResult::Exit to go back to the launcher. ```rust use crate::apps::{App, AppInput, AppResult, AppState}; use crate::peripherals::touch::{SwipeDirection, TouchPoint}; use embedded_graphics::pixelcolor::Rgb565; use embedded_graphics::prelude::DrawTarget; // Input state passed each frame let input = AppInput { touch: Some(TouchPoint { x: 100, y: 200, fingers: 1 }), swipe: Some(SwipeDirection::Up), tap: false, accel: (0.1, -0.05, 0.98), // (x, y, z) in g dt_ms: 16, // milliseconds since last frame }; // Implement the App trait for custom apps pub struct MyApp { score: u32, } impl App for MyApp { fn name(&self) -> &str { "My App" } fn setup(&mut self) { self.score = 0; } fn update(&mut self, input: &AppInput) -> AppResult { // Handle swipe input if let Some(SwipeDirection::Up) = input.swipe { self.score += 10; } // Return Exit to go back to launcher if input.tap { return AppResult::Exit; } AppResult::Continue } fn render>(&self, d: &mut D) { // Draw game state using embedded-graphics } } // App states for the main state machine match app_state { AppState::Watchface => { /* ... */ } AppState::Launcher => { /* ... */ } AppState::Snake => { snake_game.update(&input); } AppState::Game2048 => { game_2048.update(&input); } AppState::Tetris => { tetris_game.update(&input); } AppState::Flappy => { flappy_game.update(&input); } AppState::Maze => { maze_game.update(&input); } AppState::Mp3Player => { mp3_player.update(&input); } AppState::SmartHome => { smarthome_app.update(&input); } AppState::Settings => { settings_app.update(dt_ms); } } ``` -------------------------------- ### Initialize WiFi and Connect Source: https://context7.com/infinition/waveshare-watch-rs/llms.txt Initializes the WiFi radio, configures STA mode with provided credentials, and connects to the network. It then sets up the network stack with DHCP and waits for an IP address. ```rust use esp_radio::wifi::{Config, ModeConfig, ClientConfig, AuthMethod}; use embassy_net::{Config as NetConfig, StackResources}; use embassy_time::{Duration, Timer}; // Initialize WiFi radio let radio_controller = esp_radio::init().expect("esp-radio init failed"); let wifi_config = Config::default(); let (mut wifi_controller, wifi_interfaces) = esp_radio::wifi::new( radio_controller, peripherals.WIFI, wifi_config, ).expect("WiFi init failed"); // Configure STA mode with credentials let client_config = ClientConfig::default() .with_ssid(String::from(env!("WIFI_SSID"))) .with_password(String::from(env!("WIFI_PASS"))) .with_auth_method(AuthMethod::WpaWpa2Personal); wifi_controller.set_config(&ModeConfig::Client(client_config))?; wifi_controller.start()?; // Connect asynchronously wifi_controller.connect_async().await?; // Set up network stack with DHCP let net_config = NetConfig::dhcpv4(Default::default()); let (stack, runner) = embassy_net::new(wifi_interfaces.sta, net_config, resources, seed); // Spawn network task spawner.spawn(net_task(runner)).ok(); // Wait for DHCP IP loop { if let Some(config) = stack.config_v4() { println!("IP: {}", config.address); break; } Timer::after(Duration::from_millis(100)).await; } ``` -------------------------------- ### Initialize QSPI Bus and Control Display Source: https://context7.com/infinition/waveshare-watch-rs/llms.txt Sets up the QSPI bus for the CO5300 AMOLED display, including SPI configuration, chip select, and DMA. Demonstrates writing commands and streaming pixel data. ```rust use crate::drivers::qspi_bus::QspiBus; use esp_hal::spi::master::Spi; use esp_hal::gpio::Output; // Create QSPI bus with SPI peripheral and chip select pin let spi = Spi::new(peripherals.SPI2, spi_config) .expect("SPI failed") .with_sck(peripherals.GPIO11) .with_sio0(peripherals.GPIO4) .with_sio1(peripherals.GPIO5) .with_sio2(peripherals.GPIO6) .with_sio3(peripherals.GPIO7) .with_dma(peripherals.DMA_CH0) .with_buffers(dma_rx, dma_tx); let cs = Output::new(peripherals.GPIO12, Level::High, OutputConfig::default()); let mut qspi_bus = QspiBus::new(spi, cs); // Write a single command (single-mode) qspi_bus.write_command(0x11); // SLPOUT // Write command with 8-bit data qspi_bus.write_c8d8(0x51, 0xD0); // Set brightness // Write command with two 16-bit parameters qspi_bus.write_c8d16d16(0x2A, x_start, x_end); // Column address set // Stream pixels in quad mode (for framebuffer flush) qspi_bus.begin_pixels(); qspi_bus.stream_pixels(&pixel_buffer[0..1000]); qspi_bus.stream_pixels(&pixel_buffer[1000..2000]); qspi_bus.end_pixels(); // Fill screen with repeated color qspi_bus.write_repeat(0xFFFF, 410 * 502); // White fill ``` -------------------------------- ### Build Firmware Source: https://github.com/infinition/waveshare-watch-rs/blob/main/README.md Command to build the project with WiFi credentials. ```bash WIFI_SSID="MyNetwork" WIFI_PASS="MyPassword" cargo build --release ``` -------------------------------- ### Flash and Monitor Source: https://github.com/infinition/waveshare-watch-rs/blob/main/README.md Command to flash the firmware to the device and open the serial monitor. ```bash espflash flash --port COM7 --monitor target/xtensa-esp32s3-none-elf/release/waveshare-watch-rs ``` -------------------------------- ### Set WiFi Credentials Source: https://github.com/infinition/waveshare-watch-rs/blob/main/README.md Commands to set WiFi environment variables for different shells. ```bash export WIFI_SSID="MyNetwork" export WIFI_PASS="MyPassword" ``` ```powershell $env:WIFI_SSID = "MyNetwork" $env:WIFI_PASS = "MyPassword" ``` -------------------------------- ### Configure MSVC Linker Path Source: https://github.com/infinition/waveshare-watch-rs/blob/main/README.md Environment variable configuration for the MSVC linker on Windows. ```bash export PATH="/c/Program Files/Microsoft Visual Studio/18/Community/VC/Tools/MSVC/14.50.35717/bin/Hostx64/x64:$PATH" ``` -------------------------------- ### QspiBus API Definition Source: https://github.com/infinition/waveshare-watch-rs/blob/main/README.md Defines the half-duplex quad-SPI bus interface for the CO5300 display. ```rust fn write_command(&mut self, cmd: u8) fn write_c8d8(&mut self, cmd: u8, data: u8) fn write_pixels(&mut self, pixels: &[u16]) fn begin_pixels(&mut self) fn stream_pixels(&mut self, pixels: &[u16]) fn end_pixels(&mut self) ``` -------------------------------- ### Handle FT3168 Touch Input Source: https://context7.com/infinition/waveshare-watch-rs/llms.txt Initialize the touch controller and process raw touch points or gesture events. ```rust use crate::peripherals::touch::{Ft3168Touch, SwipeDirection, TouchPoint}; use embedded_hal_bus::i2c::RefCellDevice; // Create touch controller let mut touch = Ft3168Touch::new(RefCellDevice::new(&i2c_ref)); // Initialize in monitor mode (GPIO38 asserts on touch) touch.init()?; // Hardware reset sequence touch_rst.set_low(); delay.delay_millis(10); touch_rst.set_high(); delay.delay_millis(50); // Read raw touch point (None if no touch) if let Ok(Some(point)) = touch.read() { println!("Touch at ({}, {}), fingers: {}", point.x, point.y, point.fingers); } // Poll with gesture detection (recommended for main loop) match touch.poll()? { (Some(point), None) => { // Finger currently touching, use point.x/y for tracking println!("Tracking: ({}, {})", point.x, point.y); } (None, Some(event)) => { // Finger lifted, gesture detected match event.direction { SwipeDirection::Up => println!("Swipe up!"), SwipeDirection::Down => println!("Swipe down!"), SwipeDirection::Left => println!("Swipe left!"), SwipeDirection::Right => println!("Swipe right!"), SwipeDirection::Tap => println!("Tap at ({}, {})", event.end_x, event.end_y), } } (None, None) => { // No touch activity } _ => {} } // Read hardware gesture ID let gesture = touch.read_gesture()?; ``` -------------------------------- ### Initialize and Update WatchFace Component Source: https://context7.com/infinition/waveshare-watch-rs/llms.txt Initialize the WatchFace component and update its display elements like time, date, battery status, and accelerometer data. Use needs_render() to check if a redraw is necessary. ```rust use crate::ui::watchface::WatchFace; use embedded_graphics::pixelcolor::Rgb565; // Create watchface let mut watchface = WatchFace::new(); // Update time (triggers dirty flag if changed) watchface.update_time(14, 30, 45); // 14:30:45 // Update date watchface.update_date(6, 4, 26); // April 6, 2026 // Update battery status watchface.update_battery(75, 3850, false); // 75%, 3850mV, not charging // Update accelerometer for gyro ball watchface.update_accel(0.1, -0.05, 0.98); // WiFi indicator watchface.wifi_connected = true; // Toggle gyroscope display (returns new state) let gyro_enabled = watchface.toggle_gyro(); // Check if tap is in gyro zone if WatchFace::is_gyro_zone(touch_y) { watchface.toggle_gyro(); } // Check if rendering is needed if watchface.needs_render() { // Force full redraw after sleep/wake watchface.force_redraw(); // Render to framebuffer let outcome = watchface.render(&mut fb)?; if outcome.full_redraw { fb.flush(&mut display); } } // Render Always-On Display (minimal HH:MM, anti burn-in) watchface.render_aod(&mut fb)?; ``` -------------------------------- ### Initialize and Control CO5300 AMOLED Display Source: https://context7.com/infinition/waveshare-watch-rs/llms.txt Manages the CO5300 AMOLED panel, including initialization, address windowing, brightness, and sleep states. Supports direct rendering via the `embedded-graphics` trait. ```rust use crate::drivers::co5300::Co5300Display; use crate::drivers::qspi_bus::QspiBus; use embedded_graphics::pixelcolor::Rgb565; use embedded_graphics::prelude::*; use embedded_graphics::primitives::{Rectangle, PrimitiveStyle}; // Create display with QSPI bus and reset pin let reset = Output::new(peripherals.GPIO8, Level::High, OutputConfig::default()); let mut display = Co5300Display::new(qspi_bus, reset); // Initialize the display (hardware reset + init sequence) display.init(); // Enable tearing effect for VSync display.bus_mut().write_c8d8(0x35, 0x00); // Set address window for partial updates display.set_addr_window(0, 0, 410, 502); // Fill entire screen with a color display.fill_screen(Rgb565::BLACK); // Fill rectangular area display.write_pixels_area(100, 100, 50, 50, Rgb565::RED); // Set brightness (0x00=off, 0xD0=default, 0xFF=max) display.set_brightness(0xD0); // Sleep control display.display_off(); // DISPOFF + SLPIN (saves power) display.display_on(); // SLPOUT + DISPON (wake up) // Direct drawing with embedded-graphics Rectangle::new(Point::new(10, 10), Size::new(100, 50)) .into_styled(PrimitiveStyle::with_fill(Rgb565::GREEN)) .draw(&mut display)?; ``` -------------------------------- ### Qmi8658Imu API Definition Source: https://github.com/infinition/waveshare-watch-rs/blob/main/README.md Defines the interface for reading accelerometer, gyroscope, and temperature data from the QMI8658 IMU. ```rust fn read_accel() -> AccelData // m.x, y, z in g fn read_gyro() -> GyroData // °/s fn read_temperature() -> f32 // °C fn power_up() / power_down() // CTRL7 0x03 / 0x00 ``` -------------------------------- ### Event-driven main loop Source: https://github.com/infinition/waveshare-watch-rs/blob/main/README.md The main loop uses an asynchronous select3 pattern to sleep until a timer, touch event, or button press occurs, significantly reducing CPU wake-ups. ```rust loop { // Choose tick based on state let tick = match (screen_state, app_state, current_page, gyro_enabled) { ... }; // Sleep until next event select3( Timer::after(tick), touch_int.wait_for_falling_edge(), boot_button.wait_for_falling_edge(), ).await; // Sensors throttled by need + screen_state if need_imu { imu.read_accel(); ... } if screen_state >= 2 && now >= next_rtc { rtc.get_time(); ... } if now >= next_battery { power.get_battery_percent(); ... } // Conditional touch poll (finger placed or just lifted) if touch_active { touch.poll(); ... } // Sleep/wake state machine → transitions 3→2→1→0 // WiFi auto-disconnect // AOD render path (1x/min) → continue // Screen OFF → continue // App state machine → render + conditional flush } ``` -------------------------------- ### NTP Sync and Power Management Source: https://context7.com/infinition/waveshare-watch-rs/llms.txt Performs NTP synchronization with the network stack and RTC. Includes logic to disconnect WiFi after a period of inactivity to conserve power, and reconnects when needed. ```rust // NTP sync to RTC ntp_sync(stack, &mut rtc).await?; // Auto-disconnect after idle (saves power) if idle_secs >= 300 { wifi_controller.disconnect_async().await?; } // Reconnect on wake wifi_controller.connect_async().await?; ``` -------------------------------- ### Configure AXP2101 Power Management Source: https://context7.com/infinition/waveshare-watch-rs/llms.txt Interface with the AXP2101 PMIC over I2C to monitor battery, system voltage, and charging status. ```rust use crate::peripherals::power::Axp2101Power; use embedded_hal_bus::i2c::RefCellDevice; use core::cell::RefCell; // Create I2C bus and power driver let i2c = I2c::new(peripherals.I2C0, I2cConfig::default().with_frequency(Rate::from_khz(400))) .with_sda(peripherals.GPIO15) .with_scl(peripherals.GPIO14); let i2c_ref = RefCell::new(i2c); let mut power = Axp2101Power::new(RefCellDevice::new(&i2c_ref)); // Initialize power rails and ADC power.init()?; // Read battery percentage (0-100) let battery_percent = power.get_battery_percent()?; // Read battery voltage in millivolts let battery_mv = power.get_battery_voltage()?; // Read system voltage let system_mv = power.get_system_voltage()?; // Read VBUS (USB) voltage let vbus_mv = power.get_vbus_voltage()?; // Check charging status let is_charging = power.is_charging()?; // Check if USB is connected let usb_connected = power.is_vbus_in()?; // Verify chip communication let chip_id = power.read_chip_id()?; ``` -------------------------------- ### Event-Driven Main Loop with Adaptive Tick Source: https://context7.com/infinition/waveshare-watch-rs/llms.txt Implements an event-driven main loop that sleeps the CPU between events using `select3`. The tick rate adapts based on the application state to minimize power consumption. ```rust use embassy_futures::select::select3; use embassy_time::{Duration, Instant, Timer}; use esp_hal::gpio::Input; // Touch and button interrupts let mut touch_int = Input::new(peripherals.GPIO38, InputConfig::default().with_pull(Pull::Up)); let mut boot_button = Input::new(peripherals.GPIO0, InputConfig::default().with_pull(Pull::Up)); loop { // Adaptive tick based on current state let tick = match (screen_state, app_state) { (0, _) => Duration::from_secs(30), // Screen off: 30s (1, _) => Duration::from_secs(10), // AOD: 10s (_, AppState::Watchface) => Duration::from_secs(1), // Clock: 1s (_, AppState::Flappy) => Duration::from_millis(8), // Flappy: 125Hz (_, AppState::Snake) => Duration::from_millis(16), // Games: 60Hz _ => Duration::from_millis(100), // Default: 10Hz }; // Sleep until event (CPU parked, GPIO interrupts wake) let _ = select3( Timer::after(tick), touch_int.wait_for_falling_edge(), boot_button.wait_for_falling_edge(), ).await; // Process sensors, input, and render based on state // ... } ``` -------------------------------- ### Manage Display Framebuffer Source: https://context7.com/infinition/waveshare-watch-rs/llms.txt Use the Framebuffer for PSRAM-backed double-buffered rendering. Supports direct pixel manipulation, embedded-graphics integration, and VSync-synchronized flushes. ```rust use crate::drivers::framebuffer::Framebuffer; use embedded_graphics::pixelcolor::Rgb565; use embedded_graphics::prelude::*; use embedded_graphics::primitives::{Circle, PrimitiveStyle}; // Allocate framebuffer in PSRAM let mut fb = Framebuffer::new(); // Clear to a solid color fb.clear_color(Rgb565::BLACK); // Direct pixel manipulation fb.set_pixel(100, 100, 0xF800); // Red pixel (RGB565) // Fill rectangle directly fb.fill_rect(50, 50, 100, 100, 0x07E0); // Green rectangle // Use embedded-graphics for complex drawing Circle::new(Point::new(200, 200), 50) .into_styled(PrimitiveStyle::with_fill(Rgb565::CYAN)) .draw(&mut fb)?; // Flush entire framebuffer to display fb.flush(&mut display); // VSync-synchronized flush (waits for TE signal) let te_pin = Input::new(peripherals.GPIO13, InputConfig::default()); fb.flush_vsync(&mut display, &te_pin); // Double-buffer swap + flush for games (zero tearing) fb.swap_and_flush(&mut display, &te_pin); // Partial region update (dirty rect optimization) fb.flush_region(&mut display, x, y, width, height); // Direct buffer access for snapshots let buffer: &[u16] = fb.buffer(); let buffer_mut: &mut [u16] = fb.buffer_mut(); ``` -------------------------------- ### Display Pipeline Data Flow Source: https://github.com/infinition/waveshare-watch-rs/blob/main/README.md Visual representation of the display pipeline from draw calls to DMA transfer. ```text Embedded-graphics draw calls │ ▼ 410×502 u16 RGB565 PSRAM Framebuffer (402 KB back buffer) │ │ fb.flush() OR fb.flush_vsync(te_pin) ▼ Co5300Display::set_addr_window(...) │ ▼ QspiBus::write_pixels() │ ▼ esp-hal SPI2 half_duplex_write( DataMode::Quad, ← 4-bit QSPI mode Command::_8Bit(0x12), ← write memory Address::_24Bit(0x003C00), dummy = 0, buffer, ← pixel data in quad mode ) │ ▼ DMA_CH0 → GPIO SIO0..SIO3 @ 80 MHz ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.