Skip to content

RFC: New Testing Approach for Power Management Subsystem. #80989

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
gbarkadiusz opened this issue Nov 6, 2024 · 47 comments
Closed

RFC: New Testing Approach for Power Management Subsystem. #80989

gbarkadiusz opened this issue Nov 6, 2024 · 47 comments
Assignees
Labels
area: Power Management RFC Request For Comments: want input from the community

Comments

@gbarkadiusz
Copy link
Collaborator

Introduction

This proposal suggests a new method for testing the Power Management (PM) subsystem in Zephyr. The approach enhances the testing environment by adding an external power monitor to verify that the system reaches expected power states accurately. This improvement aims to make power management tests more reliable and to provide precise measurements for validating power state transitions.

Problem description

The Zephyr Power Management subsystem provides multiple sleep modes for efficient power usage. However, reliably confirming that a device enters each specific power state is challenging. For example, the stm32l562e_dk board supports multiple power states (k_cpu_idle, stop_0, stop_1, stop_2), but current test methods are limited in confirming that each state is reached as expected.

Proposed change

This proposal involves integrating a Power Monitor to track current consumption during testing. Using the stm32l562e_dk board’s onboard Power Monitor, the test setup can measure current in each power state. Pytest will then calculate the Root Mean Square (RMS) value of each detected power state, making it easier to verify whether each state has been achieved. This allows for accurate state detection and validation based on measured current consumption.

Detailed RFC

An external Power Monitor will be added to measure the current of the target device during tests. The stm32l562e_dk board, which has an onboard Power Monitor, will be used for testing. A script named pwsh_stm32l562.py will facilitate communication with the board, handling the data retrieval and current measurement.

Proposed change (Detailed)

The Power_Monitor abstract class serves as a base for implementing this functionality. It requires the following methods to be implemented:

init_power_monitor(device_id: str) -> bool:
Initializes the power monitor for a specific device ID.
measure_current(duration: int):
Measures current over a given duration.
get_data(duration: int) -> List[float]:
Retrieves an array of current measurements over the specified duration, returning values in amperes.

Setup and Configuration
image
Documentation:
STM32L562E-DK Evaluation Board: STMicroelectronics STM32L562E-DK
STM32L562E-DK User Manual: User Manual DM0063555
Power Monitor Firmware Documentation: Getting Started with PowerShield Firmware
Configuration: Based on the documentation provided, the stm32l562e_dk board should be configured as follows:
image

Three tests demonstrate this approach, each designed to validate different aspects of power management:

pm.states: Verifies the transition to all available power states on stm32l562e_dk.
pm.residency_time: Tests the residency time of each power state.
pm.wakeup_timer: Tests the RTC alarm’s ability to wake the CPU from the lowest power state.
Each test calculates RMS current values for each detected state and compares them to expected values, which were manually set based on experimental data.

Test: pm.states
This test is designed to verify all available power states on the stm32l562e_dk board. The results from the measure_current function can be found in the pm_states.csv file.
image
The chart above illustrates the measured current consumption during the execution of the pm_states test. Key elements of the chart are highlighted as follows:

Startup: The current peak observed at the beginning of the test.
Boot Delay: The delay associated with the additional configuration set to CONFIG_BOOT_DELAY=500.
k_cpu_idle: The low power state achieved using k_msleep(Tms), where Tms is shorter than the minimum residency time for the stop_0 power state defined in the device tree source (.dts) file.
Wake Up and Print Log: The target board wakes up between each power state and logs the corresponding messages.
stop_0, stop_1, stop_2, active_state: The measured current consumption for each respective power state.

During the test, pytest detects each step and calculates the RMS (Root Mean Square) current for every detected state. The test then compares the calculated RMS values against the expected values, which have been determined manually through experimental observation

To Reproduce:

  1. Configure the stm32l562e_dk board according to the instructions provided above.
  2. Connect both USB ports of the board to your host system. The board will appear as two separate targets:
    • Power Monitor: e.g., /dev/ttyACM0 or /dev/serial/by-id/usb-STMicroelectronics_PowerShield__Virtual_ComPort_in_FS_Mode__FFFFFFFEFFFF-if00
    • Target: e.g., /dev/ttyACM1 or /dev/serial/by-id/usb-STMicroelectronics_STLINK-V3_004##############39-if02
  3. Copy the path of the Power Monitor and update the <path_to_power_monitor> in the tests/subsys/pm/power_states/testcase.yaml file under the pytest_args section as follows:
pytest_args: [--powershield=<path_to_power_monitor>]
  1. Run the following command to execute the test:
twister --device-testing --device-serial <path_to_target> -p stm32l562e_dk -T tests/subsys/pm/power_states/ -x=CONFIG_BOOT_DELAY=500 -vv

Pytest log:

DEBUG   - PYTEST: k_cpu_idle:
DEBUG   - PYTEST: Expected RMS: 57.00 mA
DEBUG   - PYTEST: Tolerance: 5.70 mA
DEBUG   - PYTEST: Current RMS: 56.44 mA
DEBUG   - PYTEST: state 0:
DEBUG   - PYTEST: Expected RMS: 4.00 mA
DEBUG   - PYTEST: Tolerance: 0.40 mA
DEBUG   - PYTEST: Current RMS: 3.99 mA
DEBUG   - PYTEST: state 1:
DEBUG   - PYTEST: Expected RMS: 1.30 mA
DEBUG   - PYTEST: Tolerance: 0.13 mA
DEBUG   - PYTEST: Current RMS: 1.29 mA
DEBUG   - PYTEST: state 2:
DEBUG   - PYTEST: Expected RMS: 0.27 mA
DEBUG   - PYTEST: Tolerance: 0.03 mA
DEBUG   - PYTEST: Current RMS: 0.27 mA
DEBUG   - PYTEST: PASSED

Test: pm.residency_time
This test evaluates the residency time of different power states on the stm32l562e_dk board. The sequence of states is as follows:
image
The chart above illustrates the measured current consumption during the execution of the pm_residency_time test. Key elements of the chart are highlighted as follows:
Startup: The current peak observed at the beginning of the test.
Boot Delay: The delay associated with the additional configuration set to CONFIG_BOOT_DELAY=500.
k_cpu_idle: This represents the kernel idle state, where the sleep time is shorter than the residency time required for the stop_0 state.
stop_0: In this state, there is sufficient sleep time for the system to enter stop_0.
A_stop_1: Here, the sleep time is again shorter than the residency time required to transition to the stop_1 state.
B_stop_1: This state allows for enough sleep time to transition into stop_1.
A_stop_2: In this iteration, the sleep time is shorter than the residency time required to transition to the stop_2 state.
B_stop_2: The system has enough sleep time to enter the stop_2 state.
active_state: This state indicates that the application is running in an infinite loop, representing the active state of the system.
Wake Up and Print Log: The target board wakes up between each power state and logs the corresponding messages.

