From e1da7dc918c0bcda6fbbdd9ee6f89c5428ec5039 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Thu, 19 Feb 2026 14:14:28 -0800 Subject: [PATCH] Reduce panda power usage (#2340) * add bootkick for ship mode * boot standby * comment * stop mode * fix som status * exti wakeup * all standby * clean * analog mode * adc * clean * HSI * stop only cuatro * comments * UM2331 * rm * comment * enforce SAFETY_SILENT * clean * misra * rm * disable pulls * assert * Revert "disable pulls" This reverts commit 3b954b929a5f864279d52e28a01768ca22962810. * pull note * add stop mode USB cmd * add stop mode to HITL * fix * async stop mode request * test all harness/ign configs * more time * use uptime * print debug * unstuck * show prints * more * logger * loglevel * log * uptime test * tighter timing * print wakeout source * rm debug * clean * robust * add CAN2 * test more cans * clean * more * longer * multiple * more * can1 * reorder * normal * clean * partial * clean * test * time * delay * reset * setup * reset * revert * silent * fix ordering * no random * warnings * err * timings * heartbeat * time * faster * can1 * simpler * test 20x * put back random * clean * comment * jenkinsfile * final --- board/main.c | 10 ++++ board/main_comms.h | 8 +++ board/sys/power_saving.h | 102 ++++++++++++++++++++++++++++++++++++++- board/sys/sys.h | 3 -- python/__init__.py | 3 ++ tests/hitl/8_misc.py | 31 ++++++++++++ 6 files changed, 153 insertions(+), 4 deletions(-) diff --git a/board/main.c b/board/main.c index eb130ae3..e3e98074 100644 --- a/board/main.c +++ b/board/main.c @@ -341,6 +341,11 @@ int main(void) { // LED should keep on blinking all the time while (true) { + #ifdef ALLOW_DEBUG + if (stop_mode_requested) { + enter_stop_mode(); + } + #endif if (!power_save_enabled) { #ifdef DEBUG_FAULTS if (fault_status == FAULT_STATUS_NONE) { @@ -369,6 +374,11 @@ int main(void) { } #endif } else { + if ((hw_type == HW_TYPE_CUATRO) && !current_board->read_som_gpio()) { + assert_fatal(current_safety_mode == SAFETY_SILENT, "Error: Entering low power mode while not in SAFETY_SILENT. Hanging\n"); + enter_stop_mode(); // deep sleep, wakes on CAN or SBU activity + assert_fatal(false, "Error: enter_stop_mode returned after system reset. Hanging\n"); + } __WFI(); SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk; } diff --git a/board/main_comms.h b/board/main_comms.h index 07aceb9f..fdcfd68f 100644 --- a/board/main_comms.h +++ b/board/main_comms.h @@ -95,6 +95,14 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { resp[1] = ((fan_state.rpm & 0xFF00U) >> 8U); resp_len = 2; break; + // **** 0xb5: request deep sleep, wakes on CAN or SBU + #ifdef ALLOW_DEBUG + case 0xb5: + set_safety_mode(SAFETY_SILENT, 0U); + set_power_save_state(true); + stop_mode_requested = true; + break; + #endif // **** 0xc0: reset communications state case 0xc0: comms_can_reset(); diff --git a/board/sys/power_saving.h b/board/sys/power_saving.h index 2ab3e993..accfdb01 100644 --- a/board/sys/power_saving.h +++ b/board/sys/power_saving.h @@ -1,9 +1,14 @@ #include "board/sys/sys.h" -// WARNING: To stay in compliance with the SIL2 rules laid out in STM UM1840, we should never implement any of the available hardware low power modes. +// WARNING: To stay in compliance with the SIL2 rules laid out in STM UM2331, we should never use any of the available hardware low power modes during safety function execution. // See rule: CoU_3 +// Low power state "stop mode" is only entered from SAFETY_SILENT when no safety function is active and exited via reset which is a safe state. + bool power_save_enabled = false; +#ifdef ALLOW_DEBUG +volatile bool stop_mode_requested = false; +#endif void enable_can_transceivers(bool enabled) { // Leave main CAN always on for CAN-based ignition detection @@ -46,3 +51,98 @@ void set_power_save_state(bool enable) { power_save_enabled = enable; } } + +static void enter_stop_mode(void) { + // set all GPIO to analog mode to reduce power, analog mode also disables pull resistors + register_set(&(GPIOA->MODER), 0xFFFFFFFFU, 0xFFFFFFFFU); + register_set(&(GPIOB->MODER), 0xFFFFFFFFU, 0xFFFFFFFFU); + register_set(&(GPIOC->MODER), 0xFFFFFFFFU, 0xFFFFFFFFU); + register_set(&(GPIOD->MODER), 0xFFFFFFFFU, 0xFFFFFFFFU); + register_set(&(GPIOE->MODER), 0xFFFFFFFFU, 0xFFFFFFFFU); + register_set(&(GPIOF->MODER), 0xFFFFFFFFU, 0xFFFFFFFFU); + register_set(&(GPIOG->MODER), 0xFFFFFFFFU, 0xFFFFFFFFU); + + // init GPIO to lowest power state + current_board->set_bootkick(BOOT_STANDBY); + current_board->set_amp_enabled(false); + for (uint8_t i = 1U; i <= 4U; i++) { + current_board->enable_can_transceiver(i, false); + } + + // disable ADCs + ADC1->CR &= ~(ADC_CR_ADEN); + ADC1->CR |= ADC_CR_DEEPPWD; + ADC2->CR &= ~(ADC_CR_ADEN); + ADC2->CR |= ADC_CR_DEEPPWD; + + // disable HSI48: 48 MHz USB clock + register_clear_bits(&(RCC->CR), RCC_CR_HSI48ON); + // disable SRAM retention in stop mode + register_clear_bits(&(RCC->AHB2LPENR), RCC_AHB2LPENR_SRAM1LPEN | RCC_AHB2LPENR_SRAM2LPEN); + register_clear_bits(&(RCC->AHB4LPENR), RCC_AHB4LPENR_SRAM4LPEN); + register_clear_bits(&(RCC->AHB3LPENR), RCC_AHB3LPENR_AXISRAMLPEN); + + // SBU pins to input for EXTI wakeup + set_gpio_mode(current_board->harness_config->GPIO_SBU1, + current_board->harness_config->pin_SBU1, MODE_INPUT); + set_gpio_mode(current_board->harness_config->GPIO_SBU2, + current_board->harness_config->pin_SBU2, MODE_INPUT); + + // EXTI1: SBU2 (PA1) + // EXTI4: SBU1 (PC4) + register_set(&(SYSCFG->EXTICR[0]), SYSCFG_EXTICR1_EXTI1_PA, 0xF0U); + register_set(&(SYSCFG->EXTICR[1]), SYSCFG_EXTICR2_EXTI4_PC, 0xFU); + register_set_bits(&(EXTI->IMR1), (1U << 1) | (1U << 4)); + register_set_bits(&(EXTI->RTSR1), (1U << 1) | (1U << 4)); + register_set_bits(&(EXTI->FTSR1), (1U << 1) | (1U << 4)); + + // EXTI for CAN wakeup + // EXTI8: FDCAN1 RX (PB8) + // EXTI5: FDCAN2 RX (PB5) + // EXTI12: FDCAN3 RX (PD12) + set_gpio_mode(GPIOB, 8, MODE_INPUT); + register_set(&(SYSCFG->EXTICR[2]), SYSCFG_EXTICR3_EXTI8_PB, 0xFU); + set_gpio_mode(GPIOB, 5, MODE_INPUT); + register_set(&(SYSCFG->EXTICR[1]), SYSCFG_EXTICR2_EXTI5_PB, 0xF0U); + set_gpio_mode(GPIOD, 12, MODE_INPUT); + register_set(&(SYSCFG->EXTICR[3]), SYSCFG_EXTICR4_EXTI12_PD, 0xFU); + uint32_t can_exti_line = (1UL << 8) | (1UL << 5) | (1UL << 12); + register_set_bits(&(EXTI->IMR1), can_exti_line); + register_set_bits(&(EXTI->FTSR1), can_exti_line); + + // clear pending EXTI + EXTI->PR1 = (1U << 1) | (1U << 4) | can_exti_line; + + // reset if ignition just came on before going to sleep + if (harness_check_ignition()) { + NVIC_SystemReset(); + } + + // stop mode + register_clear_bits(&(PWR->CPUCR), PWR_CPUCR_PDDS_D1 | PWR_CPUCR_PDDS_D2 | PWR_CPUCR_PDDS_D3); + + // set SVOS5 voltage scaling, flash low-power + register_set(&(PWR->CR1), PWR_CR1_SVOS_0 | PWR_CR1_FLPS, PWR_CR1_SVOS | PWR_CR1_FLPS); + + // enter stop mode on WFI + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + + __disable_irq(); + + // disable all NVIC interrupts and clear pending + for (uint32_t i = 0U; i < 8U; i++) { + NVIC->ICER[i] = 0xFFFFFFFFU; + NVIC->ICPR[i] = 0xFFFFFFFFU; + } + // enable only wakeup EXTI interrupts + NVIC_EnableIRQ(EXTI1_IRQn); // SBU2 (PA1) + NVIC_EnableIRQ(EXTI4_IRQn); // SBU1 (PC4) + NVIC_EnableIRQ(EXTI9_5_IRQn); // FDCAN1 RX (PB8), FDCAN2 RX (PB5) + NVIC_EnableIRQ(EXTI15_10_IRQn); // FDCAN3 RX (PD12) + + __DSB(); + __ISB(); + __WFI(); + + NVIC_SystemReset(); +} diff --git a/board/sys/sys.h b/board/sys/sys.h index c329ad7f..fe5527a0 100644 --- a/board/sys/sys.h +++ b/board/sys/sys.h @@ -58,9 +58,6 @@ void fault_recovered(uint32_t fault); // ******************** power_saving ******************** -// WARNING: To stay in compliance with the SIL2 rules laid out in STM UM1840, we should never implement any of the available hardware low power modes. -// See rule: CoU_3 - extern bool power_save_enabled; void set_power_save_state(bool enable); diff --git a/python/__init__.py b/python/__init__.py index 89a5ebbe..52700350 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -641,6 +641,9 @@ class Panda: def set_power_save(self, power_save_enabled=0): self._handle.controlWrite(Panda.REQUEST_OUT, 0xe7, int(power_save_enabled), 0, b'') + def enter_stop_mode(self): + self._handle.controlWrite(Panda.REQUEST_OUT, 0xb5, 0, 0, b'', expect_disconnect=True) + def set_safety_mode(self, mode=CarParams.SafetyModel.silent, param=0): self._handle.controlWrite(Panda.REQUEST_OUT, 0xdc, mode, param, b'') diff --git a/tests/hitl/8_misc.py b/tests/hitl/8_misc.py index 3c47ed49..dad612bf 100644 --- a/tests/hitl/8_misc.py +++ b/tests/hitl/8_misc.py @@ -1,4 +1,5 @@ import time +import pytest from panda import Panda @@ -11,3 +12,33 @@ def test_boot_time(p): # USB enumeration is slow, so SPI is faster assert time.monotonic() - st < (1.0 if p.spi else 5.0) +@pytest.mark.panda_expect_can_error +@pytest.mark.test_panda_types((Panda.HW_TYPE_CUATRO, )) +def test_stop_mode(p, panda_jungle): + serial = p.get_usb_serial() + panda_jungle.set_obd(True) + + for orientation in (Panda.HARNESS_STATUS_FLIPPED, Panda.HARNESS_STATUS_NORMAL): + panda_jungle.set_harness_orientation(orientation) + time.sleep(0.25) # wait for orientation detection + + for wakeup in ("ign", "0", "1", "2"): + panda_jungle.set_ignition(False) + print(f"orientation={orientation} wakeup={wakeup}") + + p.enter_stop_mode() + p.close() + # wait for panda to enter stop mode + time.sleep(1.5) + + # wake via ignition or CAN activity + if wakeup == "ign": + panda_jungle.set_ignition(True) + else: + panda_jungle.can_send(0x123, b'\x01\x02', int(wakeup)) + + # panda should reset and come back + assert Panda.wait_for_panda(serial, timeout=10) + + p.reconnect() + assert p.health()['uptime'] < 3