working uart but not waking after sleep

This commit is contained in:
Priec
2025-12-03 21:41:45 +01:00
parent 3ebbd97760
commit c7a74df023
22 changed files with 2513 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
// src/bin/main.rs
#![no_std]
#![no_main]
use defmt::*;
use embassy_stm32::pac;
use embassy_executor::Spawner;
use embassy_futures::yield_now;
use embassy_stm32::bind_interrupts;
use embassy_stm32::peripherals;
use embassy_stm32::Config;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel};
use embassy_stm32::usart::{BufferedUart, Config as UsartConfig, BufferedInterruptHandler};
use embassy_stm32::gpio::{Output, Level, Speed};
use embassy_time::{Duration, Timer};
use static_cell::StaticCell;
use dma_gpio::config::{
BAUD, PIPE_HW_RX, PIPE_HW_TX,
};
use dma_gpio::hw_uart_pc::driver::uart_task;
use dma_gpio::wakeup::iwdg::{clear_wakeup_flags, init_watchdog};
use dma_gpio::sleep::shutdown;
use dma_gpio::sleep::standby;
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
USART1 => BufferedInterruptHandler<peripherals::USART1>;
});
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum LowPowerCmd {
Standby8k, // 1
StandbyFull, // 2
Standby, // 3
Shutdown, // 4
}
static CMD_CH: Channel<CriticalSectionRawMutex, LowPowerCmd, 1> = Channel::new();
#[embassy_executor::task]
async fn uart_cmd_task() {
// Prompt once
let _ = PIPE_HW_TX
.write(b"Modes: 1=SB8, 2=SBfull, 3=SB, 4=SD\r\n")
.await;
let mut b = [0u8; 1];
loop {
let n = PIPE_HW_RX.read(&mut b).await;
if n == 0 {
continue;
}
match b[0] {
b'1' => { let _ = PIPE_HW_TX.write(b"ACK 1: standby 8KB\r\n").await; CMD_CH.send(LowPowerCmd::Standby8k).await; }
b'2' => { let _ = PIPE_HW_TX.write(b"ACK 2: standby full\r\n").await; CMD_CH.send(LowPowerCmd::StandbyFull).await; }
b'3' => { let _ = PIPE_HW_TX.write(b"ACK 3: standby\r\n").await; CMD_CH.send(LowPowerCmd::Standby).await; }
b'4' => { let _ = PIPE_HW_TX.write(b"ACK 4: shutdown\r\n").await; CMD_CH.send(LowPowerCmd::Shutdown).await; }
b'\r' | b'\n' | b' ' => {}
_ => { let _ = PIPE_HW_TX.write(b"ERR: use 1|2|3|4\r\n").await; }
}
yield_now().await;
}
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
info!("boot");
let p = embassy_stm32::init(Config::default());
let mut led = Output::new(p.PA3, Level::Low, Speed::Low);
let _led_ground = Output::new(p.PB0, Level::Low, Speed::Low);
info!("init m8");
clear_wakeup_flags();
led.set_high();
info!("LED ON (MCU awake)");
// HARDWARE UART to the PC
let mut cfg = UsartConfig::default();
cfg.baudrate = BAUD;
static TX_BUF: StaticCell<[u8; 256]> = StaticCell::new();
static RX_BUF: StaticCell<[u8; 256]> = StaticCell::new();
let uart = BufferedUart::new(
p.USART1,
p.PA10, // RX pin
p.PA9, // TX pin
TX_BUF.init([0; 256]),
RX_BUF.init([0; 256]),
Irqs,
cfg,
).unwrap();
// let yield_period = usart1::setup_and_spawn(BAUD);
spawner.spawn(uart_task(uart, &PIPE_HW_TX, &PIPE_HW_RX).unwrap());
spawner.spawn(uart_cmd_task().unwrap());
// END OF HARDWARE UART to the PC
// DEBUG INFO
let dbg = pac::DBGMCU;
let cr = dbg.cr().read();
info!("DBGMCU CR: dbg_stop={}, dbg_standby={}", cr.dbg_stop(), cr.dbg_standby());
use dma_gpio::config::WATCHDOG_TIMEOUT_US;
info!(
"Baud: {} bps | Watchdog: {}s\n\
UART1 TX=PA9 RX=PA10\n\
Modes via UART input:\n\
[1] Standby + 8 KB SRAM2 retention\n\
[2] Standby + full SRAM2 retention\n\
[3] Standby — minimal power, SRAM2 lost\n\
[4] Shutdown — lowest power, full reset on wake",
BAUD, WATCHDOG_TIMEOUT_US / 1_000_000
);
// END OF DEBUG INFO
// MAIN LOOP
Timer::after(Duration::from_millis(500)).await;
Timer::after(Duration::from_millis(10)).await;
info!("ready for uart");
loop {
let cmd = CMD_CH.receive().await;
match cmd {
LowPowerCmd::Standby8k => {
init_watchdog(p.IWDG).await; // watchdog reset at configurated time
Timer::after(Duration::from_millis(10)).await; // let UART flush
standby::enter_standby_with_sram2_8kb();
}
LowPowerCmd::StandbyFull => {
init_watchdog(p.IWDG).await;
Timer::after(Duration::from_millis(10)).await;
standby::enter_standby_with_sram2_full();
}
LowPowerCmd::Standby => {
init_watchdog(p.IWDG).await;
Timer::after(Duration::from_millis(10)).await;
standby::enter_standby();
}
LowPowerCmd::Shutdown => {
Timer::after(Duration::from_millis(10)).await;
shutdown::enter_shutdown();
}
}
}
}