To Reproduce:
(1-2) As previously.
3. Copy the path of the Power Monitor and update the <path_to_power_monitor> in the tests/subsys/pm/power_residency_time/testcase.yaml file under the pytest_args section as follows:

pytest_args: [--powershield=<path_to_power_monitor>]
  1. Run the following command to execute the tests:
twister --device-testing --device-serial <path_to>/Target -p stm32l562e_dk -T tests/subsys/pm/power_residency_time/ -x=CONFIG_BOOT_DELAY=500 -vv

Pytest Log:

DEBUG   - PYTEST: k_cpu_idle:
DEBUG   - PYTEST: Expected RMS: 57.00 mA
DEBUG   - PYTEST: Tolerance: 5.70 mA
DEBUG   - PYTEST: Current RMS: 56.37 mA
DEBUG   - PYTEST: state 0:
DEBUG   - PYTEST: Expected RMS: 4.00 mA
DEBUG   - PYTEST: Tolerance: 0.40 mA
DEBUG   - PYTEST: Current RMS: 3.78 mA
DEBUG   - PYTEST: state 0:
DEBUG   - PYTEST: Expected RMS: 4.00 mA
DEBUG   - PYTEST: Tolerance: 0.40 mA
DEBUG   - PYTEST: Current RMS: 3.92 mA
DEBUG   - PYTEST: state 1:
DEBUG   - PYTEST: Expected RMS: 1.20 mA
DEBUG   - PYTEST: Tolerance: 0.12 mA
DEBUG   - PYTEST: Current RMS: 1.19 mA
DEBUG   - PYTEST: state 1:
DEBUG   - PYTEST: Expected RMS: 1.20 mA
DEBUG   - PYTEST: Tolerance: 0.12 mA
DEBUG   - PYTEST: Current RMS: 1.18 mA
DEBUG   - PYTEST: state 2:
DEBUG   - PYTEST: Expected RMS: 0.27 mA
DEBUG   - PYTEST: Tolerance: 0.03 mA
DEBUG   - PYTEST: Current RMS: 0.27 mA
DEBUG   - PYTEST: active:
DEBUG   - PYTEST: Expected RMS: 92.70 mA
DEBUG   - PYTEST: Tolerance: 9.27 mA
DEBUG   - PYTEST: Current RMS: 94.33 mA
DEBUG   - PYTEST: PASSED

Test: pm.wakeup_timer
In this test, the stm32l562e_dk target board is configured to set the RTC Alarm for 1 second before entering the lowest power state by calling the k_msleep() function. The sequence of operations is as follows:
-The RTC Alarm is set for 1 second.
-The board enters a low-power state, effectively minimizing power consumption.
-After the alarm triggers, the RTC Alarm callback wakes up the CPU.
-The CPU then enters an anti-sleeping loop to prevent returning to sleep mode.
-Throughout this process, the power monitor continuously measures the power consumption of the board.
This test helps verify the functionality of the wake-up timer and assesses the power consumption during low-power operation.
image
The chart above illustrates the measured current consumption during the execution of the pm_wakeup_timer test. The following key elements are highlighted in the chart:
Startup: The current peak observed at the beginning of the test.
Boot Delay: The delay associated with the additional configuration set to CONFIG_BOOT_DELAY=500.
Go Sleep: This phase represents when the alarm is set, and the board enters a low-power state by calling the k_msleep() function.
Stop 2: The measured current consumption during the stop_2 power state.
Alarm Interrupt: This indicates the moment the RTC alarm interrupt wakes the CPU, transitioning into an infinite loop.
Active State: This state signifies that the application is running in an infinite loop, representing the active operational state of the system.

To Reproduce:
(1-2) As previously.
3. Copy the path of the Power Monitor and update the <path_to_power_monitor> in the tests/subsys/pm/power_wakeup_timer/testcase.yaml file under the pytest_args section as follows:

pytest_args: [--powershield=<path_to_power_monitor>]
  1. Run the following command to execute the tests:
twister --device-testing --device-serial <path_to_target> -p stm32l562e_dk -T tests/subsys/pm/power_wakeup_timer/ -x=CONFIG_BOOT_DELAY=500 -vv

Pytest_log:

DEBUG   - PYTEST: Expected RMS: 0.26 mA
DEBUG   - PYTEST: Tolerance: 0.03 mA
DEBUG   - PYTEST: Current RMS: 0.27 mA
DEBUG   - PYTEST: active:
DEBUG   - PYTEST: Expected RMS: 95.00 mA
DEBUG   - PYTEST: Tolerance: 9.50 mA
DEBUG   - PYTEST: Current RMS: 91.90 mA
DEBUG   - PYTEST: PASSED

All the tests mentioned above are available in the following pull request: #80692. This pull request is currently in draft status and will remain so until the RFC is reviewed and applied.

Dependencies

This change requires access to the stm32l562e_dk board and an external (onboarded) Power Monitor. It may also necessitate modification of test scripts if additional power monitors are used.

@gbarkadiusz gbarkadiusz added area: Power Management RFC Request For Comments: want input from the community labels Nov 6, 2024
@nashif
Copy link
Member

nashif commented Nov 13, 2024

@erwango @bjarki-andreasen @ceolin please take a look

@nashif nashif removed their assignment Nov 13, 2024
@bjarki-andreasen
Copy link
Collaborator

I agree! We need to add power usage testing!

Some notes from my side:

  • We should measure power usage in watts (joules) :) amps only tell half the story
  • We should support power monitors as flexibly as we support debuggers in west. I would likely opt for a https://www.joulescope.com/ for testing
  • We should be testing more than power states, power usage of UARTs, I2C, whole applications really, is really important as well :) We should be able to just "enable power measuring" for any test or application at any time :)

I do have ideas for solutions for this as well but that's for later :)

@bjarki-andreasen
Copy link
Collaborator

bjarki-andreasen commented Nov 13, 2024

Can't help it, I would tie expected power measurement in unit tests to output from probe like:

device output:

*** Booting Zephyr OS build v4.0.0-rc2 ***
Running TESTSUITE power_test
===================================================================
START - test_power
 PROBE POWER - 100us 1uJ 3uJ
 PROBE POWER - 100us 6uJ 10uJ
 PASS - test_power in 0.200 seconds
===================================================================

test runner listens for PROBE POWER - <duration> <min> <max> and starts measuring power with whatever power measuring probe. The test suite code could look like:

ZTEST(power_test, test_power)
{
        /* enter power mode */
        ...

        /* request power measurement for duration of 100us expecting between 1 and 3 uJ */
        ztest_probe_power(100, 1, 3);

        /* enter next power mode */
        ...

        /* request power measurement for duration of 100us expecting between 6 and 10 uJ */
        ztest_probe_power(100, 6, 10);
}

over UART, ztest_probe_power(100, 1, 3) would result in printing PROBE POWER - 100 1 3 which prompts the test runner to perform the measurement using the probe, and fail the test suite if actual power was not within limits.

