From 6809a6fb58e875783c716b0e539a3a5adab89959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C5=82=C4=85b?= Date: Wed, 11 Sep 2024 12:38:22 +0200 Subject: [PATCH] tests: benchmarks: Add stress test for drivers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create test that will concurrently use drivers for multiple peripherals, such as: - UART (LOG system), - SPI (adxl362 accelerometer), - I2C (bme680 sensor), - ADC, - PWM (LED dimming), - GPIO (LED toggle), - timer (kernel), - counter (timer instance), - CAN (only on nrf54h20, internal loopback mode), - temperature sensor (DIE_TEMP), - flash (external NOR Flash memory; read only), - I2S (only on nrf54l, send in air, read from air), - watchdog, - plus cpu load thread. Signed-off-by: Sebastian Głąb --- .github/test-spec.yml | 1 + CODEOWNERS | 1 + scripts/ci/tags.yaml | 19 ++ .../benchmarks/peripheral_load/CMakeLists.txt | 12 + .../boards/nrf54h20dk_nrf54h20_cpuapp.conf | 1 + .../boards/nrf54h20dk_nrf54h20_cpuapp.overlay | 56 ++++ .../boards/nrf54l15dk_nrf54l15_cpuapp.overlay | 105 ++++++++ tests/benchmarks/peripheral_load/prj.conf | 11 + tests/benchmarks/peripheral_load/sample.yaml | 112 ++++++++ .../src/accelerometer_thread.c | 72 ++++++ .../peripheral_load/src/adc_thread.c | 109 ++++++++ .../peripheral_load/src/bme680_thread.c | 98 +++++++ .../peripheral_load/src/can_thread.c | 118 +++++++++ tests/benchmarks/peripheral_load/src/common.h | 102 ++++++++ .../peripheral_load/src/counter_thread.c | 105 ++++++++ .../peripheral_load/src/flash_thread.c | 64 +++++ .../peripheral_load/src/gpio_thread.c | 61 +++++ .../peripheral_load/src/i2s_thread.c | 241 ++++++++++++++++++ .../peripheral_load/src/load_thread.c | 47 ++++ tests/benchmarks/peripheral_load/src/main.c | 43 ++++ .../peripheral_load/src/pwm_thread.c | 124 +++++++++ .../peripheral_load/src/temp_thread.c | 64 +++++ .../peripheral_load/src/timer_thread.c | 54 ++++ .../peripheral_load/src/wdt_thread.c | 93 +++++++ 24 files changed, 1713 insertions(+) create mode 100644 tests/benchmarks/peripheral_load/CMakeLists.txt create mode 100644 tests/benchmarks/peripheral_load/boards/nrf54h20dk_nrf54h20_cpuapp.conf create mode 100644 tests/benchmarks/peripheral_load/boards/nrf54h20dk_nrf54h20_cpuapp.overlay create mode 100644 tests/benchmarks/peripheral_load/boards/nrf54l15dk_nrf54l15_cpuapp.overlay create mode 100644 tests/benchmarks/peripheral_load/prj.conf create mode 100644 tests/benchmarks/peripheral_load/sample.yaml create mode 100644 tests/benchmarks/peripheral_load/src/accelerometer_thread.c create mode 100644 tests/benchmarks/peripheral_load/src/adc_thread.c create mode 100644 tests/benchmarks/peripheral_load/src/bme680_thread.c create mode 100644 tests/benchmarks/peripheral_load/src/can_thread.c create mode 100644 tests/benchmarks/peripheral_load/src/common.h create mode 100644 tests/benchmarks/peripheral_load/src/counter_thread.c create mode 100644 tests/benchmarks/peripheral_load/src/flash_thread.c create mode 100644 tests/benchmarks/peripheral_load/src/gpio_thread.c create mode 100644 tests/benchmarks/peripheral_load/src/i2s_thread.c create mode 100644 tests/benchmarks/peripheral_load/src/load_thread.c create mode 100644 tests/benchmarks/peripheral_load/src/main.c create mode 100644 tests/benchmarks/peripheral_load/src/pwm_thread.c create mode 100644 tests/benchmarks/peripheral_load/src/temp_thread.c create mode 100644 tests/benchmarks/peripheral_load/src/timer_thread.c create mode 100644 tests/benchmarks/peripheral_load/src/wdt_thread.c diff --git a/.github/test-spec.yml b/.github/test-spec.yml index 687726761493..1b8b6ac57fa9 100644 --- a/.github/test-spec.yml +++ b/.github/test-spec.yml @@ -592,6 +592,7 @@ - "snippets/emulated-*/**/*" - "tests/subsys/event_manager_proxy/**/*" - "tests/benchmarks/multicore/**/*" + - "tests/benchmarks/peripheral_load/**/*" - "tests/drivers/pwm/**/*" - "tests/drivers/sensor/**/*" diff --git a/CODEOWNERS b/CODEOWNERS index bc045ec78f92..f152e6045c28 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -344,6 +344,7 @@ /tests/benchmarks/multicore/idle_gpio/ @adamkondraciuk @nrfconnect/ncs-low-level-test /tests/benchmarks/i2c_endless/ @nrfconnect/ncs-low-level-test /tests/benchmarks/spi_endless/ @nrfconnect/ncs-low-level-test +/tests/benchmarks/peripheral_load/ @nrfconnect/ncs-low-level-test /tests/bluetooth/tester/ @carlescufi @nrfconnect/ncs-paladin /tests/bluetooth/iso/ @nrfconnect/ncs-audio @Frodevan /tests/crypto/ @stephen-nordic @magnev diff --git a/scripts/ci/tags.yaml b/scripts/ci/tags.yaml index d71519793044..85e64f915b62 100644 --- a/scripts/ci/tags.yaml +++ b/scripts/ci/tags.yaml @@ -1211,6 +1211,25 @@ ci_tests_benchmarks_i2c_endless: - nrf/tests/benchmarks/i2c_endless/ - zephyr/drivers/i2c/ +ci_tests_benchmarks_peripheral_load: + files: + - modules/hal/nordic/nrfx/ + - nrf/tests/benchmarks/peripheral_load/ + - zephyr/drivers/sensor/adi/adxl362 + - zephyr/drivers/sensor/bosch/bme680 + - zephyr/drivers/sensor/nordic/temp/ + - zephyr/drivers/adc/ + - zephyr/drivers/can/ + - zephyr/drivers/counter/ + - zephyr/drivers/flash/ + - zephyr/drivers/gpio/ + - zephyr/drivers/i2c/ + - zephyr/drivers/i2s/ + - zephyr/drivers/pwm/ + - zephyr/drivers/spi/ + - zephyr/drivers/watchdog/ + - zephyr/soc/nordic/ + ci_tests_benchmarks_spi_endless: files: - modules/hal/nordic/nrfx/ diff --git a/tests/benchmarks/peripheral_load/CMakeLists.txt b/tests/benchmarks/peripheral_load/CMakeLists.txt new file mode 100644 index 000000000000..2a45cd69e5f8 --- /dev/null +++ b/tests/benchmarks/peripheral_load/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(periph_stress) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/benchmarks/peripheral_load/boards/nrf54h20dk_nrf54h20_cpuapp.conf b/tests/benchmarks/peripheral_load/boards/nrf54h20dk_nrf54h20_cpuapp.conf new file mode 100644 index 000000000000..219e5ebb46ed --- /dev/null +++ b/tests/benchmarks/peripheral_load/boards/nrf54h20dk_nrf54h20_cpuapp.conf @@ -0,0 +1 @@ +CONFIG_NRFS=y diff --git a/tests/benchmarks/peripheral_load/boards/nrf54h20dk_nrf54h20_cpuapp.overlay b/tests/benchmarks/peripheral_load/boards/nrf54h20dk_nrf54h20_cpuapp.overlay new file mode 100644 index 000000000000..203eee621db5 --- /dev/null +++ b/tests/benchmarks/peripheral_load/boards/nrf54h20dk_nrf54h20_cpuapp.overlay @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + aliases { + test-counter = &timer137; + watchdog0 = &wdt010; + }; + + zephyr,user { + io-channels = <&adc 0>, <&adc 1>; + }; +}; + +&adc { + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1_2"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; + zephyr,resolution = <10>; + }; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_2"; + zephyr,reference = "ADC_REF_EXTERNAL0"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; + zephyr,resolution = <10>; + }; +}; + +&mx25uw63 { + status = "okay"; +}; + +&timer137 { + status = "okay"; + prescaler = <4>; +}; + +temp_sensor: &temp_nrfs { + status = "okay"; +}; + +&wdt010 { + status = "okay"; +}; diff --git a/tests/benchmarks/peripheral_load/boards/nrf54l15dk_nrf54l15_cpuapp.overlay b/tests/benchmarks/peripheral_load/boards/nrf54l15dk_nrf54l15_cpuapp.overlay new file mode 100644 index 000000000000..cfc66f000697 --- /dev/null +++ b/tests/benchmarks/peripheral_load/boards/nrf54l15dk_nrf54l15_cpuapp.overlay @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/* Rev. 0.8.1: + * P0.00 - TX_0 + * P0.01 - RX_0 + * P0.02 - RTS_0 + * P0.03 - CTS_0 + * P0.04 - Button_3 + * + * P1.00 - 32k_XTAL1 + * P1.01 - 32k_XTAL2 + * P1.02 - NFC_1 + * P1.03 - NFC_2 + * P1.04 - TX_1 + * P1.05 - RX_1 + * P1.06 - RTS_1 + * P1.07 - CTS_1 + * P1.08 - Button_1 / I2S_SDOUT + * P1.09 - Button_2 / I2S_SDIN + * P1.10 - LED_1 / I2S_LRCK_M + * P1.11 - TWIM_SCL to BME680 + ADC_ch0 + * P1.12 - TWIM_SDA to BME680 + ADC_ch1 + * P1.13 - Button_4 / CS_BME680 + * P1.14 - LED_3 / IRQ / I2S_SCK_M + * + * P2.00 - QSPI + * P2.01 - QSPI + * P2.02 - QSPI + * P2.03 - QSPI + * P2.04 - QSPI + * P2.05 - QSPI + * P2.06 - SPIM_SCK + * P2.07 - LED_2 + * P2.08 - SPIM_MOSI to ADXL362 + * P2.09 - LED_0 / SPIM_MISO to ADXL362 + * P2.10 - CS_ADXL362 + */ + +/ { + aliases { + i2s-node0 = &i2s20; + test-counter = &timer24; + }; + + zephyr,user { + io-channels = <&adc 0>, <&adc 1>; + }; +}; + +&pinctrl { + i2s20_default_alt: i2s20_default_alt { + group1 { + psels = , + , + , + ; + }; + }; +}; + +&adc { + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.11 - I2C SCL on sensor shield */ + zephyr,resolution = <10>; + }; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1_4"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.12 - I2C SDA on sensor shield */ + zephyr,resolution = <12>; + }; +}; + +&i2s20 { + status = "okay"; + pinctrl-0 = <&i2s20_default_alt>; + pinctrl-names = "default"; +}; + +&timer24 { + status = "okay"; + prescaler = <4>; +}; + +temp_sensor: &temp { + status = "okay"; +}; + +&wdt31 { + status = "okay"; +}; diff --git a/tests/benchmarks/peripheral_load/prj.conf b/tests/benchmarks/peripheral_load/prj.conf new file mode 100644 index 000000000000..a7bee0ca0b3b --- /dev/null +++ b/tests/benchmarks/peripheral_load/prj.conf @@ -0,0 +1,11 @@ +CONFIG_LOG=y + +CONFIG_SENSOR=y +CONFIG_ADC=y +CONFIG_CAN=y +CONFIG_COUNTER=y +CONFIG_FLASH=y +CONFIG_GPIO=y +CONFIG_I2S=y +CONFIG_PWM=y +CONFIG_WATCHDOG=y diff --git a/tests/benchmarks/peripheral_load/sample.yaml b/tests/benchmarks/peripheral_load/sample.yaml new file mode 100644 index 000000000000..07416c91a219 --- /dev/null +++ b/tests/benchmarks/peripheral_load/sample.yaml @@ -0,0 +1,112 @@ +sample: + name: Peripheral stress test + +common: + depends_on: adc gpio i2c pwm spi watchdog + tags: drivers spi sensors ci_tests_benchmarks_peripheral_load + harness: console + +tests: + sample.benchmarks.peripheral_stress_test.nrf54h: + filter: not CONFIG_COVERAGE + harness_config: + fixture: pca63566 + type: multi_line + ordered: false + regex: + - ".*Accelerometer thread has completed" + - ".*ADC thread has completed" + - ".*BME680 thread has completed" + - ".*CAN thread has completed" + - ".*Counter thread has completed" + - ".*Flash thread has completed" + - ".*GPIO thread has completed" + - ".*PWM thread has completed" + - ".*TEMP thread has completed" + - ".*Timer thread has completed" + - ".*WDT thread has completed" + - ".*CPU load thread has completed" + - ".*all \\d{1,} threads have completed" + extra_args: + - SHIELD=pca63566 + platform_allow: nrf54h20dk/nrf54h20/cpuapp + integration_platforms: + - nrf54h20dk/nrf54h20/cpuapp + + sample.benchmarks.peripheral_stress_test.nrf54h_coverage: + filter: CONFIG_COVERAGE + harness_config: + fixture: pca63566 + type: multi_line + ordered: false + regex: + - ".*Accelerometer thread has completed" + - ".*ADC thread has completed" + - ".*BME680 thread has completed" + - ".*CAN thread has completed" + - ".*Counter thread has completed" + - ".*Flash thread has completed" + - ".*GPIO thread has completed" + - ".*PWM thread has completed" + - ".*TEMP thread has completed" + - ".*Timer thread has completed" + - ".*WDT thread has completed" + - ".*CPU load thread has completed" + - ".*all \\d{1,} threads have completed" + extra_args: + - SHIELD=pca63566;coverage_support + platform_allow: nrf54h20dk/nrf54h20/cpuapp + integration_platforms: + - nrf54h20dk/nrf54h20/cpuapp + + sample.benchmarks.peripheral_stress_test.nrf54l: + filter: not CONFIG_COVERAGE + harness_config: + fixture: pca63565 + type: multi_line + ordered: false + regex: + - ".*Accelerometer thread has completed" + - ".*ADC thread has completed" + - ".*BME680 thread has completed" + - ".*Counter thread has completed" + - ".*Flash thread has completed" + - ".*GPIO thread has completed" + - ".*I2S thread has completed" + - ".*PWM thread has completed" + - ".*TEMP thread has completed" + - ".*Timer thread has completed" + - ".*WDT thread has completed" + - ".*CPU load thread has completed" + - ".*all \\d{1,} threads have completed" + extra_args: + - SHIELD=pca63565 + platform_allow: nrf54l15dk/nrf54l15/cpuapp + integration_platforms: + - nrf54l15dk/nrf54l15/cpuapp + + sample.benchmarks.peripheral_stress_test.nrf54l_coverage: + filter: CONFIG_COVERAGE + harness_config: + fixture: pca63565 + type: multi_line + ordered: false + regex: + - ".*Accelerometer thread has completed" + - ".*ADC thread has completed" + - ".*BME680 thread has completed" + - ".*Counter thread has completed" + - ".*Flash thread has completed" + - ".*GPIO thread has completed" + - ".*I2S thread has completed" + - ".*PWM thread has completed" + - ".*TEMP thread has completed" + - ".*Timer thread has completed" + - ".*WDT thread has completed" + - ".*CPU load thread has completed" + - ".*all \\d{1,} threads have completed" + extra_args: + - SHIELD=pca63565;coverage_support + platform_allow: nrf54l15dk/nrf54l15/cpuapp + integration_platforms: + - nrf54l15dk/nrf54l15/cpuapp diff --git a/tests/benchmarks/peripheral_load/src/accelerometer_thread.c b/tests/benchmarks/peripheral_load/src/accelerometer_thread.c new file mode 100644 index 000000000000..c31d0d4c6e61 --- /dev/null +++ b/tests/benchmarks/peripheral_load/src/accelerometer_thread.c @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +LOG_MODULE_REGISTER(accel, LOG_LEVEL_INF); + +#include +#include "common.h" + +static const struct device *const accelerometer = DEVICE_DT_GET(DT_ALIAS(accel0)); + +/* Accelerometer thread */ +static void accel_thread(void *arg1, void *arg2, void *arg3) +{ + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + int ret; + struct sensor_value samples[3]; + double latest[3]; + double average[3] = {0.0}; + + atomic_inc(&started_threads); + + if (!device_is_ready(accelerometer)) { + LOG_ERR("Device %s is not ready.", accelerometer->name); + atomic_inc(&completed_threads); + return; + } + + for (int i = 0; i < (ACCEL_THREAD_COUNT_MAX * ACCEL_THREAD_OVERSAMPLING); i++) { + /* Capture acceleration value */ + ret = sensor_sample_fetch(accelerometer); + if (ret < 0) { + LOG_ERR("%s: sensor_sample_fetch() returned: %d", accelerometer->name, ret); + atomic_inc(&completed_threads); + return; + } + + /* Retrieve samples */ + ret = sensor_channel_get(accelerometer, SENSOR_CHAN_ACCEL_XYZ, samples); + if (ret < 0) { + LOG_ERR("%s: sensor_channel_get() returned: %d", accelerometer->name, ret); + atomic_inc(&completed_threads); + return; + } + + /* Calculate average */ + for (int axis = 0; axis < 3; axis++) { + latest[axis] = sensor_value_to_double(&samples[axis]); + average[axis] = (average[axis] + latest[axis]) / 2; + } + + /* Print result */ + if ((i % ACCEL_THREAD_OVERSAMPLING) == 0) { + LOG_INF("%10s - X: %6.2f; Y: %6.2f; Z: %6.2f", + accelerometer->name, average[0], average[1], average[2]); + } + + k_msleep(ACCEL_THREAD_SLEEP / ACCEL_THREAD_OVERSAMPLING); + } + LOG_INF("Accelerometer thread has completed"); + + atomic_inc(&completed_threads); +} + +K_THREAD_DEFINE(thread_accel_id, ACCEL_THREAD_STACKSIZE, accel_thread, NULL, NULL, NULL, + K_PRIO_PREEMPT(ACCEL_THREAD_PRIORITY), 0, 0); diff --git a/tests/benchmarks/peripheral_load/src/adc_thread.c b/tests/benchmarks/peripheral_load/src/adc_thread.c new file mode 100644 index 000000000000..6dc606a94220 --- /dev/null +++ b/tests/benchmarks/peripheral_load/src/adc_thread.c @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +LOG_MODULE_REGISTER(adc, LOG_LEVEL_INF); + +#include +#include "common.h" + +#if !DT_NODE_EXISTS(DT_PATH(zephyr_user)) || \ + !DT_NODE_HAS_PROP(DT_PATH(zephyr_user), io_channels) +#error "No suitable devicetree overlay specified" +#endif + +#define DT_SPEC_AND_COMMA(node_id, prop, idx) ADC_DT_SPEC_GET_BY_IDX(node_id, idx), + +/* Data of ADC io-channels specified in devicetree. */ +static const struct adc_dt_spec adc_channels[] = { + DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels, DT_SPEC_AND_COMMA) +}; + +/* Get the number of channels defined on the DTS. */ +#define CHANNEL_COUNT ARRAY_SIZE(adc_channels) + +/* ADC thread */ +static void adc_thread(void *arg1, void *arg2, void *arg3) +{ + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + int ret; + uint16_t buf; + struct adc_sequence sequence = { + .buffer = &buf, + /* buffer size in bytes, not number of samples */ + .buffer_size = sizeof(buf), + }; + uint16_t average[CHANNEL_COUNT] = { 0 }; + int32_t average_mv[CHANNEL_COUNT]; + + atomic_inc(&started_threads); + + /* Configure channels individually prior to sampling. */ + for (size_t i = 0U; i < CHANNEL_COUNT; i++) { + if (!adc_is_ready_dt(&adc_channels[i])) { + LOG_ERR("ADC controller devivce %s is not ready", + adc_channels[i].dev->name); + atomic_inc(&completed_threads); + return; + } + + ret = adc_channel_setup_dt(&adc_channels[i]); + if (ret < 0) { + LOG_ERR("Could not setup channel #%d (%d)", i, ret); + atomic_inc(&completed_threads); + return; + } + } + + for (int i = 0; i < (ADC_THREAD_COUNT_MAX * ADC_THREAD_OVERSAMPLING); i++) { + /* For each ADC channel */ + for (size_t i = 0U; i < CHANNEL_COUNT; i++) { + + /* Read ADC channel */ + ret = adc_sequence_init_dt(&adc_channels[i], &sequence); + if (ret < 0) { + LOG_ERR("adc_sequence_init_dt(%d) failed (%d)", i, ret); + atomic_inc(&completed_threads); + return; + } + + ret = adc_read_dt(&adc_channels[i], &sequence); + if (ret < 0) { + LOG_ERR("adc_read_dt(%d) failed (%d)", i, ret); + atomic_inc(&completed_threads); + return; + } + + /* Calculate running average */ + average[i] = (average[i] + buf) >> 1; + average_mv[i] = average[i]; + + ret = adc_raw_to_millivolts_dt(&adc_channels[i], &average_mv[i]); + if (ret < 0) { + LOG_ERR("adc_raw_to_millivolts_dt(%d) failed (%d)", i, ret); + atomic_inc(&completed_threads); + return; + } + } + + /* Print result */ + if ((i % ADC_THREAD_OVERSAMPLING) == 0) { + LOG_INF("CH0 = %d (%d mv), CH1 = %d (%d mv)", + average[0], average_mv[0], average[1], average_mv[1]); + } + + k_msleep(ADC_THREAD_SLEEP / ADC_THREAD_OVERSAMPLING); + } + LOG_INF("ADC thread has completed"); + + atomic_inc(&completed_threads); +} + +K_THREAD_DEFINE(thread_adc_id, ADC_THREAD_STACKSIZE, adc_thread, NULL, NULL, NULL, + K_PRIO_PREEMPT(ADC_THREAD_PRIORITY), 0, 0); diff --git a/tests/benchmarks/peripheral_load/src/bme680_thread.c b/tests/benchmarks/peripheral_load/src/bme680_thread.c new file mode 100644 index 000000000000..2b57763a2c57 --- /dev/null +++ b/tests/benchmarks/peripheral_load/src/bme680_thread.c @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +LOG_MODULE_REGISTER(be680, LOG_LEVEL_INF); + +#include +#include "common.h" + +static const struct device *const bme680 = DEVICE_DT_GET_ONE(bosch_bme680); + +/* BME680 thread */ +static void bme680_thread(void *arg1, void *arg2, void *arg3) +{ + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + int ret; + struct sensor_value samples[4]; /* 0-temp, 1-press, 2-humidity, 3-gas_res*/ + int32_t latest_val1[4]; + int32_t latest_val2[4]; + int32_t average_val1[4] = {0}; + int32_t average_val2[4] = {0}; + + atomic_inc(&started_threads); + + if (!device_is_ready(bme680)) { + LOG_ERR("Device %s is not ready.", bme680->name); + atomic_inc(&completed_threads); + return; + } + + for (int i = 0; i < (BME680_THREAD_COUNT_MAX * BME680_THREAD_OVERSAMPLING); i++) { + /* Capture samples */ + ret = sensor_sample_fetch(bme680); + if (ret < 0) { + LOG_ERR("%s: sensor_sample_fetch() returned: %d", bme680->name, ret); + atomic_inc(&completed_threads); + return; + } + + /* Retrieve samples */ + sensor_channel_get(bme680, SENSOR_CHAN_AMBIENT_TEMP, &samples[0]); + if (ret < 0) { + LOG_ERR("%s: sensor_channel_get(TEMP) returned: %d", bme680->name, ret); + atomic_inc(&completed_threads); + return; + } + sensor_channel_get(bme680, SENSOR_CHAN_PRESS, &samples[1]); + if (ret < 0) { + LOG_ERR("%s: sensor_channel_get(PRESS) returned: %d", bme680->name, ret); + atomic_inc(&completed_threads); + return; + } + sensor_channel_get(bme680, SENSOR_CHAN_HUMIDITY, &samples[2]); + if (ret < 0) { + LOG_ERR("%s: sensor_channel_get(HUMIDITY) returned: %d", bme680->name, ret); + atomic_inc(&completed_threads); + return; + } + sensor_channel_get(bme680, SENSOR_CHAN_GAS_RES, &samples[3]); + if (ret < 0) { + LOG_ERR("%s: sensor_channel_get(GAS_RES) returned: %d", bme680->name, ret); + atomic_inc(&completed_threads); + return; + } + + /* Calculate average */ + for (int property = 0; property < 4; property++) { + latest_val1[property] = samples[property].val1; + latest_val2[property] = samples[property].val2; + average_val1[property] = + (average_val1[property] + latest_val1[property]) / 2; + average_val2[property] = + (average_val2[property] + latest_val2[property]) / 2; + } + + if ((i % BME680_THREAD_OVERSAMPLING) == 0) { + /* Print result */ + LOG_INF("%10s - T: %d.%06d; P: %d.%06d; H: %d.%06d; G: %d.%06d", + bme680->name, + average_val1[0], average_val2[0], average_val1[1], average_val2[1], + average_val1[2], average_val2[2], average_val1[3], average_val2[3]); + } + + k_msleep(BME680_THREAD_SLEEP / BME680_THREAD_OVERSAMPLING); + } + LOG_INF("BME680 thread has completed"); + + atomic_inc(&completed_threads); +} + +K_THREAD_DEFINE(thread_bme680_id, BME680_THREAD_STACKSIZE, bme680_thread, NULL, NULL, NULL, + K_PRIO_PREEMPT(BME680_THREAD_PRIORITY), 0, 0); diff --git a/tests/benchmarks/peripheral_load/src/can_thread.c b/tests/benchmarks/peripheral_load/src/can_thread.c new file mode 100644 index 000000000000..83a6cf6ede51 --- /dev/null +++ b/tests/benchmarks/peripheral_load/src/can_thread.c @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +LOG_MODULE_REGISTER(can, LOG_LEVEL_INF); + +#include +#include "common.h" + +#if DT_NODE_HAS_STATUS(DT_CHOSEN(zephyr_canbus), okay) + +static const struct device *const can_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_canbus)); +static int rx_counter; + +const struct can_frame test_std_frame = { + .flags = 0, + .id = 0x555, + .dlc = 8, + .data = {1, 2, 3, 4, 5, 6, 7, 8} +}; + +const struct can_filter test_std_filter = { + .flags = 0U, + .id = 0x555, + .mask = CAN_STD_ID_MASK +}; + +void tx_callback(const struct device *dev, int error, void *user_data) +{ + char *sender = (char *)user_data; + + if (error != 0) { + LOG_ERR("Sending failed [%d]\nSender: %s", error, sender); + } +} + +void rx_callback(const struct device *dev, struct can_frame *frame, void *user_data) +{ + rx_counter++; + LOG_INF("Received CAN frame (# %d)", rx_counter); +} + +/* CAN thread */ +static void can_thread(void *arg1, void *arg2, void *arg3) +{ + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + int ret; + + atomic_inc(&started_threads); + + if (!device_is_ready(can_dev)) { + LOG_ERR("Device %s is not ready.", can_dev->name); + atomic_inc(&completed_threads); + return; + } + + /* failsafe */ + (void)can_stop(can_dev); + + /* set loopback mode */ + ret = can_set_mode(can_dev, CAN_MODE_LOOPBACK); + if (ret < 0) { + LOG_ERR("can_set_mode() returned %d", ret); + atomic_inc(&completed_threads); + return; + } + + ret = can_start(can_dev); + if (ret < 0) { + LOG_ERR("can_start() returned %d", ret); + atomic_inc(&completed_threads); + return; + } + + /* Configure CAN RX filter */ + ret = can_add_rx_filter(can_dev, rx_callback, NULL, &test_std_filter); + if (ret < 0) { + LOG_ERR("can_add_rx_filter() returned %d", ret); + atomic_inc(&completed_threads); + return; + } + + while (rx_counter < CAN_THREAD_COUNT_MAX) { + /* Send CAN message */ + ret = can_send(can_dev, &test_std_frame, K_FOREVER, tx_callback, "CAN thread"); + if (ret < 0) { + LOG_ERR("can_send() returned: %d", ret); + atomic_inc(&completed_threads); + return; + } + + k_msleep(CAN_THREAD_SLEEP); + } + + ret = can_stop(can_dev); + if (ret < 0) { + LOG_ERR("can_stop() returned %d", ret); + atomic_inc(&completed_threads); + return; + } + + LOG_INF("CAN thread has completed"); + + atomic_inc(&completed_threads); +} + +K_THREAD_DEFINE(thread_can_id, CAN_THREAD_STACKSIZE, can_thread, NULL, NULL, NULL, + K_PRIO_PREEMPT(CAN_THREAD_PRIORITY), 0, 0); + +#else +#pragma message("CAN thread skipped due to missing node in the DTS") +#endif diff --git a/tests/benchmarks/peripheral_load/src/common.h b/tests/benchmarks/peripheral_load/src/common.h new file mode 100644 index 000000000000..a7fdee610124 --- /dev/null +++ b/tests/benchmarks/peripheral_load/src/common.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef PERIPHERAL_STRESS_TEST_H +#define PERIPHERAL_STRESS_TEST_H + +#include +#include + +/* Thread status */ +extern atomic_t started_threads; +extern atomic_t completed_threads; + +/* Accelerator readout via SPI: */ +#define ACCEL_THREAD_COUNT_MAX (20) +#define ACCEL_THREAD_OVERSAMPLING (3) +#define ACCEL_THREAD_STACKSIZE (1024) +#define ACCEL_THREAD_PRIORITY (5) +#define ACCEL_THREAD_SLEEP (200) + +/* ADC thread: */ +#define ADC_THREAD_COUNT_MAX (20) +#define ADC_THREAD_OVERSAMPLING (3) +#define ADC_THREAD_STACKSIZE (1024) +#define ADC_THREAD_PRIORITY (5) +#define ADC_THREAD_SLEEP (200) + +/* BME680 environment sensor readout via I2C: */ +#define BME680_THREAD_COUNT_MAX (20) +#define BME680_THREAD_OVERSAMPLING (2) +#define BME680_THREAD_STACKSIZE (1024) +#define BME680_THREAD_PRIORITY (5) +#define BME680_THREAD_SLEEP (200) + +/* CAN thread: */ +#define CAN_THREAD_COUNT_MAX (20) +#define CAN_THREAD_STACKSIZE (1024) +#define CAN_THREAD_PRIORITY (5) +#define CAN_THREAD_SLEEP (200) + +/* Counter thread: */ +#define COUNTER_THREAD_COUNT_MAX (20) +#define COUNTER_THREAD_STACKSIZE (512) +#define COUNTER_THREAD_PRIORITY (5) +#define COUNTER_THREAD_PERIOD (200) + +/* FLASH thread: */ +#define FLASH_THREAD_COUNT_MAX (20) +#define FLASH_THREAD_STACKSIZE (1024) +#define FLASH_THREAD_PRIORITY (5) +#define FLASH_THREAD_SLEEP (200) + +/* GPIO thread: */ +#define GPIO_THREAD_COUNT_MAX (20) +#define GPIO_THREAD_STACKSIZE (512) +#define GPIO_THREAD_PRIORITY (5) +#define GPIO_THREAD_SLEEP (200) + +/* I2S thread: */ +#define I2S_THREAD_COUNT_MAX (20) +#define I2S_THREAD_STACKSIZE (1024) +#define I2S_THREAD_PRIORITY (5) +#define I2S_THREAD_SLEEP (200) + +/* PWM thread: */ +#define PWM_THREAD_COUNT_MAX (20) +#define PWM_THREAD_OVERSAMPLING (10) +#define PWM_THREAD_STACKSIZE (512) +#define PWM_THREAD_PRIORITY (5) +#define PWM_THREAD_SLEEP (200) + +/* TEMP thread: */ +#define TEMP_THREAD_COUNT_MAX (20) +#define TEMP_THREAD_STACKSIZE (512) +#define TEMP_THREAD_PRIORITY (5) +#define TEMP_THREAD_PERIOD (200) + +/* Timer thread: */ +#define TIMER_THREAD_COUNT_MAX (20) +#define TIMER_THREAD_STACKSIZE (512) +#define TIMER_THREAD_PRIORITY (5) +#define TIMER_THREAD_PERIOD (200) + +/* Watchdog thread: */ +#define WDT_THREAD_COUNT_MAX (20) +#define WDT_THREAD_STACKSIZE (512) +#define WDT_THREAD_PRIORITY (4) +#define WDT_THREAD_PERIOD (200) +#define WDT_WINDOW_MAX (2 * WDT_THREAD_PERIOD) +#define WDT_TAG (0x12345678U) + + +/* Busy load thread: */ +#define LOAD_THREAD_STACKSIZE (1024) +#define LOAD_THREAD_PRIORITY (CONFIG_NUM_PREEMPT_PRIORITIES - 1) +#define LOAD_THREAD_DURATION (ACCEL_THREAD_SLEEP * (ACCEL_THREAD_COUNT_MAX + 2)) + + +#endif diff --git a/tests/benchmarks/peripheral_load/src/counter_thread.c b/tests/benchmarks/peripheral_load/src/counter_thread.c new file mode 100644 index 000000000000..d89691dde637 --- /dev/null +++ b/tests/benchmarks/peripheral_load/src/counter_thread.c @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +LOG_MODULE_REGISTER(cnt, LOG_LEVEL_INF); + +#include +#include "common.h" + +#define ALARM_CHANNEL_ID (0) + +static const struct device *const counter_dev = DEVICE_DT_GET(DT_ALIAS(test_counter)); +static struct counter_alarm_cfg alarm_cfg; +static int counter_expire_count; + + +static void my_counter_cb(const struct device *counter_dev, + uint8_t chan_id, uint32_t ticks, void *user_data) +{ + int ret; + uint32_t now_ticks; + uint64_t now_usec; + uint64_t expire_usec; + + counter_expire_count++; + + /* Get current ticks */ + ret = counter_get_value(counter_dev, &now_ticks); + if (ret) { + LOG_ERR("counter_get_value() failed (%d)", ret); + return; + } + + /* Set a new alarm */ + if (counter_expire_count < COUNTER_THREAD_COUNT_MAX) { + ret = counter_set_channel_alarm(counter_dev, ALARM_CHANNEL_ID, user_data); + if (ret != 0) { + LOG_ERR("counter_set_channel_alarm() failed (%d)", ret); + } + } else { + counter_stop(counter_dev); + } + + /* Convert ticks to us */ + expire_usec = counter_ticks_to_us(counter_dev, ticks); + now_usec = counter_ticks_to_us(counter_dev, now_ticks); + + LOG_INF("Counter expired %d times (at %lld us), restarted just after %lld us", + counter_expire_count, expire_usec, now_usec); +} + +/* Counter thread */ +static void counter_thread(void *arg1, void *arg2, void *arg3) +{ + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + int ret; + + atomic_inc(&started_threads); + + if (!device_is_ready(counter_dev)) { + LOG_ERR("Device %s is not ready.", counter_dev->name); + atomic_inc(&completed_threads); + return; + } + + counter_start(counter_dev); + + alarm_cfg.flags = 0; + alarm_cfg.ticks = counter_us_to_ticks(counter_dev, COUNTER_THREAD_PERIOD * 1000); + alarm_cfg.callback = my_counter_cb; + alarm_cfg.user_data = &alarm_cfg; + + ret = counter_set_channel_alarm(counter_dev, ALARM_CHANNEL_ID, &alarm_cfg); + if (-EINVAL == ret) { + LOG_ERR("Alarm settings invalid"); + atomic_inc(&completed_threads); + return; + } else if (-ENOTSUP == ret) { + LOG_ERR("Alarm setting request not supported"); + atomic_inc(&completed_threads); + return; + } else if (ret != 0) { + LOG_ERR("counter_set_channel_alarm() failed (%d)", ret); + atomic_inc(&completed_threads); + return; + } + + while (counter_expire_count < COUNTER_THREAD_COUNT_MAX) { + /* Wait for condition to be meet */ + k_msleep(100); + } + + LOG_INF("Counter thread has completed"); + + atomic_inc(&completed_threads); +} + +K_THREAD_DEFINE(thread_counter_id, COUNTER_THREAD_STACKSIZE, counter_thread, NULL, NULL, NULL, + K_PRIO_PREEMPT(COUNTER_THREAD_PRIORITY), 0, 0); diff --git a/tests/benchmarks/peripheral_load/src/flash_thread.c b/tests/benchmarks/peripheral_load/src/flash_thread.c new file mode 100644 index 000000000000..2add58919295 --- /dev/null +++ b/tests/benchmarks/peripheral_load/src/flash_thread.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +LOG_MODULE_REGISTER(flash, LOG_LEVEL_INF); + +#include +#include +#include "common.h" + +#if DT_HAS_COMPAT_STATUS_OKAY(jedec_spi_nor) +#define FLASH_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(jedec_spi_nor) +#elif DT_HAS_COMPAT_STATUS_OKAY(nordic_qspi_nor) +#define FLASH_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(nordic_qspi_nor) +#else +#error Unsupported flash driver +#define FLASH_NODE DT_INVALID_NODE +#endif + +#define TEST_AREA_OFFSET (0xff000) +#define EXPECTED_SIZE (512) + +static const struct device *const flash_dev = DEVICE_DT_GET(FLASH_NODE); + +/* Flash thread */ +static void flash_thread(void *arg1, void *arg2, void *arg3) +{ + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + int ret; + uint8_t buf[EXPECTED_SIZE]; + + atomic_inc(&started_threads); + + if (!device_is_ready(flash_dev)) { + LOG_ERR("Device %s is not ready.", flash_dev->name); + atomic_inc(&completed_threads); + return; + } + + for (int i = 0; i < FLASH_THREAD_COUNT_MAX; i++) { + /* Read external memory */ + ret = flash_read(flash_dev, TEST_AREA_OFFSET, buf, EXPECTED_SIZE); + if (ret < 0) { + LOG_ERR("flash_read() failed (%d)", ret); + atomic_inc(&completed_threads); + return; + } + LOG_INF("flash_read() #%d done", i); + + k_msleep(FLASH_THREAD_SLEEP); + } + LOG_INF("Flash thread has completed"); + + atomic_inc(&completed_threads); +} + +K_THREAD_DEFINE(thread_flash_id, FLASH_THREAD_STACKSIZE, flash_thread, NULL, NULL, NULL, + K_PRIO_PREEMPT(FLASH_THREAD_PRIORITY), 0, 0); diff --git a/tests/benchmarks/peripheral_load/src/gpio_thread.c b/tests/benchmarks/peripheral_load/src/gpio_thread.c new file mode 100644 index 000000000000..1da72fd0cbbf --- /dev/null +++ b/tests/benchmarks/peripheral_load/src/gpio_thread.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +LOG_MODULE_REGISTER(gpio, LOG_LEVEL_INF); + +#include +#include "common.h" + +#define GPIO_NODE DT_ALIAS(led2) +#if !DT_NODE_HAS_STATUS(GPIO_NODE, okay) +#error "Unsupported board: led2 devicetree alias is not defined" +#endif + +static const struct gpio_dt_spec gpio_spec = GPIO_DT_SPEC_GET(GPIO_NODE, gpios); + +/* GPIO thread */ +static void gpio_thread(void *arg1, void *arg2, void *arg3) +{ + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + int ret; + + atomic_inc(&started_threads); + + if (!gpio_is_ready_dt(&gpio_spec)) { + LOG_ERR("Device %s is not ready.", gpio_spec.port->name); + atomic_inc(&completed_threads); + return; + } + + ret = gpio_pin_configure_dt(&gpio_spec, GPIO_OUTPUT); + if (ret != 0) { + LOG_ERR("gpio_pin_configure_dt(pin %d) returned %d", gpio_spec.pin, ret); + atomic_inc(&completed_threads); + return; + } + + for (int i = 0; i < GPIO_THREAD_COUNT_MAX; i++) { + ret = gpio_pin_toggle_dt(&gpio_spec); + if (ret < 0) { + LOG_ERR("gpio_pin_toggle_dt(pin %d) returned %d", gpio_spec.pin, ret); + atomic_inc(&completed_threads); + return; + } + + LOG_INF("LED was toggled"); + k_msleep(GPIO_THREAD_SLEEP); + } + LOG_INF("GPIO thread has completed"); + + atomic_inc(&completed_threads); +} + +K_THREAD_DEFINE(thread_gpio_id, GPIO_THREAD_STACKSIZE, gpio_thread, NULL, NULL, NULL, + K_PRIO_PREEMPT(GPIO_THREAD_PRIORITY), 0, 0); diff --git a/tests/benchmarks/peripheral_load/src/i2s_thread.c b/tests/benchmarks/peripheral_load/src/i2s_thread.c new file mode 100644 index 000000000000..bd231ac0ee97 --- /dev/null +++ b/tests/benchmarks/peripheral_load/src/i2s_thread.c @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +LOG_MODULE_REGISTER(i2s, LOG_LEVEL_INF); + +#include +#include "common.h" + +#if DT_NODE_HAS_STATUS(DT_ALIAS(i2s_node0), okay) + +static const struct device *dev_i2s = DEVICE_DT_GET_OR_NULL(DT_ALIAS(i2s_node0)); + +#define SAMPLE_NO 32 +#define FRAME_CLK_FREQ 8000 +#define TIMEOUT 2000 +#define NUM_RX_BLOCKS 4 +#define NUM_TX_BLOCKS 4 + +/* The data_l represent a sine wave */ +static int16_t data_l[SAMPLE_NO] = { + 6392, 12539, 18204, 23169, 27244, 30272, 32137, 32767, 32137, + 30272, 27244, 23169, 18204, 12539, 6392, 0, -6393, -12540, + -18205, -23170, -27245, -30273, -32138, -32767, -32138, -30273, -27245, + -23170, -18205, -12540, -6393, -1, +}; + +/* The data_r represent a sine wave with double the frequency of data_l */ +static int16_t data_r[SAMPLE_NO] = { + 12539, 23169, 30272, 32767, 30272, 23169, 12539, 0, -12540, + -23170, -30273, -32767, -30273, -23170, -12540, -1, 12539, 23169, + 30272, 32767, 30272, 23169, 12539, 0, -12540, -23170, -30273, + -32767, -30273, -23170, -12540, -1, +}; + +#define BLOCK_SIZE (2 * sizeof(data_l)) + +K_MEM_SLAB_DEFINE(rx_mem_slab, BLOCK_SIZE, NUM_RX_BLOCKS, 32); +K_MEM_SLAB_DEFINE(tx_mem_slab, BLOCK_SIZE, NUM_TX_BLOCKS, 32); + + +static void fill_buf(int16_t *tx_block, int att) +{ + for (int i = 0; i < SAMPLE_NO; i++) { + tx_block[2 * i] = data_l[i] >> att; + tx_block[2 * i + 1] = data_r[i] >> att; + } +} + +static int configure_stream(const struct device *dev_i2s, enum i2s_dir dir) +{ + int ret; + struct i2s_config i2s_cfg; + + i2s_cfg.word_size = 16U; + i2s_cfg.channels = 2U; + i2s_cfg.format = I2S_FMT_DATA_FORMAT_I2S; + i2s_cfg.frame_clk_freq = FRAME_CLK_FREQ; + i2s_cfg.block_size = BLOCK_SIZE; + i2s_cfg.timeout = TIMEOUT; + + if (dir == I2S_DIR_TX) { + /* Configure the Transmit port as Master */ + i2s_cfg.options = I2S_OPT_FRAME_CLK_MASTER + | I2S_OPT_BIT_CLK_MASTER; + } else if (dir == I2S_DIR_RX) { + /* Configure the Receive port as Slave */ + i2s_cfg.options = I2S_OPT_FRAME_CLK_SLAVE + | I2S_OPT_BIT_CLK_SLAVE; + } else { /* dir == I2S_DIR_BOTH */ + i2s_cfg.options = I2S_OPT_FRAME_CLK_MASTER + | I2S_OPT_BIT_CLK_MASTER; + } + + if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) { + i2s_cfg.mem_slab = &tx_mem_slab; + ret = i2s_configure(dev_i2s, I2S_DIR_TX, &i2s_cfg); + if (ret < 0) { + LOG_ERR("Failed to configure I2S TX stream (%d)", ret); + return -1; + } + } + + if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) { + i2s_cfg.mem_slab = &rx_mem_slab; + ret = i2s_configure(dev_i2s, I2S_DIR_RX, &i2s_cfg); + if (ret < 0) { + LOG_ERR("Failed to configure I2S RX stream (%d)", ret); + return -1; + } + } + + return 0; +} + +static int tx_block_write(const struct device *dev_i2s, int att, int err) +{ + char tx_block[BLOCK_SIZE]; + int ret; + + fill_buf((uint16_t *)tx_block, att); + ret = i2s_buf_write(dev_i2s, tx_block, BLOCK_SIZE); + if (ret != err) { + LOG_ERR("i2s_buf_write failed (%d)", ret); + return ret; + } + + return 0; +} + +static int rx_block_read(const struct device *dev_i2s, int att) +{ + char rx_block[BLOCK_SIZE]; /* Data is received but left unalyzed */ + size_t rx_size; + int ret; + + ret = i2s_buf_read(dev_i2s, rx_block, &rx_size); + if (ret < 0 || rx_size != BLOCK_SIZE) { + LOG_ERR("i2s_buf_read() failed (%d)", ret); + return ret; + } + LOG_DBG("i2s_buf_read() received %d", rx_size); + + return 0; +} + +/* I2S thread */ +static void i2s_thread(void *arg1, void *arg2, void *arg3) +{ + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + int ret; + + atomic_inc(&started_threads); + + if (!device_is_ready(dev_i2s)) { + LOG_ERR("Device %s is not ready.", dev_i2s->name); + atomic_inc(&completed_threads); + return; + } + + /* Configure I2S */ + ret = configure_stream(dev_i2s, I2S_DIR_BOTH); + if (ret < 0) { + atomic_inc(&completed_threads); + return; + } + + for (int i = 0; i < I2S_THREAD_COUNT_MAX; i++) { + /* Drop??? previous communication */ + ret = i2s_trigger(dev_i2s, I2S_DIR_BOTH, I2S_TRIGGER_DROP); + if (ret < 0) { + LOG_ERR("i2s_trigger(I2S_TRIGGER_DROP) TX/RX failed (%d)", ret); + atomic_inc(&completed_threads); + return; + } + + /* Prefill TX queue */ + ret = tx_block_write(dev_i2s, 0, 0); + if (ret != 0) { + LOG_ERR("tx_block_write(0) failed (%d)", ret); + atomic_inc(&completed_threads); + return; + } + LOG_DBG("TX 1 - OK"); + + ret = tx_block_write(dev_i2s, 1, 0); + if (ret != 0) { + LOG_ERR("tx_block_write(1) failed (%d)", ret); + atomic_inc(&completed_threads); + return; + } + LOG_DBG("TX 2 - OK"); + + ret = i2s_trigger(dev_i2s, I2S_DIR_BOTH, I2S_TRIGGER_START); + if (ret < 0) { + LOG_ERR("i2s_trigger(I2S_TRIGGER_START) TX/RX failed (%d)", ret); + atomic_inc(&completed_threads); + return; + } + + ret = rx_block_read(dev_i2s, 0); + if (ret < 0) { + LOG_ERR("rx_block_read(0) failed (%d)", ret); + atomic_inc(&completed_threads); + return; + } + LOG_DBG("RX 1 - OK"); + + ret = tx_block_write(dev_i2s, 2, 0); + if (ret != 0) { + LOG_ERR("tx_block_write(2) failed (%d)", ret); + atomic_inc(&completed_threads); + return; + } + LOG_DBG("TX 3 - OK"); + + /* All data written, drain TX queue and stop both streams. */ + ret = i2s_trigger(dev_i2s, I2S_DIR_BOTH, I2S_TRIGGER_DRAIN); + if (ret < 0) { + LOG_ERR("i2s_trigger(I2S_TRIGGER_DRAIN) TX/RX failed (%d)", ret); + atomic_inc(&completed_threads); + return; + } + + ret = rx_block_read(dev_i2s, 1); + if (ret < 0) { + LOG_ERR("rx_block_read(1) failed (%d)", ret); + atomic_inc(&completed_threads); + return; + } + LOG_DBG("RX 2 - OK"); + + ret = rx_block_read(dev_i2s, 2); + if (ret < 0) { + LOG_ERR("rx_block_read(2) failed (%d)", ret); + atomic_inc(&completed_threads); + return; + } + LOG_DBG("RX 3 - OK"); + + LOG_INF("I2S data was sent"); + k_msleep(I2S_THREAD_SLEEP); + } + + LOG_INF("I2S thread has completed"); + + atomic_inc(&completed_threads); +} + +K_THREAD_DEFINE(thread_i2s_id, I2S_THREAD_STACKSIZE, i2s_thread, NULL, NULL, NULL, + K_PRIO_PREEMPT(I2S_THREAD_PRIORITY), 0, 0); + +#else +#pragma message("I2S thread skipped due to missing node in the DTS") +#endif diff --git a/tests/benchmarks/peripheral_load/src/load_thread.c b/tests/benchmarks/peripheral_load/src/load_thread.c new file mode 100644 index 000000000000..debc4a415895 --- /dev/null +++ b/tests/benchmarks/peripheral_load/src/load_thread.c @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +LOG_MODULE_REGISTER(load, LOG_LEVEL_INF); + +#include "common.h" + +static struct k_timer load_timer; +static atomic_t load_active; + +void load_timer_handler(struct k_timer *dummy) +{ + atomic_set(&load_active, 0); +} + +/* CPU load thread */ +static void load_thread(void *arg1, void *arg2, void *arg3) +{ + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + atomic_inc(&started_threads); + + atomic_set(&load_active, 1); + + k_timer_init(&load_timer, load_timer_handler, NULL); + + /* start a one-shot timer that expires after LOAD_THREAD_DURATION */ + k_timer_start(&load_timer, K_MSEC(LOAD_THREAD_DURATION), K_FOREVER); + + while (atomic_get(&load_active) == 1) { + k_busy_wait(1000); + k_yield(); + } + + LOG_INF("CPU load thread has completed"); + + atomic_inc(&completed_threads); +} + +K_THREAD_DEFINE(thread_load_id, LOAD_THREAD_STACKSIZE, load_thread, NULL, NULL, NULL, + K_PRIO_PREEMPT(LOAD_THREAD_PRIORITY), 0, 0); diff --git a/tests/benchmarks/peripheral_load/src/main.c b/tests/benchmarks/peripheral_load/src/main.c new file mode 100644 index 000000000000..5270b6c273c0 --- /dev/null +++ b/tests/benchmarks/peripheral_load/src/main.c @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +LOG_MODULE_REGISTER(main, LOG_LEVEL_INF); + +#include +#include +#include "common.h" + +/* Thread status */ +atomic_t started_threads; +atomic_t completed_threads; + +/* Watchdog status: */ +extern volatile uint32_t wdt_status; + +/* Main thread */ +int main(void) +{ + /* Check status of flag that is set when watchdog fires */ + if (wdt_status == WDT_TAG) { + LOG_INF("Starting after Watchdog has expired"); + } else { + LOG_INF("That wasn't Watchdog"); + } + wdt_status = 0U; + + /* Wait for threads to complete */ + while (atomic_get(&started_threads) < 1) { + LOG_DBG("%ld threads were started", atomic_get(&started_threads)); + k_msleep(500); + } + while (atomic_get(&completed_threads) < atomic_get(&started_threads)) { + LOG_DBG("%ld threads were started, %ld has completed", + atomic_get(&started_threads), atomic_get(&completed_threads)); + k_msleep(500); + } + LOG_INF("main: all %ld threads have completed", atomic_get(&completed_threads)); +} diff --git a/tests/benchmarks/peripheral_load/src/pwm_thread.c b/tests/benchmarks/peripheral_load/src/pwm_thread.c new file mode 100644 index 000000000000..762be558563e --- /dev/null +++ b/tests/benchmarks/peripheral_load/src/pwm_thread.c @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +LOG_MODULE_REGISTER(pwm, LOG_LEVEL_INF); + +#include +#include "common.h" + +static const struct pwm_dt_spec pwm_led0 = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led0)); + +/* PWM thread */ +static void pwm_thread(void *arg1, void *arg2, void *arg3) +{ + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + int ret; + bool dir_up; + uint32_t pwm_period; + uint32_t pulse_min; + uint32_t pulse_max; + int32_t pulse_step, pulse_step_min, pulse_step_diff; + uint32_t current_pulse_width; + + atomic_inc(&started_threads); + + if (!pwm_is_ready_dt(&pwm_led0)) { + LOG_ERR("Device %s is not ready.", pwm_led0.dev->name); + atomic_inc(&completed_threads); + return; + } + + /* + * In case the default pwm_period value cannot be set for + * some PWM hardware, decrease its value until it can. + */ + LOG_INF("Detecting PWM period for channel %d", pwm_led0.channel); + pwm_period = PWM_USEC(200U); + while (pwm_set_dt(&pwm_led0, pwm_period, pwm_period) != 0) { + pwm_period /= 2U; + LOG_INF("Decreasing period to %u", pwm_period); + } + + /* + * There is no distinct change in LED brightness for high PWM duty cycles. + * Thus limit duty cycle to [0; max/2]. + * Also, brightness is NOT a linear function of voltage on LED's terminals. + */ + pulse_min = 0; + pulse_max = pwm_period / 2; + pulse_step_min = (pulse_max - pulse_min) / 78U; + pulse_step_diff = (pulse_max - pulse_min) / 3000U; + /* Apply default values */ + pulse_step = pulse_step_min; + current_pulse_width = pulse_min; + dir_up = true; + LOG_INF("Period is %u nsec", pwm_period); + LOG_INF("PWM pulse width varies from %u to %u with %u step (variable)", + pulse_min, pulse_max, pulse_step); + + for (int i = 0; i < (PWM_THREAD_COUNT_MAX * PWM_THREAD_OVERSAMPLING); i++) { + /* set pulse width */ + ret = pwm_set_dt(&pwm_led0, pwm_period, current_pulse_width); + if (ret) { + LOG_ERR("pwm_set_dt(%u, %u) returned %d", + pwm_period, current_pulse_width, ret); + atomic_inc(&completed_threads); + return; + } + + if (dir_up) { + /* increase brightness */ + current_pulse_width += pulse_step; + if (current_pulse_width >= pulse_max) { + /* allow current_pulse_width be higher than pulse_max */ + /* current_pulse_width = pulse_max; */ + dir_up = false; + } else { + /* increase step size */ + pulse_step += pulse_step_diff; + } + } else { + /* decrease brightness */ + if (current_pulse_width >= pulse_step) { + /* prevent value overflow */ + current_pulse_width -= pulse_step; + } else { + current_pulse_width = 0; + } + /* with decreasing step */ + if (pulse_step >= pulse_step_diff) { + /* prevent value overflow */ + pulse_step -= pulse_step_diff; + } else { + pulse_step = pulse_step_min; + } + if (current_pulse_width <= pulse_min) { + /* At lowest brightnest, restore defaul values */ + current_pulse_width = pulse_min; + pulse_step = pulse_step_min; + dir_up = true; + } + } + + /* Print current_pulse_width */ + if ((i % PWM_THREAD_OVERSAMPLING) == 0) { + LOG_INF("pwm_period = %u, current_pulse_width = %u, pulse_step = %u", + pwm_period, current_pulse_width, pulse_step); + } + + k_msleep(PWM_THREAD_SLEEP / PWM_THREAD_OVERSAMPLING); + } + LOG_INF("PWM thread has completed"); + + atomic_inc(&completed_threads); +} + +K_THREAD_DEFINE(thread_pwm_id, PWM_THREAD_STACKSIZE, pwm_thread, NULL, NULL, NULL, + K_PRIO_PREEMPT(PWM_THREAD_PRIORITY), 0, 0); diff --git a/tests/benchmarks/peripheral_load/src/temp_thread.c b/tests/benchmarks/peripheral_load/src/temp_thread.c new file mode 100644 index 000000000000..a7f565f025b0 --- /dev/null +++ b/tests/benchmarks/peripheral_load/src/temp_thread.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +LOG_MODULE_REGISTER(temp, LOG_LEVEL_INF); + +#include +#include "common.h" + +static const struct device *temp_dev = DEVICE_DT_GET(DT_NODELABEL(temp_sensor)); +static enum sensor_channel chan_to_use = SENSOR_CHAN_DIE_TEMP; + +/* TEMP thread */ +static void temp_thread(void *arg1, void *arg2, void *arg3) +{ + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + int ret; + struct sensor_value val; + int32_t temp_val; + + atomic_inc(&started_threads); + + if (!device_is_ready(temp_dev)) { + LOG_ERR("Device %s is not ready.", temp_dev->name); + atomic_inc(&completed_threads); + return; + } + + for (int i = 0; i < TEMP_THREAD_COUNT_MAX; i++) { + /* Capture TEMP sample */ + ret = sensor_sample_fetch_chan(temp_dev, chan_to_use); + if (ret < 0) { + LOG_ERR("sensor_sample_fetch_chan() returned: %d", ret); + atomic_inc(&completed_threads); + return; + } + + /* Retrieve TEMP sample */ + ret = sensor_channel_get(temp_dev, chan_to_use, &val); + if (ret < 0) { + LOG_ERR("sensor_channel_get() returned: %d", ret); + atomic_inc(&completed_threads); + return; + } + temp_val = (val.val1 * 100) + (val.val2 / 10000); + + LOG_INF("DIE_TEMP is %d.%02u", temp_val / 100, abs(temp_val) % 100); + + k_msleep(TEMP_THREAD_PERIOD); + } + + LOG_INF("TEMP thread has completed"); + + atomic_inc(&completed_threads); +} + +K_THREAD_DEFINE(thread_temp_id, TEMP_THREAD_STACKSIZE, temp_thread, NULL, NULL, NULL, + K_PRIO_PREEMPT(TEMP_THREAD_PRIORITY), 0, 0); diff --git a/tests/benchmarks/peripheral_load/src/timer_thread.c b/tests/benchmarks/peripheral_load/src/timer_thread.c new file mode 100644 index 000000000000..41145b313e72 --- /dev/null +++ b/tests/benchmarks/peripheral_load/src/timer_thread.c @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +LOG_MODULE_REGISTER(timer, LOG_LEVEL_INF); + +#include "common.h" + +static struct k_timer my_timer; +static int timer_expire_count; + +void my_work_handler(struct k_work *work) +{ + timer_expire_count++; + LOG_INF("Timer expired %d times", timer_expire_count); +} + +K_WORK_DEFINE(my_work, my_work_handler); + +void my_timer_handler(struct k_timer *dummy) +{ + k_work_submit(&my_work); +} + +/* Timer thread */ +static void timer_thread(void *arg1, void *arg2, void *arg3) +{ + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + atomic_inc(&started_threads); + + k_timer_init(&my_timer, my_timer_handler, NULL); + + /* start a periodic timer that expires once every TIMER_THREAD_PERIOD */ + k_timer_start(&my_timer, K_MSEC(TIMER_THREAD_PERIOD), K_MSEC(TIMER_THREAD_PERIOD)); + + while (timer_expire_count < TIMER_THREAD_COUNT_MAX) { + /* Wait for condition to be meet */ + k_msleep(100); + } + k_timer_stop(&my_timer); + + LOG_INF("Timer thread has completed"); + + atomic_inc(&completed_threads); +} + +K_THREAD_DEFINE(thread_timer_id, TIMER_THREAD_STACKSIZE, timer_thread, NULL, NULL, NULL, + K_PRIO_PREEMPT(TIMER_THREAD_PRIORITY), 0, 0); diff --git a/tests/benchmarks/peripheral_load/src/wdt_thread.c b/tests/benchmarks/peripheral_load/src/wdt_thread.c new file mode 100644 index 000000000000..7c439b47f23b --- /dev/null +++ b/tests/benchmarks/peripheral_load/src/wdt_thread.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +LOG_MODULE_REGISTER(wdt, LOG_LEVEL_INF); + +#include +#include "common.h" + + +static const struct device *const my_wdt_device = DEVICE_DT_GET(DT_ALIAS(watchdog0)); +static struct wdt_timeout_cfg m_cfg_wdt0; +static int my_wdt_channel; + +#if DT_NODE_HAS_STATUS(DT_CHOSEN(zephyr_dtcm), okay) +#define NOINIT_SECTION ".dtcm_noinit.test_wdt" +#else +#define NOINIT_SECTION ".noinit.test_wdt" +#endif +volatile uint32_t wdt_status __attribute__((section(NOINIT_SECTION))); + +static void wdt_int_cb(const struct device *wdt_dev, int channel_id) +{ + ARG_UNUSED(wdt_dev); + ARG_UNUSED(channel_id); + wdt_status = WDT_TAG; +} + + +/* WDT thread */ +static void wdt_thread(void *arg1, void *arg2, void *arg3) +{ + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + int ret; + + atomic_inc(&started_threads); + + if (!device_is_ready(my_wdt_device)) { + LOG_ERR("WDT device %s is not ready", my_wdt_device->name); + atomic_inc(&completed_threads); + return; + } + + /* Configure Watchdog */ + m_cfg_wdt0.callback = wdt_int_cb; + m_cfg_wdt0.flags = WDT_FLAG_RESET_SOC; + m_cfg_wdt0.window.max = WDT_WINDOW_MAX; + m_cfg_wdt0.window.min = 0U; + my_wdt_channel = wdt_install_timeout(my_wdt_device, &m_cfg_wdt0); + if (my_wdt_channel < 0) { + LOG_ERR("wdt_install_timeout() returned %d", my_wdt_channel); + atomic_inc(&completed_threads); + return; + } + + ret = wdt_setup(my_wdt_device, WDT_OPT_PAUSE_HALTED_BY_DBG); + if (ret < 0) { + LOG_ERR("wdt_setup() returned %d", ret); + atomic_inc(&completed_threads); + return; + } + + for (int i = 0; i < WDT_THREAD_COUNT_MAX; i++) { + + /* Feed watchdog */ + ret = wdt_feed(my_wdt_device, my_wdt_channel); + if (ret < 0) { + LOG_ERR("wdt_feed() returned: %d", ret); + /* Stop watchdog */ + wdt_disable(my_wdt_device); + return; + } + + LOG_INF("WDT was feed"); + k_msleep(WDT_THREAD_PERIOD); + } + + /* Stop watchdog */ + wdt_disable(my_wdt_device); + + LOG_INF("WDT thread has completed"); + + atomic_inc(&completed_threads); +} + +K_THREAD_DEFINE(thread_wdt_id, WDT_THREAD_STACKSIZE, wdt_thread, NULL, NULL, NULL, + K_PRIO_PREEMPT(WDT_THREAD_PRIORITY), 0, 0);