View File

@@ -0,0 +1,12 @@
// src/config.rs
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::pipe::Pipe;
pub const BAUD: u32 = 9_600;
pub const PIPE_HW_TX_SIZE: usize = 1024;
pub const PIPE_HW_RX_SIZE: usize = 1024;
pub const WATCHDOG_TIMEOUT_US: u32 = 2_000_000; // 2 seconds
pub static PIPE_HW_TX: Pipe<CriticalSectionRawMutex, PIPE_HW_TX_SIZE> = Pipe::new();
pub static PIPE_HW_RX: Pipe<CriticalSectionRawMutex, PIPE_HW_RX_SIZE> = Pipe::new();

View File

@@ -0,0 +1,41 @@
// src/hw_uart_pc/driver.rs
use defmt::unwrap;
use embassy_futures::select::{select, Either};
use embassy_stm32::usart::BufferedUart;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::pipe::Pipe;
use embedded_io_async::{Read, Write};
use crate::hw_uart_pc::safety::{RX_PIPE_CAP, TX_PIPE_CAP};
use embassy_futures::yield_now;
#[embassy_executor::task]
pub async fn uart_task(
mut uart: BufferedUart<'static>,
tx_pipe: &'static Pipe<CriticalSectionRawMutex, TX_PIPE_CAP>,
rx_pipe: &'static Pipe<CriticalSectionRawMutex, RX_PIPE_CAP>,
) {
let mut rx_byte = [0u8; 1];
let mut tx_buf = [0u8; 64];
loop {
let rx_fut = uart.read(&mut rx_byte);
let tx_fut = async {
let n = tx_pipe.read(&mut tx_buf).await;
n
};
match select(rx_fut, tx_fut).await {
// Incoming data from UART hardware
Either::First(res) => {
if let Ok(_) = res {
let _ = rx_pipe.write(&rx_byte).await;
}
}
// Outgoing data waiting in TX pipe
Either::Second(n) => {
unwrap!(uart.write(&tx_buf[..n]).await);
}
}
yield_now().await;
}
}

View File

@@ -0,0 +1,4 @@
// src/hw_uart_pc/mod.rs
pub mod driver;
pub mod usart1;
pub mod safety;

View File

@@ -0,0 +1,57 @@
// src/safety.rs
use defmt::info;
use embassy_time::Duration;
// ISR RX ring capacity = RX_BUF len
const ISR_RX_BUF_CAP: usize = 256;
// Yield 1/2 the time it takes to fill ISR RX ring.
const YIELD_MARGIN_NUM: u32 = 1;
const YIELD_MARGIN_DEN: u32 = 2;
// Ensure RX_PIPE_CAP can hold this.
const WORST_MAIN_LATENCY_MS: u32 = 20;
pub const TX_PIPE_CAP: usize = 1024;
pub const RX_PIPE_CAP: usize = 1024;
/// Perform safety checks and compute yield timing to avoid buffer overflow.
///
/// # Panics
/// Panics if pipe capacities are too small for the configured baud.
pub fn preflight_and_suggest_yield_period(baud: u32) -> Duration {
// Approx bytes per second for 8N1 (10 bits per byte on the wire)
let bytes_per_sec = (baud / 10).max(1);
// Time until ISR RX ring fills, in microseconds.
let t_fill_us = (ISR_RX_BUF_CAP as u64) * 1_000_000u64 / (bytes_per_sec as u64);
// Choose a yield period as a fraction of t_fill.
let yield_us = (t_fill_us as u64)
.saturating_mul(YIELD_MARGIN_NUM as u64)
/ (YIELD_MARGIN_DEN as u64);
// Verify RX pipe can absorb a worst-case app latency so uart_task
// can always forward without dropping when it runs.
let required_rx_pipe = (bytes_per_sec as u64) * (WORST_MAIN_LATENCY_MS as u64) / 1000;
if (RX_PIPE_CAP as u64) < required_rx_pipe {
core::panic!(
"RX pipe too small: have {}B, need >= {}B for {}ms at {} bps",
RX_PIPE_CAP, required_rx_pipe, WORST_MAIN_LATENCY_MS, baud
);
}
info!(
"Preflight: baud={}, rx_isr={}B, rx_pipe={}B, bytes/s={}, t_fill_us={}, yield_us={}",
baud,
ISR_RX_BUF_CAP,
RX_PIPE_CAP,
bytes_per_sec,
t_fill_us,
yield_us
);
// Never choose zero.
Duration::from_micros(yield_us.max(1) as u64)
}