This is scalable to probing for other things like temperatures, voltage RMS, etc etc.

*** Booting Zephyr OS build v4.0.0-rc2 ***
Running TESTSUITE power_test
===================================================================
START - test_power
 PROBE CURRENT AVG - 100us 12uA 14uA
 PROBE VOLTAGE RMS - 100us 1.2uV 1.3uV
 PASS - test_power in 0.200 seconds
===================================================================

@gbarkadiusz
Copy link
Collaborator Author

gbarkadiusz commented Nov 15, 2024

I agree! We need to add power usage testing!

Some notes from my side:

  • We should measure power usage in watts (joules) :) amps only tell half the story
  • We should support power monitors as flexibly as we support debuggers in west. I would likely opt for a https://www.joulescope.com/ for testing
  • We should be testing more than power states, power usage of UARTs, I2C, whole applications really, is really important as well :) We should be able to just "enable power measuring" for any test or application at any time :)

I do have ideas for solutions for this as well but that's for later :)

Regarding these pointed things.
Watts are a good choice for measurement, but they require measuring the target board's supply voltage, which makes the setup a bit more complex. Additionally, I’m not sure if West is the best tool for handling this functionality. It could lead to potential issues with synchronization between West and Twister. Moreover, analyzing the results through Twister (as the tool responsible for testing) might be problematic—but I’m not certain, just raising the question.

Another question to consider is whether we should focus on measuring the relative changes in power consumption between different power modes/states, or if we should aim to measure the specific values for each application state. For example, should we also measure the power usage of every peripheral available on a specific target?

@bjarki-andreasen
Copy link
Collaborator

Regarding these pointed things. Watts are a good choice for measurement, but they require measuring the target board's supply voltage, which makes the setup a bit more complex.

Yeah, with some probes it may not be possible to measure joules, but in these cases, the test suites should still indicate a number of expected jules, and the user should provide a "fixed" voltage for twister to use to calculate joules from the current measurement :)

Additionally, I’m not sure if West is the best tool for handling this functionality. It could lead to potential issues with synchronization between West and Twister. Moreover, analyzing the results through Twister (as the tool responsible for testing) might be problematic—but I’m not certain, just raising the question.

Looking into it, twister is the tool used to manage tests, so it would be the place to add probes, alongside harnesses IMO :)

Another question to consider is whether we should focus on measuring the relative changes in power consumption between different power modes/states, or if we should aim to measure the specific values for each application state. For example, should we also measure the power usage of every peripheral available on a specific target?

I would strongly encourage specific measurements :) Test suites are about as controlled an environment we can get, changes somewhere which unexpectedly enables/disables some peripheral should be caught as an anomaly.

@gbarkadiusz
Copy link
Collaborator Author

gbarkadiusz commented Nov 20, 2024

@bjarki-andreasen

I noticed you updated the comment—thank you for that!

I have another question regarding the testing setup. I saw that you're using the ztest framework. Could you please explain how you'd like to integrate the measuring functionality with the ztest framework? I'm having some trouble understanding the connection and would appreciate your guidance.

Thank you!

PS: I can imagine using Serial/Uart to correspond zephyr app with python script to trigger the measurement.
e.g.:

void ztest_probe_power(int duration_us, int min_uJ, int max_uJ)
{
    const struct device *uart_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_shell_uart));
    char cmd[64];
    snprintf(cmd, sizeof(cmd), "measure_power %d %d %d\n", duration_us, min_uJ, max_uJ);

    // Send the command over UART
    for (size_t i = 0; i < strlen(cmd); ++i) {
        uart_poll_out(uart_dev, cmd[i]);
    }

    // Wait for acknowledgment and result
    char response[128];
    size_t len = 0;
    while (len < sizeof(response) - 1) {
        if (uart_poll_in(uart_dev, &response[len]) == 0) {
            len++;
            if (response[len - 1] == '\n') {
                break; // End of response
            }
        }
    }
    response[len] = '\0';
}

@bjarki-andreasen
Copy link
Collaborator

bjarki-andreasen commented Nov 20, 2024

@bjarki-andreasen

I noticed you updated the comment—thank you for that!

I have another question regarding the testing setup. I saw that you're using the ztest framework. Could you please explain how you'd like to integrate the measuring functionality with the ztest framework? I'm having some trouble understanding the connection and would appreciate your guidance.

Thank you!

PS: I can imagine using Serial/Uart to correspond zephyr app with python script to trigger the measurement. e.g.:

void ztest_probe_power(int duration_us, int min_uJ, int max_uJ)
{
    const struct device *uart_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_shell_uart));
    char cmd[64];
    snprintf(cmd, sizeof(cmd), "measure_power %d %d %d\n", duration_us, min_uJ, max_uJ);

    // Send the command over UART
    for (size_t i = 0; i < strlen(cmd); ++i) {
        uart_poll_out(uart_dev, cmd[i]);
    }

    // Wait for acknowledgment and result
    char response[128];
    size_t len = 0;
    while (len < sizeof(response) - 1) {
        if (uart_poll_in(uart_dev, &response[len]) == 0) {
            len++;
            if (response[len - 1] == '\n') {
                break; // End of response
            }
        }
    }
    response[len] = '\0';
}

To send back an answer, we would likely need to enable shell support, and add a shell backend for testing, something that would look like:

===================================================================
START - test_power
 PROBE CURRENT AVG - 100us 12uA 14uA
uart:~$ ztest PASS
 PASS - test_power
===================================================================

in the shell, where we would listen for PROBE CURRENT AVG - 100us 12uA 14uA, start the measurement, and send back the status PASS. ztest would be the subcommand, taking one parameter, PASSED or FAILED.

The code could look like:

static K_SEM_DEFINE(shell_result, 0, 1);
static bool passed;

void ztest_probe_power(int duration_us, int min_uJ, int max_uJ)
{
        /* Request measurement */
        TC_PRINT("measure_power %d %d %d\n", duration_us, min_uJ, max_uJ);

        /* Wait for shell input */
        k_sem_take(&shell_result, K_FOREVER);

        /* Check result */
        if (!passed) {
                ztest_test_fail();
        }
}

excluding the implementation of the ztest shell backend.

However, test suites currently run with only console enabled, since its simple, and all we need if the entire test suite is run on target. Enabling the shell takes quite a bit of ROM, and may make some targets incompatible with the test suite. For that reason, I would compromise to something like:

void ztest_probe_power(int duration_us, int min_uJ, int max_uJ)
{
        /* Request measurement */
        TC_PRINT("measure_power %d %d %d\n", duration_us, min_uJ, max_uJ);
        
        /* wait "some time" for twister to start test */
        k_sleep(K_MSEC(10));

        /* wait for duration */
        k_sleep(K_USEC(duration_us));
}

Its likely less stable then the shell approach, but its an option :)

The last option would be to have something completely custom similar to your suggestion with directly interacting with the UART, also a fine solution, but we may be limiting ourselves a bit compared to adding shell support

