diff --git a/.gitignore b/.gitignore index 83f7521..b356432 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,9 @@ Cargo.lock .DS_Store Thumbs.db +# Local environment overrides +.env + # Debug files debug/ *.pdb diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d8bbd00 --- /dev/null +++ b/Makefile @@ -0,0 +1,63 @@ +## Default goal +.DEFAULT_GOAL := help + +SHELL := /bin/bash + +ROOT_DIR := $(abspath $(CURDIR)) +EX_DIR := examples +SELECT_PROBE_SCRIPT := $(ROOT_DIR)/scripts/select-probe.sh + +EXAMPLES := stm32g0-charging stm32g0-otg stm32g0-sc8815-sw2303 stm32g4 + +.PHONY: help select-probe + +help: + @echo 'Usage:' + @echo ' 1. eval "$$(make -s select-probe)"' + @echo ' 2. make ' + @echo + @echo 'Targets by example (release build + probe-rs tooling):' + @for ex in $(EXAMPLES); do \ + echo " $$ex: help run build attach reset reset-attach select-probe"; \ + done + @echo + @echo 'Shortcuts:' + @echo ' run- -> make -C examples/ run' + @echo ' build- -> make -C examples/ build' + @echo ' attach- -> make -C examples/ attach' + @echo ' reset- -> make -C examples/ reset' + @echo ' reset-attach- -> make -C examples/ reset-attach' + @echo ' help- -> make -C examples/ help' + @echo + @echo 'Examples:' + @echo ' make run-stm32g0-otg' + @echo ' make -C examples/stm32g0-otg help' + @echo ' eval "$$(make -s select-probe)"' + +select-probe: + @$(SELECT_PROBE_SCRIPT) + +define MAKE_DELEGATES +.PHONY: run-$(1) build-$(1) attach-$(1) reset-$(1) reset-attach-$(1) help-$(1) + +run-$(1): + $(MAKE) -C $(EX_DIR)/$(1) run + +build-$(1): + $(MAKE) -C $(EX_DIR)/$(1) build + +attach-$(1): + $(MAKE) -C $(EX_DIR)/$(1) attach + +reset-$(1): + $(MAKE) -C $(EX_DIR)/$(1) reset + +reset-attach-$(1): + $(MAKE) -C $(EX_DIR)/$(1) reset-attach + +help-$(1): + $(MAKE) -C $(EX_DIR)/$(1) help + +endef + +$(foreach ex,$(EXAMPLES),$(eval $(call MAKE_DELEGATES,$(ex)))) diff --git a/README.md b/README.md index 3890dd8..b862d54 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ where I2C::Error: core::fmt::Debug The SC8815 provides a GPO pin for external MOSFET control and power path management. -Note on semantics: writing `1` (true) enables the internal open-drain transistor and PULLS THE PIN LOW via ~6kΩ. Writing `0` (false) turns it OFF and leaves the pin HIGH-Z. +Semantics: writing `true` enables the internal open-drain transistor and pulls the pin LOW via ~6kΩ. Writing `false` turns it off and leaves the pin high-impedance. ```rust,ignore use sc8815::{SC8815, PowerPathStatus, registers::constants::DEFAULT_ADDRESS}; @@ -168,11 +168,7 @@ where I2C::Error: core::fmt::Debug **GPO Pin Behavior:** - `true` (1): GPO pin pulls LOW (6kΩ internal pull-down active, open-drain ON) -- `false` (0): GPO pin is HIGH-Z (open drain) - -API 变更(更清晰的语义): -- 替换 `set_gpo_control(true/false)` 为 `set_gpo_pull_low(true/false)`(true=拉低) -- 替换 `get_gpo_status()` 为 `is_gpo_pulled_low()` +- `false` (0): GPO pin is high-impedance (open-drain OFF) ## Feature Flags diff --git a/examples/stm32g0-charging/Makefile b/examples/stm32g0-charging/Makefile new file mode 100644 index 0000000..2aa8698 --- /dev/null +++ b/examples/stm32g0-charging/Makefile @@ -0,0 +1,51 @@ +SHELL := /bin/bash + +CARGO ?= cargo +PROBE_RS ?= probe-rs + +ROOT_DIR := $(abspath $(CURDIR)/../..) +TARGET_TRIPLE := thumbv6m-none-eabi +CHIP := STM32G031C8Ux +BINARY := sc8815_stm32g031c8u6_charging_example +ELF := target/$(TARGET_TRIPLE)/release/$(BINARY) +SELECT_PROBE_SCRIPT := $(ROOT_DIR)/scripts/select-probe.sh + +.PHONY: help select-probe build run attach reset reset-attach require-probe + +help: + @echo 'stm32g0-charging example targets:' + @echo ' help - Show this message' + @echo ' select-probe - Interactively choose a debug probe (prints export command)' + @echo ' build - cargo build --release --target $(TARGET_TRIPLE)' + @echo ' run - Build + probe-rs run --release' + @echo ' attach - Build + probe-rs attach' + @echo ' reset - probe-rs reset' + @echo ' reset-attach - Build + probe-rs reset + attach (software reset)' + @echo + @echo 'Before run/attach/reset targets, set PROBE_ID:' + @echo ' eval "$$(make -s select-probe)"' + +select-probe: + @$(SELECT_PROBE_SCRIPT) + +require-probe: + @if [ -z "$(PROBE_ID)" ]; then \ + echo 'PROBE_ID is not set. Run: eval "$$(make -s select-probe)"' >&2; \ + exit 1; \ + fi + +build: + $(CARGO) build --release --target $(TARGET_TRIPLE) + +run: require-probe build + $(PROBE_RS) run --probe $(PROBE_ID) --chip $(CHIP) $(ELF) + +attach: require-probe build + $(PROBE_RS) attach --probe $(PROBE_ID) --chip $(CHIP) $(ELF) + +reset: require-probe + $(PROBE_RS) reset --probe $(PROBE_ID) --chip $(CHIP) + +reset-attach: require-probe build + $(PROBE_RS) reset --probe $(PROBE_ID) --chip $(CHIP) + $(PROBE_RS) attach --probe $(PROBE_ID) --chip $(CHIP) $(ELF) diff --git a/examples/stm32g0-otg/.cargo/config.toml b/examples/stm32g0-otg/.cargo/config.toml index 4a3c22f..8c04da3 100644 --- a/examples/stm32g0-otg/.cargo/config.toml +++ b/examples/stm32g0-otg/.cargo/config.toml @@ -1,9 +1,10 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] runner = "probe-rs run --chip STM32G031G8" # to list chips, run `probe-rs chip list.` +linker = "rust-lld" rustflags = [ - "-C", "link-arg=-Tlink.x", "-C", "link-arg=-Tdefmt.x", + "-C", "link-arg=-Tlink.x", # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 "-C", "link-arg=--nmagic", diff --git a/examples/stm32g0-otg/Makefile b/examples/stm32g0-otg/Makefile new file mode 100644 index 0000000..9e36cf9 --- /dev/null +++ b/examples/stm32g0-otg/Makefile @@ -0,0 +1,51 @@ +SHELL := /bin/bash + +CARGO ?= cargo +PROBE_RS ?= probe-rs + +ROOT_DIR := $(abspath $(CURDIR)/../..) +TARGET_TRIPLE := thumbv6m-none-eabi +CHIP := STM32G031G8 +BINARY := sc8815_stm32g031g8_otg_example +ELF := target/$(TARGET_TRIPLE)/release/$(BINARY) +SELECT_PROBE_SCRIPT := $(ROOT_DIR)/scripts/select-probe.sh + +.PHONY: help select-probe build run attach reset reset-attach require-probe + +help: + @echo 'stm32g0-otg example targets:' + @echo ' help - Show this message' + @echo ' select-probe - Interactively choose a debug probe (prints export command)' + @echo ' build - cargo build --release --target $(TARGET_TRIPLE)' + @echo ' run - Build + probe-rs run --release' + @echo ' attach - Build + probe-rs attach' + @echo ' reset - probe-rs reset' + @echo ' reset-attach - Build + probe-rs reset + attach (software reset)' + @echo + @echo 'Before run/attach/reset targets, set PROBE_ID:' + @echo ' eval "$$(make -s select-probe)"' + +select-probe: + @$(SELECT_PROBE_SCRIPT) + +require-probe: + @if [ -z "$(PROBE_ID)" ]; then \ + echo 'PROBE_ID is not set. Run: eval "$$(make -s select-probe)"' >&2; \ + exit 1; \ + fi + +build: + $(CARGO) build --release --target $(TARGET_TRIPLE) + +run: require-probe build + $(PROBE_RS) run --probe $(PROBE_ID) --chip $(CHIP) $(ELF) + +attach: require-probe build + $(PROBE_RS) attach --probe $(PROBE_ID) --chip $(CHIP) $(ELF) + +reset: require-probe + $(PROBE_RS) reset --probe $(PROBE_ID) --chip $(CHIP) + +reset-attach: require-probe build + $(PROBE_RS) reset --probe $(PROBE_ID) --chip $(CHIP) + $(PROBE_RS) attach --probe $(PROBE_ID) --chip $(CHIP) $(ELF) diff --git a/examples/stm32g0-sc8815-sw2303/.cargo/config.toml b/examples/stm32g0-sc8815-sw2303/.cargo/config.toml new file mode 100644 index 0000000..2b6d463 --- /dev/null +++ b/examples/stm32g0-sc8815-sw2303/.cargo/config.toml @@ -0,0 +1,15 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs run --chip STM32G031G8" +linker = "rust-lld" + +rustflags = [ + "-C", "link-arg=-Tdefmt.x", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=--nmagic", +] + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "info" diff --git a/examples/stm32g0-sc8815-sw2303/Cargo.toml b/examples/stm32g0-sc8815-sw2303/Cargo.toml new file mode 100644 index 0000000..0bcde18 --- /dev/null +++ b/examples/stm32g0-sc8815-sw2303/Cargo.toml @@ -0,0 +1,72 @@ +[package] +name = "sc8815_sw2303_fb_example" +version = "0.1.0" +edition = "2024" + +[dependencies] +embassy-stm32 = { git = "https://github.com/embassy-rs/embassy.git", rev = "94f9b2707486ca3eade5bf4b237edf3d6aa90f35", features = [ + "defmt", + "time-driver-any", + "stm32g031g8", + "memory-x", + "unstable-pac", + "exti", +] } +embassy-embedded-hal = { git = "https://github.com/embassy-rs/embassy.git", rev = "94f9b2707486ca3eade5bf4b237edf3d6aa90f35" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", rev = "94f9b2707486ca3eade5bf4b237edf3d6aa90f35", features = [ + "arch-cortex-m", + "executor-thread", + "defmt", +] } +embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "94f9b2707486ca3eade5bf4b237edf3d6aa90f35", features = [ + "defmt", + "defmt-timestamp-uptime", + "tick-hz-32_768", +] } +embassy-sync = { git = "https://github.com/embassy-rs/embassy.git", rev = "94f9b2707486ca3eade5bf4b237edf3d6aa90f35", features = [ + "defmt", +] } + +defmt = "1.0.1" +defmt-rtt = "1.0.0" + +cortex-m = { version = "0.7.7", features = [ + "inline-asm", + "critical-section-single-core", +] } +cortex-m-rt = "0.7.5" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +portable-atomic = { version = "1.11.0", features = ["critical-section"] } +static_cell = "2.1.0" +libm = "0.2.8" + +sc8815 = { version = "*", path = "../../", features = ["async", "defmt"] } +sw2303 = { version = "*", path = "../../../sw2303-rs", features = ["async", "defmt"] } + +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true +incremental = false +opt-level = 3 +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +strip = "none" +debug-assertions = false +incremental = false +lto = "thin" +opt-level = 2 +overflow-checks = false + +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = 3 +overflow-checks = false diff --git a/examples/stm32g0-sc8815-sw2303/Makefile b/examples/stm32g0-sc8815-sw2303/Makefile new file mode 100644 index 0000000..9441b0a --- /dev/null +++ b/examples/stm32g0-sc8815-sw2303/Makefile @@ -0,0 +1,51 @@ +SHELL := /bin/bash + +CARGO ?= cargo +PROBE_RS ?= probe-rs + +ROOT_DIR := $(abspath $(CURDIR)/../..) +TARGET_TRIPLE := thumbv6m-none-eabi +CHIP := STM32G031G8 +BINARY := sc8815_sw2303_fb_example +ELF := target/$(TARGET_TRIPLE)/release/$(BINARY) +SELECT_PROBE_SCRIPT := $(ROOT_DIR)/scripts/select-probe.sh + +.PHONY: help select-probe build run attach reset reset-attach require-probe + +help: + @echo 'stm32g0-sc8815-sw2303 example targets:' + @echo ' help - Show this message' + @echo ' select-probe - Interactively choose a debug probe (prints export command)' + @echo ' build - cargo build --release --target $(TARGET_TRIPLE)' + @echo ' run - Build + probe-rs run --release' + @echo ' attach - Build + probe-rs attach' + @echo ' reset - probe-rs reset' + @echo ' reset-attach - Build + probe-rs reset + attach (software reset)' + @echo + @echo 'Before run/attach/reset targets, set PROBE_ID:' + @echo ' eval "$$(make -s select-probe)"' + +select-probe: + @$(SELECT_PROBE_SCRIPT) + +require-probe: + @if [ -z "$(PROBE_ID)" ]; then \ + echo 'PROBE_ID is not set. Run: eval "$$(make -s select-probe)"' >&2; \ + exit 1; \ + fi + +build: + $(CARGO) build --release --target $(TARGET_TRIPLE) + +run: require-probe build + $(PROBE_RS) run --probe $(PROBE_ID) --chip $(CHIP) $(ELF) + +attach: require-probe build + $(PROBE_RS) attach --probe $(PROBE_ID) --chip $(CHIP) $(ELF) + +reset: require-probe + $(PROBE_RS) reset --probe $(PROBE_ID) --chip $(CHIP) + +reset-attach: require-probe build + $(PROBE_RS) reset --probe $(PROBE_ID) --chip $(CHIP) + $(PROBE_RS) attach --probe $(PROBE_ID) --chip $(CHIP) $(ELF) diff --git a/examples/stm32g0-sc8815-sw2303/README.md b/examples/stm32g0-sc8815-sw2303/README.md new file mode 100644 index 0000000..b5de076 --- /dev/null +++ b/examples/stm32g0-sc8815-sw2303/README.md @@ -0,0 +1,45 @@ +# SC8815 + SW2303 FB Regulation Demo (STM32G031G8U6) + +This example configures an SC8815 power-management IC and an SW2303 USB-PD controller on an STM32G031G8U6 platform. VBUS is regulated through the SC8815 FB network using an internal 1.2 V reference and external resistors, while the SW2303 negotiates charging protocols and provides telemetry. + +## Hardware Overview + +- **MCU**: STM32G031G8U6 (PB6 = I²C1 SCL, PB7 = I²C1 SDA, PB5 = SC8815 PSTOP, PB8 = status LED) +- **SC8815**: + - Operating mode: OTG (discharge) + - External FB divider: Rp = 100 kΩ, Rd = 31.6 kΩ → ≈5 V output with 1.2 V reference + - Sense resistors: RS1 = RS2 = 5 mΩ + - Current limits: IBUS = 3.5 A, IBAT = 2 A + - VBUS/VBAT sensing: **internal VBAT monitoring (VBATS tied to VBAT)** with driver defaults targeting a 4S pack + - Switching frequency: 450 kHz, dead time: 80 ns +- **SW2303**: + - Default protocol set, monitoring of negotiated fast-charging mode + - 12-bit ADC readings for VBUS and output current (ICH) + +## Firmware Behavior + +1. Hold `PSTOP` high to keep SC8815 in standby. +2. Configure SC8815 (OTG mode, FB network, ADC). +3. Set `PSTOP` low and wait 500 ms for stabilization. +4. Initialize SW2303 on the same I²C bus. +5. Every 500 ms log: + - SC8815 VBAT / IBAT / VBUS / IBUS + - SW2303 VBUS / IBUS (ICH) + - Active SW2303 protocol (if any) + +Any configuration failure triggers a repeating LED blink fault loop. + +## Building & Running + +```bash +# From this example directory +make build # Cross-builds for thumbv6m-none-eabi (release profile) +eval "$(make -s select-probe)" # Optional helper to choose a probe +make run # Flash + run via probe-rs +``` + +Debug output is available via `defmt-rtt`. To keep the control loop stable: + +- Keep `VBATS` hard-wired to `VBAT` so the SC8815 uses its internal VBAT monitor. +- Use the bundled configuration (4S battery defaults) unless you also redesign the resistor network and ADC ratios. +- Verify the external FB network (Rp/Rd) is wired exactly as shown to avoid hardware damage. diff --git a/examples/stm32g0-sc8815-sw2303/src/main.rs b/examples/stm32g0-sc8815-sw2303/src/main.rs new file mode 100644 index 0000000..cf346f2 --- /dev/null +++ b/examples/stm32g0-sc8815-sw2303/src/main.rs @@ -0,0 +1,306 @@ +#![no_std] +#![no_main] + +// SC8815 + SW2303 STM32G031G8U6 FB Regulation Demo +// Demonstrates power bank configuration with SC8815 regulating VBUS through an external FB divider +// and SW2303 handling protocol negotiation / monitoring on the same bus. + +use defmt::{error, info, warn}; +use embassy_executor::Spawner; +use embassy_stm32::{ + bind_interrupts, + gpio::{Level, Output, Speed}, + i2c::{self, Config as I2cConfig, I2c}, + mode::Async as I2cAsyncMode, + peripherals::I2C1, + time::Hertz, +}; +use embassy_time::{Duration, Timer}; +use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; +use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +use sc8815::{ + CellCount, DeadTime, DeviceConfiguration, IbusRatio, OperatingMode, SC8815, SwitchingFrequency, + VoltagePerCell, VbusRatio, registers::constants::DEFAULT_ADDRESS as SC8815_DEFAULT_ADDRESS, +}; +use sw2303::{ + ProtocolType, SW2303, + registers::constants::DEFAULT_ADDRESS as SW2303_DEFAULT_ADDRESS, +}; + +// External FB divider parameters: Rp = 100kΩ, Rd = 31.6kΩ +const FB_R_UP_OHM: u32 = 100_000; +const FB_R_DOWN_OHM: u32 = 31_600; +const FB_REFERENCE_MV: u16 = 1200; // Reference voltage applied to FB network +const TARGET_VBUS_MV: u16 = 5000; // Expected output voltage after resistor scaling + +const SC_IBUS_LIMIT_MA: u16 = 3_500; +const SC_IBAT_LIMIT_MA: u16 = 2_000; +const SC_RSENSE_MOHM: u16 = 5; + +const SW_LOG_INTERVAL: Duration = Duration::from_millis(500); +const SC_CONFIG_TO_SW_DELAY: Duration = Duration::from_millis(500); + +type SharedI2cBus = Mutex>; + +static I2C_BUS: StaticCell = StaticCell::new(); + +bind_interrupts!(struct Irqs { + I2C1 => i2c::EventInterruptHandler, i2c::ErrorInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("SC8815 + SW2303 FB regulation demo starting"); + + // Status LED on PB8 (active low) + let mut led = Output::new(p.PB8, Level::High, Speed::Low); + + // PSTOP on PB5 keeps SC8815 in standby until configuration is complete. + let mut pstop = Output::new(p.PB5, Level::High, Speed::Low); + info!("PSTOP held HIGH - SC8815 in standby for safe configuration"); + + // Configure I2C1 (PB6 = SCL, PB7 = SDA) @ 100 kHz with internal pull-ups. + let mut i2c_cfg = I2cConfig::default(); + i2c_cfg.scl_pullup = true; + i2c_cfg.sda_pullup = true; + + let i2c = I2c::new( + p.I2C1, + p.PB6, + p.PB7, + Irqs, + p.DMA1_CH1, + p.DMA1_CH2, + Hertz(100_000), + i2c_cfg, + ); + + // Share I2C bus between SC8815 and SW2303. + let bus_handle: &'static SharedI2cBus = I2C_BUS.init(Mutex::new(i2c)); + + let sc_i2c = I2cDevice::new(bus_handle); + let mut sc8815 = SC8815::new(sc_i2c, SC8815_DEFAULT_ADDRESS); + + let mut sw_i2c = I2cDevice::new(bus_handle); + let mut sw2303 = SW2303::new(&mut sw_i2c, SW2303_DEFAULT_ADDRESS); + + Timer::after(Duration::from_millis(100)).await; + + info!("Initializing SC8815 (address 0x{:02X})", SC8815_DEFAULT_ADDRESS); + if let Err(e) = sc8815.init().await { + error!("SC8815 init failed: {:?}", e); + signal_error(&mut led).await; + } + + info!("Preparing SC8815 device configuration for FB-regulated OTG output"); + let mut config = DeviceConfiguration::default(); + config.battery.cell_count = CellCount::Cells4S; + config.battery.voltage_per_cell = VoltagePerCell::Mv4200; + config.battery.use_internal_setting = true; + config.trickle_charging = false; + config.charging_termination = false; + config.use_ibus_for_charging = false; + + config.current_limits.ibus_limit_ma = SC_IBUS_LIMIT_MA; + config.current_limits.ibat_limit_ma = SC_IBAT_LIMIT_MA; + config.current_limits.ibus_ratio = IbusRatio::Ratio6x; + config.current_limits.rs1_mohm = SC_RSENSE_MOHM; + config.current_limits.rs2_mohm = SC_RSENSE_MOHM; + + config.power.operating_mode = OperatingMode::OTG; + config.power.switching_frequency = SwitchingFrequency::Freq450kHz; + config.power.dead_time = DeadTime::Ns80; + config.power.frequency_dithering = false; + config.power.pfm_mode = false; + config.power.vbus_ratio = VbusRatio::Ratio12_5x; + + if let Err(e) = sc8815.set_short_foldback_disable(true).await { + warn!("Failed to disable short foldback: {:?}", e); + } + + if let Err(e) = sc8815.configure_device(&config).await { + error!("SC8815 configure_device failed: {:?}", e); + signal_error(&mut led).await; + } + + info!("Setting SC8815 external feedback reference to {}mV", FB_REFERENCE_MV); + if let Err(e) = sc8815.set_vbus_external_reference(FB_REFERENCE_MV).await { + error!("Failed to set external VBUS reference: {:?}", e); + signal_error(&mut led).await; + } + + if let Ok(vref_e_set) = sc8815 + .read_register(sc8815::registers::Register::VbusrefESet) + .await + { + if let Ok(vref_e_set2) = sc8815 + .read_register(sc8815::registers::Register::VbusrefESet2) + .await + { + info!( + "VBUSREF_E registers: SET=0x{:02X}, SET2=0x{:02X}", + vref_e_set, vref_e_set2 + ); + } + } + + if let Err(e) = sc8815.set_vbus_slew_rate(1).await { + warn!("Unable to configure SC8815 slew rate: {:?}", e); + } + + if let Err(e) = sc8815.set_adc_conversion(true).await { + error!("Failed to start SC8815 ADC conversion: {:?}", e); + signal_error(&mut led).await; + } + + info!("SC8815 configuration complete, enabling power blocks (PSTOP LOW)"); + pstop.set_low(); + Timer::after(Duration::from_millis(10)).await; + + info!( + "✅ SC8815 configured as power bank: expected {}V output (external FB), {}A/{}A current limits", + TARGET_VBUS_MV / 1000, + SC_IBUS_LIMIT_MA as f32 / 1000.0, + SC_IBAT_LIMIT_MA as f32 / 1000.0 + ); + info!( + "External FB divider: Rp={}Ω, Rd={}Ω, reference {}mV", + FB_R_UP_OHM, FB_R_DOWN_OHM, FB_REFERENCE_MV + ); + + if let Ok(ratio_reg) = sc8815 + .read_register(sc8815::registers::Register::Ratio) + .await + { + info!("RATIO register: 0x{:02X}", ratio_reg); + } + + info!("Waiting {:?} before configuring SW2303", SC_CONFIG_TO_SW_DELAY); + Timer::after(SC_CONFIG_TO_SW_DELAY).await; + + info!("Initializing SW2303 (address 0x{:02X})", SW2303_DEFAULT_ADDRESS); + let mut sw2303_online = false; + let mut sw2303_error_logged = false; + + if let Err(e) = sw2303.init().await { + error!("SW2303 init failed: {:?}", e); + signal_error(&mut led).await; + } else { + match sw2303.get_system_status0().await { + Ok(flags) => { + info!( + "SW2303 initial system status0=0x{:02X}", + flags.bits() + ); + sw2303_online = true; + } + Err(e) => { + warn!( + "SW2303 not responding to initial status read (will retry): {:?}", + e + ); + } + } + } + + info!("Configuration finished; entering monitoring loop"); + + loop { + led.toggle(); + + let (sc_vbus_mv, sc_vbat_mv, sc_ibus_ma, sc_ibat_ma, _) = match sc8815.read_all_adc_values().await { + Ok(values) => values, + Err(e) => { + error!("SC8815 ADC read failed: {:?}", e); + Timer::after(SW_LOG_INTERVAL).await; + continue; + } + }; + + let mut sw_vbus_mv: u32 = 0; + let mut sw_ibus_ma: u32 = 0; + let mut negotiated: Option = None; + + if sw2303_online { + match sw2303.read_vbus_mv_12bit().await { + Ok(v) => sw_vbus_mv = v, + Err(e) => { + warn!("SW2303 VBUS read failed: {:?} (marking device offline)", e); + sw2303_online = false; + sw2303_error_logged = false; + } + } + + if sw2303_online { + match sw2303.read_ich_ma_12bit().await { + Ok(i) => sw_ibus_ma = i, + Err(e) => { + warn!( + "SW2303 IBUS read failed: {:?} (marking device offline)", + e + ); + sw2303_online = false; + sw2303_error_logged = false; + } + } + } + + if sw2303_online { + match sw2303.get_negotiated_protocol().await { + Ok(proto) => negotiated = proto, + Err(e) => { + warn!( + "SW2303 protocol query failed: {:?} (marking device offline)", + e + ); + sw2303_online = false; + sw2303_error_logged = false; + } + } + } + } + + if !sw2303_online { + match sw2303.get_system_status0().await { + Ok(flags) => { + info!( + "SW2303 reconnected, system status0=0x{:02X}", + flags.bits() + ); + sw2303_online = true; + sw2303_error_logged = false; + } + Err(e) => { + if !sw2303_error_logged { + warn!("SW2303 not responding (telemetry skipped): {:?}", e); + sw2303_error_logged = true; + } + } + } + } + + info!( + "SC8815 VBAT={}mV IBAT={}mA | VBUS={}mV IBUS={}mA || SW2303 VBUS={}mV IBUS={}mA | Protocol={:?}", + sc_vbat_mv, + sc_ibat_ma, + sc_vbus_mv, + sc_ibus_ma, + sw_vbus_mv, + sw_ibus_ma, + negotiated + ); + + Timer::after(SW_LOG_INTERVAL).await; + } +} + +async fn signal_error(led: &mut Output<'_>) -> ! { + loop { + led.toggle(); + Timer::after(Duration::from_millis(250)).await; + } +} diff --git a/examples/stm32g4/Makefile b/examples/stm32g4/Makefile new file mode 100644 index 0000000..55c476e --- /dev/null +++ b/examples/stm32g4/Makefile @@ -0,0 +1,51 @@ +SHELL := /bin/bash + +CARGO ?= cargo +PROBE_RS ?= probe-rs + +ROOT_DIR := $(abspath $(CURDIR)/../..) +TARGET_TRIPLE := thumbv7em-none-eabihf +CHIP := STM32G431CBUx +BINARY := sc8815_stm32g474re_example +ELF := target/$(TARGET_TRIPLE)/release/$(BINARY) +SELECT_PROBE_SCRIPT := $(ROOT_DIR)/scripts/select-probe.sh + +.PHONY: help select-probe build run attach reset reset-attach require-probe + +help: + @echo 'stm32g4 example targets:' + @echo ' help - Show this message' + @echo ' select-probe - Interactively choose a debug probe (prints export command)' + @echo ' build - cargo build --release --target $(TARGET_TRIPLE)' + @echo ' run - Build + probe-rs run --release' + @echo ' attach - Build + probe-rs attach' + @echo ' reset - probe-rs reset' + @echo ' reset-attach - Build + probe-rs reset + attach (software reset)' + @echo + @echo 'Before run/attach/reset targets, set PROBE_ID:' + @echo ' eval "$$(make -s select-probe)"' + +select-probe: + @$(SELECT_PROBE_SCRIPT) + +require-probe: + @if [ -z "$(PROBE_ID)" ]; then \ + echo 'PROBE_ID is not set. Run: eval "$$(make -s select-probe)"' >&2; \ + exit 1; \ + fi + +build: + $(CARGO) build --release --target $(TARGET_TRIPLE) + +run: require-probe build + $(PROBE_RS) run --probe $(PROBE_ID) --chip $(CHIP) $(ELF) + +attach: require-probe build + $(PROBE_RS) attach --probe $(PROBE_ID) --chip $(CHIP) $(ELF) + +reset: require-probe + $(PROBE_RS) reset --probe $(PROBE_ID) --chip $(CHIP) + +reset-attach: require-probe build + $(PROBE_RS) reset --probe $(PROBE_ID) --chip $(CHIP) + $(PROBE_RS) attach --probe $(PROBE_ID) --chip $(CHIP) $(ELF) diff --git a/scripts/select-probe.sh b/scripts/select-probe.sh new file mode 100755 index 0000000..a7135d5 --- /dev/null +++ b/scripts/select-probe.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +set -euo pipefail + +if ! command -v probe-rs >/dev/null 2>&1; then + echo "probe-rs command not found. Install probe-rs-cli first." >&2 + exit 1 +fi + +entries=() +indices=() +ids=() + +while IFS= read -r line; do + trimmed="${line## }" + if [[ -z "${trimmed}" ]]; then + continue + fi + if [[ "${line}" =~ -- ]]; then + entries+=("${line}") + index="" + if [[ "${line}" =~ ^\[([0-9]+)\]: ]]; then + index="${BASH_REMATCH[1]}" + elif [[ "${line}" =~ ^([0-9]+): ]]; then + index="${BASH_REMATCH[1]}" + fi + if [[ -n "${index}" ]] && [[ "${line}" =~ --[[:space:]]*([0-9a-fA-F]{4}:[0-9a-fA-F]{4}(:[^[:space:]]+)?) ]]; then + indices+=("${index}") + ids+=("${BASH_REMATCH[1]}") + fi + fi +done < <(probe-rs list) + +if [[ ${#entries[@]} -eq 0 ]]; then + echo "No debug probes detected. Connect a probe and retry." >&2 + exit 1 +fi + +echo "Detected debug probes:" >&2 +for entry in "${entries[@]}"; do + echo " ${entry}" >&2 +done + +echo >&2 +read -r -p "Select probe index or enter VID:PID[:SERIAL]: " choice + +if [[ -z "${choice}" ]]; then + echo "Selection cancelled." >&2 + exit 1 +fi + +lookup_id() { + local target="$1" + local i=0 + local count="${#indices[@]}" + while [[ $i -lt $count ]]; do + if [[ "${indices[$i]}" == "${target}" ]]; then + echo "${ids[$i]}" + return 0 + fi + i=$((i + 1)) + done + return 1 +} + +selected="" + +if [[ "${choice}" =~ ^[0-9]+$ ]]; then + if selected=$(lookup_id "${choice}"); then + true + else + selected="" + fi + if [[ -z "${selected}" ]]; then + echo "Could not resolve probe with index ${choice}." >&2 + exit 1 + fi +else + selected="${choice}" +fi + +if [[ ! "${selected}" =~ ^[0-9a-fA-F]{4}:[0-9a-fA-F]{4}(:[^[:space:]]+)?$ ]]; then + echo "Invalid probe identifier: ${selected}" >&2 + exit 1 +fi + +echo "export PROBE_ID=${selected}"