View File

@@ -0,0 +1,12 @@
// src/uart/usart1.rs
use defmt::info;
use embassy_time::Duration;
use crate::hw_uart_pc::safety::preflight_and_suggest_yield_period;
pub fn setup_and_spawn(baudrate: u32,) -> Duration {
let yield_period: Duration = preflight_and_suggest_yield_period(baudrate);
info!("HW USART1 safe");
yield_period
}

View File

@@ -0,0 +1,8 @@
#![no_std]
// pub mod low_power;
// pub use low_power::*;
pub mod hw_uart_pc;
pub mod config;
pub mod sleep;
pub mod wakeup;

View File

@@ -0,0 +1,4 @@
// src/sleep/mod.rs
pub mod standby;
pub mod shutdown;

View File

@@ -0,0 +1,13 @@
// src/sleep/standby.rs
pub fn enter_shutdown() -> ! {
unsafe extern "C" {
fn HAL_PWREx_EnterSHUTDOWNMode();
}
unsafe {
HAL_PWREx_EnterSHUTDOWNMode();
}
cortex_m::asm::udf(); //panic
}

View File

@@ -0,0 +1,58 @@
// src/sleep/standby.rs
pub fn enter_standby() -> ! {
unsafe extern "C" {
fn HAL_PWR_EnterSTANDBYMode();
}
unsafe {
HAL_PWR_EnterSTANDBYMode();
}
cortex_m::asm::udf(); // never happen marker
}
pub fn enter_standby_with_sram2_8kb() -> ! {
sram2_8kb_retention();
enter_standby();
}
pub fn enter_standby_with_sram2_full() -> ! {
sram2_full_retention();
enter_standby();
}
pub fn sram2_full_retention() {
unsafe extern "C" {
fn HAL_PWREx_EnableSRAM2ContentStandbyRetention(sram2_pages: u32);
}
unsafe {
// 0x60 = PWR_SRAM2_FULL_STANDBY = PWR_CR1_RRSB1 | PWR_CR1_RRSB2
// See: STM32U5xx HAL: stm32u5xx_hal_pwr_ex.h line 227
// PWR_CR1_RRSB1=0x20, PWR_CR1_RRSB2=0x40
HAL_PWREx_EnableSRAM2ContentStandbyRetention(0x60);
}
}
pub fn sram2_8kb_retention() {
unsafe extern "C" {
fn HAL_PWREx_EnableSRAM2ContentStandbyRetention(sram2_pages: u32);
}
unsafe {
// 0x40 = PWR_SRAM2_PAGE1_STANDBY = PWR_CR1_RRSB2
// 8KB retention only
HAL_PWREx_EnableSRAM2ContentStandbyRetention(0x40);
}
}
pub fn disable_sram2_full_retention() {
unsafe extern "C" {
fn HAL_PWREx_DisableSRAM2ContentStandbyRetention(sram2_pages: u32);
}
unsafe {
HAL_PWREx_DisableSRAM2ContentStandbyRetention(0x60);
}
}

View File

@@ -0,0 +1,50 @@
// src/wakeup/iwdg.rs
use defmt::info;
use embassy_stm32::peripherals;
use embassy_stm32::wdg::IndependentWatchdog;
use embassy_stm32::Peri;
use embassy_time::{Duration, Timer};
use embassy_stm32::pac;
use crate::sleep::standby;
use crate::config::WATCHDOG_TIMEOUT_US;
/// Clears system reset and standby flags after wakeup.
///
/// Call early in startup to:
/// - Clear reset flags by setting `RMVF` in `RCC_CSR`.
/// - If `SBF` in `PWR_SR` is set (woke from Standby), clear it.
///
/// # Registers
/// - `RCC_CSR` — Reset and Clock Control / Status
/// - `PWR_SR` — Power Control / Status
pub fn clear_wakeup_flags() {
info!("Clearing wakeup flags...");
standby::disable_sram2_full_retention();
// Clear reset flags
// let rcc = unsafe { &*pac::RCC::ptr() };
let rcc = pac::RCC;
rcc.csr().write(|w| w.set_rmvf(true));
// Check and clear Standby wakeup flag
// let pwr = unsafe { &*pac::PWR::ptr() };
let pwr = pac::PWR;
if pwr.sr().read().sbf() {
info!("Woke from Standby mode");
pwr.sr().write(|w| w.set_sbf(true));
}
}
/// Initializes the Independent Watchdog (IWDG) timer.
/// Wakeup source: Can wake the system from Standby mode on timeout
///
/// # Timing
/// - Timeout value is configured in `WATCHDOG_TIMEOUT_US` from config.rs
pub async fn init_watchdog(iwdg: Peri<'_, peripherals::IWDG>) {
info!("Initializing watchdog after watchdog wake...");
let mut watchdog = IndependentWatchdog::new(iwdg, WATCHDOG_TIMEOUT_US);
watchdog.unleash();
Timer::after(Duration::from_millis(10)).await;
}

View File

@@ -0,0 +1,3 @@
// src/wakeup/mod.rs
pub mod iwdg;