@gbarkadiusz
Copy link
Collaborator Author

gbarkadiusz commented Nov 21, 2024

I see your point. A Zephyr application can request measurements at any time using methods like TC_PRINT or shell commands. The challenge lies in capturing these requests and performing the measurements. We need a mechanism to handle such commands. As you mentioned, we could use the shell backend (e.g., UART) to send commands, but we still need something on the other end to process these requests. Additionally, I’m considering whether we should use the same serial port for communication with the Zephyr application and for verifying tests via Twister. It could make the Twister log messy.

Thinking out loud:
A straightforward approach could be to use the pytest harness. For example:

import pytest
from twister_harness import DeviceAdapter

def test_measure(dut: DeviceAdapter):
    # Read lines from the device's output
    output_lines = dut.readlines()

    # Handle the command from the output
    for line in output_lines:
        if "PROBE POWER" in line:
            # Parse the command, execute measurement logic, and decide pass/fail
            handle_probe_power_command(line, dut)

The handle_probe_power_command function could process the command, execute the measurement, and communicate the result back to the device or log it appropriately. This keeps the process modular and aligns well with pytest's structure.

@bjarki-andreasen
Copy link
Collaborator

I see your point. A Zephyr application can request measurements at any time using methods like TC_PRINT or shell commands. The challenge lies in capturing these requests and performing the measurements. We need a mechanism to handle such commands. As you mentioned, we could use the shell backend (e.g., UART) to send commands, but we still need something on the other end to process these requests. Additionally, I’m considering whether we should use the same serial port for communication with the Zephyr application and for verifying tests via Twister. It could make the Twister log messy.

Thinking out loud: A straightforward approach could be to use the pytest harness. For example:

import pytest
from twister_harness import DeviceAdapter

def test_measure(dut: DeviceAdapter):
    # Read lines from the device's output
    output_lines = dut.readlines()

    # Handle the command from the output
    for line in output_lines:
        if "PROBE POWER" in line:
            # Parse the command, execute measurement logic, and decide pass/fail
            handle_probe_power_command(line, dut)

The handle_probe_power_command function could process the command, execute the measurement, and communicate the result back to the device or log it appropriately. This keeps the process modular and aligns well with pytest's structure.

This looks pretty nice :)

@gbarkadiusz
Copy link
Collaborator Author

gbarkadiusz commented Nov 22, 2024

@bjarki-andreasen I thought about the unit of measured value. We have several options.
Pure Average of current. (As you said, it is a half of story).
image
But the power can be calculated as:
image

RMS (Root Mean Square) is a better metric for fluctuating signals as it considers the variation in current over time.
RMS current can be calculated as:
image
and the power can be calculated a:
image

Total Energy Instead of Power.
If the measurement duration image is known, we can calculate the total energy (𝐸) consumed over the period. This gives a clearer picture of energy usage:
image
where:
image

The question is. How to calculate the expected value? Maybe it is not a good question at this time but let me treat this RFC as a brainstorm and place to share all ideas and warries. :)

@bjarki-andreasen
Copy link
Collaborator

bjarki-andreasen commented Nov 22, 2024

I don't think we should limit ourselves at all :) We could add support for power over time, min/max voltages, average current, rms current etc.

ztest_probe_power(); /* in joules */
ztest_probe_current_rms() /* in amps */
ztest_probe_voltage_min_max() /* in volts */

Any test can specify whatever it needs, and will get its way if the probe supports the requested measurement.

@gbarkadiusz
Copy link
Collaborator Author

I don't think we should limit ourselves at all :) We could add support for power over time, min/max voltages, average current, rms current etc.

ztest_probe_power(); /* in joules */
ztest_probe_current_rms() /* in amps */
ztest_probe_voltage_min_max() /* in volts */

Any test can specify whatever it needs, and will get its way if the probe supports the requested measurement.

Sounds great ;)

@bjarki-andreasen
Copy link
Collaborator

One little update:

ztest_probe_power_avg(); /* in watts */
ztest_probe_energy(); /* in joules */

power is in watts :)

@hakehuang
Copy link
Collaborator

hakehuang commented Nov 26, 2024

measure with on-board adc is an options, and I from NXP would like to propose to use our mcu-link-pro as a standalone measure device to do this. see https://www.nxp.com/docs/en/user-manual/UM11673.pdf

@gbarkadiusz
Copy link
Collaborator Author

gbarkadiusz commented Nov 26, 2024

@bjarki-andreasen FYI.
I performed some exercises on triggering measurements from the Zephyr application.


void ztest_probe_power(int duration_us, int min_uJ, int max_uJ)
{
    /* Request measurement */
    printk("measure_power %d %d %d\n", duration_us, min_uJ, max_uJ);
        
    /* wait for duration */
    k_sleep(K_MSEC(duration_us));
}

ZTEST(testsuite, test_name_of_testcase)
{
    ztest_probe_power(2000000, 1, 3);
}

ZTEST_SUITE(testsuite, NULL, NULL, NULL, NULL, NULL);

pytest:


def test_foo(dut: DeviceAdapter, request):
    powershield_device = request.config.getoption("--powermonitor")
    PM_Device = PowerMonitor()
    PM_Device.setup(power_device_path = powershield_device)
    while True:
        lines = dut.readlines() 
        # Find the line containing 'measure_power'
        measure_power_line = next((line for line in lines if 'measure_power' in line), None)
        if measure_power_line:
            parts = measure_power_line.split()
            # Extract the values
            command = parts[0]  # "measure_power"
            duration = int(parts[1]) /1000000 # us
            print(duration)
            value2 = int(parts[2])  # 
            value3 = int(parts[3])  # 

            PM_Device.measure(measure_unit = PM_Device.power_monitor_conf.MeasureUnit.Power, time=duration, reset=False)
            print(f"Power: {PM_Device.get_measured_data(unit = PM_Device.power_monitor_conf.MeasureUnit.POWER)} [W]")
            break
        else:
            print("measure_power not found.")

Unfortunately, I found a limitation in the onboard power monitor's firmware. Each 'start' command resets the target and starts the measurement, so it can't be triggered during the application. I need to find another solution.

@bjarki-andreasen
Copy link
Collaborator

bjarki-andreasen commented Nov 26, 2024

measure with on-board adc is an options, and I from NXP would like to propose to use our mcu-link-pro as a standalone measure device to do this. see https://www.nxp.com/docs/en/user-manual/UM11673.pdf

Absolutely we need to support it :) flashing, voltage and current measurement in one device, really neat :)

Also, we could write a zephyr sample application which uses an on-board ADC for power measurement, our own "in-house" power probe firmware :)

@bjarki-andreasen
Copy link
Collaborator

@bjarki-andreasen FYI. I performed some exercises on triggering measurements from the Zephyr application.


void ztest_probe_power(int duration_us, int min_uJ, int max_uJ)
{
    /* Request measurement */
    printk("measure_power %d %d %d\n", duration_us, min_uJ, max_uJ);
        
    /* wait for duration */
    k_sleep(K_MSEC(duration_us));
}

ZTEST(testsuite, test_name_of_testcase)
{
    ztest_probe_power(2000000, 1, 3);
}

ZTEST_SUITE(testsuite, NULL, NULL, NULL, NULL, NULL);

pytest:


def test_foo(dut: DeviceAdapter, request):
    powershield_device = request.config.getoption("--powermonitor")
    PM_Device = PowerMonitor()
    PM_Device.setup(power_device_path = powershield_device)
    while True:
        lines = dut.readlines() 
        # Find the line containing 'measure_power'
        measure_power_line = next((line for line in lines if 'measure_power' in line), None)
        if measure_power_line:
            parts = measure_power_line.split()
            # Extract the values
            command = parts[0]  # "measure_power"
            duration = int(parts[1]) /1000000 # us
            print(duration)
            value2 = int(parts[2])  # 
            value3 = int(parts[3])  # 

            PM_Device.measure(measure_unit = PM_Device.power_monitor_conf.MeasureUnit.Power, time=duration, reset=False)
            print(f"Power: {PM_Device.get_measured_data(unit = PM_Device.power_monitor_conf.MeasureUnit.POWER)} [W]")
            break
        else:
            print("measure_power not found.")

Unfortunately, I found a limitation in the onboard power monitor's firmware. Each 'start' command resets the target and starts the measurement, so it can't be triggered during the application. I need to find another solution.

In that case, could the current measurement not be started and running constantly in the "background"? We don't need to actually start it only when we care about the samples right?

@hakehuang
Copy link
Collaborator

Absolutely we need to support it :) flashing, voltage and current measurement in one device, really neat :)

I will add same support when the initial pr merged #80692

@hakehuang
Copy link
Collaborator

Also, we could write a zephyr sample application which uses an on-board ADC for power measurement, our own "in-house" power probe firmware :)

it is difficult, for power measurement, we need some additional circuits, now only lpcxpresso55s36 has such circuits on board

@gbarkadiusz
Copy link
Collaborator Author

@bjarki-andreasen FYI. I performed some exercises on triggering measurements from the Zephyr application.


void ztest_probe_power(int duration_us, int min_uJ, int max_uJ)
{
    /* Request measurement */
    printk("measure_power %d %d %d\n", duration_us, min_uJ, max_uJ);
        
    /* wait for duration */
    k_sleep(K_MSEC(duration_us));
}

ZTEST(testsuite, test_name_of_testcase)
{
    ztest_probe_power(2000000, 1, 3);
}

ZTEST_SUITE(testsuite, NULL, NULL, NULL, NULL, NULL);

pytest:


def test_foo(dut: DeviceAdapter, request):
    powershield_device = request.config.getoption("--powermonitor")
    PM_Device = PowerMonitor()
    PM_Device.setup(power_device_path = powershield_device)
    while True:
        lines = dut.readlines() 
        # Find the line containing 'measure_power'
        measure_power_line = next((line for line in lines if 'measure_power' in line), None)
        if measure_power_line:
            parts = measure_power_line.split()
            # Extract the values
            command = parts[0]  # "measure_power"
            duration = int(parts[1]) /1000000 # us
            print(duration)
            value2 = int(parts[2])  # 
            value3 = int(parts[3])  # 

            PM_Device.measure(measure_unit = PM_Device.power_monitor_conf.MeasureUnit.Power, time=duration, reset=False)
            print(f"Power: {PM_Device.get_measured_data(unit = PM_Device.power_monitor_conf.MeasureUnit.POWER)} [W]")
            break
        else:
            print("measure_power not found.")

Unfortunately, I found a limitation in the onboard power monitor's firmware. Each 'start' command resets the target and starts the measurement, so it can't be triggered during the application. I need to find another solution.

In that case, could the current measurement not be started and running constantly in the "background"? We don't need to actually start it only when we care about the samples right?

That was my first thought. WIP. :)

@gbarkadiusz
Copy link
Collaborator Author

I performed tests using the Zephyr app and triggered commands by passing them to pytest. The integration worked quite well, but I encountered an issue— I didn’t know how to pass the result of the pytest test back to the Zephyr app. As a workaround, I used a method where Zephyr triggers a command, such as measure_current_rms, with two parameters: [min_uA, max_uA]. The measured value of the current RMS should then be between these two values. To verify this condition, I used pytest's assertion, like this:

assert min_uA < current_rms < max_uA

From my perspective, this approach seems valid, but I would appreciate your opinion on whether it's correct.
Since we cannot use ztest directly in this case, the application is a "pure" Zephyr application, structured as follows:

#include <string.h>
#include <zephyr/kernel.h>

void ztest_probe_current_rms(int duration_us, int min_uA, int max_uA)
{
    /* Request measurement */
    printk("measure_current_rms %d %d %d\n", duration_us, min_uA, max_uA); 
}

int main(void)
{
    ztest_probe_current_rms(1600000, 25, 30); /*  duration = 1.6s, min_uA = 25, max_uA = 30*/

    // Sleep for 3000ms
    k_msleep(3000);
    
    /*  Anti-sleeping loop */
    while(1)
    {
        arch_nop();
    }

    return 0;
}

@bjarki-andreasen
Copy link
Collaborator

I performed tests using the Zephyr app and triggered commands by passing them to pytest. The integration worked quite well, but I encountered an issue— I didn’t know how to pass the result of the pytest test back to the Zephyr app. As a workaround, I used a method where Zephyr triggers a command, such as measure_current_rms, with two parameters: [min_uA, max_uA]. The measured value of the current RMS should then be between these two values. To verify this condition, I used pytest's assertion, like this:

assert min_uA < current_rms < max_uA

From my perspective, this approach seems valid, but I would appreciate your opinion on whether it's correct. Since we cannot use ztest directly in this case, the application is a "pure" Zephyr application, structured as follows:

#include <string.h>
#include <zephyr/kernel.h>

void ztest_probe_current_rms(int duration_us, int min_uA, int max_uA)
{
    /* Request measurement */
    printk("measure_current_rms %d %d %d\n", duration_us, min_uA, max_uA); 
}

int main(void)
{
    ztest_probe_current_rms(1600000, 25, 30); /*  duration = 1.6s, min_uA = 25, max_uA = 30*/

    // Sleep for 3000ms
    k_msleep(3000);
    
    /*  Anti-sleeping loop */
    while(1)
    {
        arch_nop();
    }

    return 0;
}

I think the solution of simply terminating the test from pytest, not caring what the DUT does after the current measurement has failed, is a valid solution. What we loose doing it this way though is that we have to terminate the entire test suite if any single test fails.

Fixing issues is often easier if one can see the set of tests that failed:

Running TESTSUITE power_test
===================================================================
START - test_uart_fast
 PROBE POWER - 100us 1uJ 3uJ
 FAILED - test_uart_fast in 0.200 seconds
START - test_adc_slow
 PROBE POWER - 100us 1uJ 3uJ
 PASS - test_adc_slow in 0.200 seconds
START - test_adc_fast
 PROBE POWER - 100us 1uJ 3uJ
 PASS - test_adc_fast in 0.200 seconds
START - test_uart_slow
 PROBE POWER - 100us 1uJ 3uJ
 FAILED - test_uart_slow in 0.200 seconds
===================================================================

with this output, its clearly a UART issue, not a larger issue affecting ADC. Compare this to

Running TESTSUITE power_test
===================================================================
START - test_uart_fast
 PROBE POWER - 100us 1uJ 3uJ

followed by pytest failing, one has to chase the actual issue, is it with UART only, or more peripherals? not clear :)

@bjarki-andreasen
Copy link
Collaborator

Forgot to add a solution here :) I found this https://github.com/zephyrproject-rtos/zephyr/blob/main/subsys/testsuite/ztest/src/ztest_shell.c so there is already a shell for test execution, surely we can extend it with test feedback :)

@gbarkadiusz
Copy link
Collaborator Author

gbarkadiusz commented Nov 29, 2024

Forgot to add a solution here :) I found this https://github.com/zephyrproject-rtos/zephyr/blob/main/subsys/testsuite/ztest/src/ztest_shell.c so there is already a shell for test execution, surely we can extend it with test feedback :)

Ztest Shell supports executing a specific test via the shell. However, there are still valid questions about how to trigger measurements within the test, collect the results, and determine whether the test passes or fails.

The main challenge lies in how to communicate between Ztest and the measurement environment.

Using Pytest
We can pass the trigger from the Zephyr application to Pytest through the console (e.g., by printing measure_power), and then read the output in Pytest using dut.readlines(). However, the problem arises in how to send the result back to Ztest in order to determine whether the test passes or fails.

Using Shell
There is support to execute a specific test via the shell. Pytest can also be used to run the test, collect the triggered command, measure the probe, and perform the necessary actions. The challenge here is determining how to send the result back to Ztest while the test is running.

Using Console Subsystem
The console subsystem supports bidirectional communication via serial. In this approach, we need to figure out how to measure the required value in background using twister/west command ?

@bjarki-andreasen
Copy link
Collaborator

Forgot to add a solution here :) I found this https://github.com/zephyrproject-rtos/zephyr/blob/main/subsys/testsuite/ztest/src/ztest_shell.c so there is already a shell for test execution, surely we can extend it with test feedback :)

Ztest Shell supports executing a specific test via the shell. However, there are still valid questions about how to trigger measurements within the test, collect the results, and determine whether the test passes or fails.

The main challenge lies in how to communicate between Ztest and the measurement environment.

Using Pytest We can pass the trigger from the Zephyr application to Pytest through the console (e.g., by printing measure_power), and then read the output in Pytest using dut.readlines(). However, the problem arises in how to send the result back to Ztest in order to determine whether the test passes or fails.

Using Shell There is support to execute a specific test via the shell. Pytest can also be used to run the test, collect the triggered command, measure the probe, and perform the necessary actions. The challenge here is determining how to send the result back to Ztest while the test is running.

Using Console Subsystem The console subsystem supports bidirectional communication via serial. In this approach, we need to figure out how to measure the required value in background using twister/west command ?

I looked into using the shell for control, and came up with:

static int cmd_control_testcase(const struct shell *sh, size_t argc, char **argv)
{
	const char *action;

	action = argv[1];

	if (!strcmp(action, "pass")) {
		ztest_test_pass();
	} else if (!strcmp(action, "skip")) {
		ztest_test_skip();
	} else if (!strcmp(action, "fail")) {
		ztest_test_fail();
	} else {
		shell_error("unsupported action %s", action);
		return -EINVAL;
	}

	return 0;
}

SHELL_STATIC_SUBCMD_SET_CREATE(
	sub_ztest_harness_cmds,
	SHELL_CMD_ARG(control_testcase, NULL, "", cmd_control_testcase, 1, 0),
	SHELL_SUBCMD_SET_END /* Array terminated. */
);

SHELL_CMD_REGISTER(ztest_harness, &sub_ztest_harness_cmds, "", NULL);

creating a simple low overhead shell which is only used for feedback (no running test suites etc. like the full ztest shell). With this, the DUT prints "PROBE POWER ...", pytest does the measurements, and writes back ztest_harness control_testcase pass or ztest_harness control_testcase fail

Definately all for doing something way simpler using the console directly, primarily to save on ROM/RAM, but we would need to check what the actual difference in ROM/RAM is comparing the shell to the console solution to choose (the shell one seems optimal to me if ROM/RAM is OK)

@gbarkadiusz
Copy link
Collaborator Author

gbarkadiusz commented Nov 29, 2024

Have you tested this approach?
Quick fix:

- shell_error("unsupported action %s", action);
+ shell_error(sh, "unsupported action %s", action);

- SHELL_CMD_ARG(control_testcase, NULL, "", cmd_control_testcase, 1, 0),
+ SHELL_CMD_ARG(control_testcase, NULL, "", cmd_control_testcase, 1, 1),

but:

uart:~$ ztest_harness control_testcase pass
 ERROR: cannot pass in test phase 'framework()', bailing
uart:~$ ztest_harness control_testcase skip
 ERROR: cannot skip in test phase 'framework()', bailing
uart:~$ ztest_harness control_testcase fail
 ERROR: cannot fail in test phase 'framework()', bailing

@bjarki-andreasen
Copy link
Collaborator

bjarki-andreasen commented Nov 29, 2024

Have you tested this approach? Quick fix:

- shell_error("unsupported action %s", action);
+ shell_error(sh, "unsupported action %s", action);

- SHELL_CMD_ARG(control_testcase, NULL, "", cmd_control_testcase, 1, 0),
+ SHELL_CMD_ARG(control_testcase, NULL, "", cmd_control_testcase, 1, 1),

but:

uart:~$ ztest_harness control_testcase pass
 ERROR: cannot pass in test phase 'framework()', bailing
uart:~$ ztest_harness control_testcase skip
 ERROR: cannot skip in test phase 'framework()', bailing
uart:~$ ztest_harness control_testcase fail
 ERROR: cannot fail in test phase 'framework()', bailing

No, I just wanted to show how to set up the shell, are you in a test case? to test it, do:

ZTEST(<suite>, <testcase>)
{
        TC_PRINT("you can control test now\n");
        k_msleep(10000);
        TC_PRINT("too late\n");
}

The functions only work while a test case is actively running

@gbarkadiusz
Copy link
Collaborator Author

gbarkadiusz commented Nov 29, 2024

The problem lies in enabled shell. If it is enabled, the tests won't be executed. After boot zephyr, the app goes to shell immediately.
So, I tried to run tests from shell as well. But it suspends shell till the test ends.

DEBUG   - PYTEST: -------------------------------- live log call ---------------------------------
DEBUG   - PYTEST: DEBUG: #: uart:~$ ztest run-all
DEBUG   - PYTEST: DEBUG: #: Running TESTSUITE testsuite
DEBUG   - PYTEST: DEBUG: #: ===================================================================
DEBUG   - PYTEST: DEBUG: #: START - test_name_of_testcase
DEBUG   - PYTEST: DEBUG: #: measure_power 10000 1 3
DEBUG   - PYTEST: DEBUG: #: PASS - test_name_of_testcase in 10.003 seconds
DEBUG   - PYTEST: DEBUG: #: ===================================================================
DEBUG   - PYTEST: DEBUG: #: TESTSUITE testsuite succeeded
DEBUG   - PYTEST: DEBUG: #: ------ TESTSUITE SUMMARY START ------
DEBUG   - PYTEST: DEBUG: #: SUITE PASS - 100.00% [testsuite]: pass = 1, fail = 0, skip = 0, total = 1 duration = 10.003 seconds
DEBUG   - PYTEST: DEBUG: #: - PASS - [testsuite.test_name_of_testcase] duration = 10.003 seconds
DEBUG   - PYTEST: DEBUG: #: ------ TESTSUITE SUMMARY END ------
DEBUG   - PYTEST: DEBUG: #: ===================================================================
DEBUG   - PYTEST: DEBUG: #: RunID: c3374ae443c5b302c52eff174cc220dd
DEBUG   - PYTEST: DEBUG: #: PROJECT EXECUTION SUCCESSFUL
DEBUG   - PYTEST: DEBUG: #: uart:~$ 
DEBUG   - PYTEST: DEBUG: #: uart:~$ ztest_harness control_testcase pass
DEBUG   - PYTEST: DEBUG: #: ERROR: cannot pass in test phase 'teardown()', bailing

The valid question is, Is it so important to trigger the measurement by zephyr app?

@bjarki-andreasen
Copy link
Collaborator

bjarki-andreasen commented Nov 29, 2024

Are you selecting CONFIG_ZTEST_SHELL=y? it should be CONFIG_ZTEST_SHELL=n, only CONFIG_SHELL=y should be enabled :) I noticed that CONFIG_ZTEST_SHELL is wildly overcomplicated for what we need, and completely changes the behavior of ZTEST, so the small control shell is separate from it

@gbarkadiusz
Copy link
Collaborator Author

Please, take a look.

main.c

#include <zephyr/ztest.h>
#include <string.h>
#include <zephyr/shell/shell.h>

static int cmd_control_testcase(const struct shell *sh, size_t argc, char **argv)
{
	const char *action;
	action = argv[1];

	if (!strcmp(action, "pass")) {
		ztest_test_pass();
	} else if (!strcmp(action, "skip")) {
		ztest_test_skip();
	} else if (!strcmp(action, "fail")) {
		ztest_test_fail();
	} else {
		shell_error(sh, "unsupported action %s", action);
		return -EINVAL;
	}

	return 0;
}

SHELL_STATIC_SUBCMD_SET_CREATE(sub_ztest_harness_cmds,
			       SHELL_CMD_ARG(control_testcase, NULL, "", cmd_control_testcase, 1, 1),
			       SHELL_SUBCMD_SET_END /* Array terminated. */);

SHELL_CMD_REGISTER(ztest_harness, &sub_ztest_harness_cmds, "", NULL);

ZTEST(testsuite, test_name_of_testcase)
{
	TC_PRINT("you can control test now\n");
	k_msleep(10000);
	TC_PRINT("too late\n");
}

ZTEST_SUITE(testsuite, NULL, NULL, NULL, NULL, NULL);

prj.conf

CONFIG_ZTEST=y
CONFIG_ZTEST_SHELL=n
CONFIG_SHELL=y

log:

DEBUG   - PYTEST: DEBUG: Flashing finished
DEBUG   - PYTEST: DEBUG: Found matching key: CONFIG_SHELL_PROMPT_UART="uart:~$ "
DEBUG   - PYTEST: INFO: Wait for prompt
DEBUG   - PYTEST: DEBUG: Got prompt
DEBUG   - PYTEST: -------------------------------- live log call ---------------------------------
DEBUG   - PYTEST: DEBUG: #: uart:~$ too late
DEBUG   - PYTEST: DEBUG: #: PASS - test_name_of_testcase in 10.004 seconds
DEBUG   - PYTEST: DEBUG: #: ===================================================================
DEBUG   - PYTEST: DEBUG: #: TESTSUITE testsuite succeeded
DEBUG   - PYTEST: DEBUG: #: ------ TESTSUITE SUMMARY START ------
DEBUG   - PYTEST: DEBUG: #: SUITE PASS - 100.00% [testsuite]: pass = 1, fail = 0, skip = 0, total = 1 duration = 10.004 seconds
DEBUG   - PYTEST: DEBUG: #: - PASS - [testsuite.test_name_of_testcase] duration = 10.004 seconds
DEBUG   - PYTEST: DEBUG: #: ------ TESTSUITE SUMMARY END ------
DEBUG   - PYTEST: DEBUG: #: ===================================================================
DEBUG   - PYTEST: DEBUG: #: RunID: 0034b44563ddef23481784e3c7cc39e7
DEBUG   - PYTEST: DEBUG: #: PROJECT EXECUTION SUCCESSFUL

There is no output with TC_PRINT parts. I need to dive deeper into the shell implementation to gather knowledge why the output is moved somewhere to background.

@gbarkadiusz
Copy link
Collaborator Author

Quick update, It seems to work.
I added

static void *shell_setup(void)
{
	/* Let the shell backend initialize. */
	k_msleep(100);
	return NULL;
}

ZTEST_SUITE(testsuite, NULL, shell_setup, NULL, NULL, NULL);

pytest

import pytest, time
from twister_harness import DeviceAdapter
from twister_harness import Shell

def test_catch_and_respons(dut: DeviceAdapter):
    while True:
        lines = dut.readlines()  # Adjust timeout as needed
        # Find the line containing 'measure_power'
        measure_power_line = next((line for line in lines if 'measure_power' in line), None)
        if measure_power_line:
            print(f"Found line: {measure_power_line}")
            shell = Shell(dut)
            shell.exec_command("ztest_harness control_testcase fail")
            break

the log:

DEBUG   - PYTEST: DEBUG: Flashing finished
DEBUG   - PYTEST: -------------------------------- live log call ---------------------------------
DEBUG   - PYTEST: DEBUG: #: *** Booting Zephyr OS build v3.7.0-rc2-3875-gac1cc17ae90a ***
DEBUG   - PYTEST: DEBUG: #: Running TESTSUITE testsuite
DEBUG   - PYTEST: DEBUG: #: ===================================================================
DEBUG   - PYTEST: DEBUG: #: 
DEBUG   - PYTEST: DEBUG: #: uart:~$ START - test_name_of_testcase
DEBUG   - PYTEST: DEBUG: #: measure_power 100 1 3
DEBUG   - PYTEST: Found line: measure_power 100 1 3
DEBUG   - PYTEST: DEBUG: #: ztest_harness control_testcase fail
DEBUG   - PYTEST: DEBUG: #: FAIL - test_name_of_testcase in 0.025 seconds
DEBUG   - PYTEST: DEBUG: #: ===================================================================
DEBUG   - PYTEST: DEBUG: #: TESTSUITE testsuite failed.
DEBUG   - PYTEST: DEBUG: #: ------ TESTSUITE SUMMARY START ------
DEBUG   - PYTEST: DEBUG: #: SUITE FAIL -   0.00% [testsuite]: pass = 0, fail = 1, skip = 0, total = 1 duration = 0.025 seconds
DEBUG   - PYTEST: DEBUG: #: - FAIL - [testsuite.test_name_of_testcase] duration = 0.025 seconds
DEBUG   - PYTEST: DEBUG: #: ------ TESTSUITE SUMMARY END ------
DEBUG   - PYTEST: DEBUG: #: ===================================================================
DEBUG   - PYTEST: DEBUG: #: RunID: 59cf315eb1495eeae656f6b34c7d2282
DEBUG   - PYTEST: DEBUG: #: PROJECT EXECUTION FAILED

@bjarki-andreasen
Copy link
Collaborator

bjarki-andreasen commented Nov 29, 2024

This is a bit more graceful (though not perfect) for waiting on the shell:

	const struct shell *sh = shell_backend_uart_get_ptr();

	zassert_true(WAIT_FOR(shell_ready(sh), 100000, k_msleep(1)));

@ceolin
Copy link
Member

ceolin commented Dec 2, 2024

@bjarki-andreasen I thought about the unit of measured value. We have several options. Pure Average of current. (As you said, it is a half of story). image But the power can be calculated as: image

RMS (Root Mean Square) is a better metric for fluctuating signals as it considers the variation in current over time. RMS current can be calculated as: image and the power can be calculated a: image

Total Energy Instead of Power. If the measurement duration image is known, we can calculate the total energy (𝐸) consumed over the period. This gives a clearer picture of energy usage: image where: image

The question is. How to calculate the expected value? Maybe it is not a good question at this time but let me treat this RFC as a brainstorm and place to share all ideas and warries. :)

Would be nice to support multiple types since they address different things. In general total energy is probably what people want because it is directly related to battery life, but it is not super helpful for debugging power states consumption, for this RMS and pure average of current are better.

I am wondering if RMS is the best measurement unit for testing sleep states, it seems that pure average of current is better for this.

@ceolin
Copy link
Member

ceolin commented Dec 2, 2024

@gbarkadiusz thanks ! really appreciate it.

@gbarkadiusz
Copy link
Collaborator Author

@gbarkadiusz thanks ! really appreciate it.

It's my pleasure ;)

@gbarkadiusz
Copy link
Collaborator Author

@bjarki-andreasen @ceolin FYI.
I encountered a new issue regarding triggering measurements from the Zephyr application after enabling the Power Management (PM) subsystem. It appears that Zephyr disables the shell while the system enters low-power states, causing the shell to become unresponsive. Maybe a quick fix can be to enable shell/uart as wakeup-source.

@bjarki-andreasen
Copy link
Collaborator

bjarki-andreasen commented Dec 4, 2024

@bjarki-andreasen @ceolin FYI. I encountered a new issue regarding triggering measurements from the Zephyr application after enabling the Power Management (PM) subsystem. It appears that Zephyr disables the shell while the system enters low-power states, causing the shell to become unresponsive. Maybe a quick fix can be to enable shell/uart as wakeup-source.

Its more likely PM is suspending the UART device in some low power state as defined by the SoC, hence the shell becomes unresponsive :)

In any case, could the solution be to trigger the measurement while in a higher power state, set some timer to exit low power state, then enter low power state? measurement is done by pytest, then system exits low power state and UART is resumed, allowing the result to be passed to the shell :)

@erwango
Copy link
Member

erwango commented Dec 5, 2024

@bjarki-andreasen @ceolin FYI. I encountered a new issue regarding triggering measurements from the Zephyr application after enabling the Power Management (PM) subsystem. It appears that Zephyr disables the shell while the system enters low-power states, causing the shell to become unresponsive. Maybe a quick fix can be to enable shell/uart as wakeup-source.

Did you try to use the following configuration:
https://github.com/zephyrproject-rtos/zephyr/blob/main/samples/boards/st/power_mgmt/serial_wakeup/boards/stm32l562e_dk.overlay ?

If not working then this is a regression

@gbarkadiusz
Copy link
Collaborator Author

gbarkadiusz commented Jan 9, 2025

@bjarki-andreasen @ceolin FYI. I encountered a new issue regarding triggering measurements from the Zephyr application after enabling the Power Management (PM) subsystem. It appears that Zephyr disables the shell while the system enters low-power states, causing the shell to become unresponsive. Maybe a quick fix can be to enable shell/uart as wakeup-source.

Did you try to use the following configuration: https://github.com/zephyrproject-rtos/zephyr/blob/main/samples/boards/st/power_mgmt/serial_wakeup/boards/stm32l562e_dk.overlay ?

If not working then this is a regression

I tried to use this sample with the STM32L562E-DK, but it didn't work, or I might have done something wrong. The shell was not responding,

@erwango
Copy link
Member

erwango commented Jan 9, 2025

I tried to use this sample with the STM32L562E-DK, but it didn't work, or I might have done something wrong. The shell was not responding,

Ok. Would you mind raising a dedicated issue ?

@gbarkadiusz
Copy link
Collaborator Author

gbarkadiusz commented Jan 9, 2025

Before that, I'd like to double check that sample with another board. But OFC I'll raise the issue with appropriate description.
@erwango
PS: I tried to test it manually using west instead of twister. I delved into the source of this sample and noticed that there is a
platform_allow: nucleo_wb55rg in sample.yaml, and then I found this pr. #49798.
Twister filters it correctly. Despite the removal of stm32l562e_dk from the sample.yaml file it should work correctly, using west?

@gbarkadiusz
Copy link
Collaborator Author

@erwango
After adding stm32l562e_dk to the sample.yaml file, the twister throws after flashing.
Timed out while monitoring serial output on stm32l562e_dk/stm32l562xx
So, It's time to raise the issue ;)

@SzymonRichert
Copy link
Collaborator

SzymonRichert commented Jan 24, 2025

@erwango @bjarki-andreasen @ceolin, can we drive it to some initial version merged? We can rework/improve/extend this later.

@nashif
Copy link
Member

nashif commented Apr 23, 2025

closing this as done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: Power Management RFC Request For Comments: want input from the community
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

7 participants