diff --git a/app/backplane/power_module/.idea/editor.xml b/app/backplane/power_module/.idea/editor.xml
index e42167fe8..ec902243e 100644
--- a/app/backplane/power_module/.idea/editor.xml
+++ b/app/backplane/power_module/.idea/editor.xml
@@ -243,18 +243,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/backplane/sensor_module/.idea/csv-editor.xml b/app/backplane/sensor_module/.idea/csv-editor.xml
new file mode 100644
index 000000000..f24169574
--- /dev/null
+++ b/app/backplane/sensor_module/.idea/csv-editor.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/backplane/sensor_module/.idea/editor.xml b/app/backplane/sensor_module/.idea/editor.xml
index e42167fe8..ec902243e 100644
--- a/app/backplane/sensor_module/.idea/editor.xml
+++ b/app/backplane/sensor_module/.idea/editor.xml
@@ -243,18 +243,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/backplane/sensor_module/src/c_sensor_module.cpp b/app/backplane/sensor_module/src/c_sensor_module.cpp
index 40cb8a7c2..0279e9e00 100644
--- a/app/backplane/sensor_module/src/c_sensor_module.cpp
+++ b/app/backplane/sensor_module/src/c_sensor_module.cpp
@@ -21,7 +21,8 @@ static auto alertMsgQueue = CMsgqMessagePort(alertQueue);
CSensorModule::CSensorModule()
: CProjectConfiguration(), sensorDataBroadcastMessagePort(broadcastMsgQueue), downlinkMessagePort(downlinkMsgQueue),
- sensorDataLogMessagePort(dataLogMsgQueue), alertMessagePort(alertMsgQueue), flight_log{"/lfs/flight_log.txt"} {}
+ sensorDataLogMessagePort(dataLogMsgQueue),
+ alertMessagePort(alertMsgQueue), flight_log{"/lfs/flight_log.txt"} {}
void CSensorModule::AddTenantsToTasks() {
// Networking
diff --git a/app/samples/.template-project/prj.conf b/app/samples/.template-project/prj.conf
index 7f839fd57..f000b3bf8 100644
--- a/app/samples/.template-project/prj.conf
+++ b/app/samples/.template-project/prj.conf
@@ -6,13 +6,10 @@ CONFIG_F_CORE=y
CONFIG_F_CORE_OS=y
# outputs
-CONFIG_SERIAL=y
-CONFIG_FILE_SYSTEM_SHELL=y
CONFIG_HEAP_MEM_POOL_SIZE=8192
CONFIG_DEBUG=y
CONFIG_CBPRINTF_FP_SUPPORT=y
-CONFIG_LZ4=y
CONFIG_MAIN_STACK_SIZE=4096
diff --git a/app/samples/cpu_monitor/.idea/.gitignore b/app/samples/cpu_monitor/.idea/.gitignore
new file mode 100644
index 000000000..8bf4d45d6
--- /dev/null
+++ b/app/samples/cpu_monitor/.idea/.gitignore
@@ -0,0 +1,6 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/app/samples/cpu_monitor/CMakeLists.txt b/app/samples/cpu_monitor/CMakeLists.txt
new file mode 100644
index 000000000..4ee9625ca
--- /dev/null
+++ b/app/samples/cpu_monitor/CMakeLists.txt
@@ -0,0 +1,16 @@
+cmake_minimum_required(VERSION 3.20.0)
+
+set(FSW_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../..)
+include(${FSW_ROOT}/cmake/Autocoders.cmake)
+
+list(APPEND CONF_FILE ${CMAKE_CURRENT_SOURCE_DIR}/prj.conf)
+
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(cpu-monitor LANGUAGES CXX)
+
+target_compile_options(app PRIVATE -Wall -Wextra -Wno-unused-parameter -Wno-unused-function -Wno-ignored-qualifiers -Wno-missing-field-initializers)
+FILE(GLOB app_sources src/*.cpp)
+target_sources(app PRIVATE ${app_sources})
+
+AutocodeTypes()
+add_dependencies(app ac_types)
\ No newline at end of file
diff --git a/app/samples/cpu_monitor/Kconfig b/app/samples/cpu_monitor/Kconfig
new file mode 100644
index 000000000..5ba198ca5
--- /dev/null
+++ b/app/samples/cpu_monitor/Kconfig
@@ -0,0 +1 @@
+source "Kconfig.zephyr"
diff --git a/app/samples/cpu_monitor/boards/nucleo_f446re.overlay b/app/samples/cpu_monitor/boards/nucleo_f446re.overlay
new file mode 100644
index 000000000..cbd35d7a5
--- /dev/null
+++ b/app/samples/cpu_monitor/boards/nucleo_f446re.overlay
@@ -0,0 +1,16 @@
+/ {
+ aliases {
+ die-temp = &die_temp;
+ };
+};
+
+&die_temp {
+ status = "okay";
+};
+
+&adc1 {
+ pinctrl-0 = <&adc1_in0_pa0>;
+ pinctrl-names = "default";
+ st,adc-prescaler = <4>;
+ status = "okay";
+};
diff --git a/app/samples/cpu_monitor/prj.conf b/app/samples/cpu_monitor/prj.conf
new file mode 100644
index 000000000..7eb66af8b
--- /dev/null
+++ b/app/samples/cpu_monitor/prj.conf
@@ -0,0 +1,26 @@
+CONFIG_CPP=y
+CONFIG_REQUIRES_FULL_LIBCPP=y
+CONFIG_STD_CPP20=y
+
+CONFIG_F_CORE=y
+CONFIG_F_CORE_OS=y
+
+CONFIG_SERIAL=y
+CONFIG_FILE_SYSTEM_SHELL=y
+CONFIG_HEAP_MEM_POOL_SIZE=8192
+
+CONFIG_DEBUG=y
+CONFIG_CBPRINTF_FP_SUPPORT=y
+
+CONFIG_MAIN_STACK_SIZE=4096
+
+# CPU Utilization Support
+CONFIG_THREAD_RUNTIME_STATS=y
+CONFIG_SCHED_THREAD_USAGE=y
+
+# Die Temperature Support
+# .conf overlay didnt work :(
+CONFIG_ADC=y
+CONFIG_SENSOR=y
+CONFIG_STM32_TEMP=y
+
diff --git a/app/samples/cpu_monitor/sample.yaml b/app/samples/cpu_monitor/sample.yaml
new file mode 100644
index 000000000..e5b3dbd5e
--- /dev/null
+++ b/app/samples/cpu_monitor/sample.yaml
@@ -0,0 +1,9 @@
+sample:
+ description: Sample application monitoring CPU utilization, uptime and die-temp.
+ name: cpu-monitor
+common:
+ build_only: true
+ platform_allow:
+ - nucleo_f446re
+tests:
+ samples.template.default: {}
diff --git a/app/samples/cpu_monitor/src/main.cpp b/app/samples/cpu_monitor/src/main.cpp
new file mode 100644
index 000000000..1f1892856
--- /dev/null
+++ b/app/samples/cpu_monitor/src/main.cpp
@@ -0,0 +1,24 @@
+#include
+#include
+#include
+#include
+
+K_MSGQ_DEFINE(cpuMonitorQueue, sizeof(CpuMonitorData), 1, 4);
+
+int main() {
+ auto cpuMonitorMsgQueue = CMsgqMessagePort(cpuMonitorQueue);
+ CCpuMonitorTenant cpuMonitorTenant(cpuMonitorMsgQueue);
+
+ cpuMonitorTenant.Startup();
+
+ while (true) {
+ CpuMonitorData cpuMonitorData{0, 0, 0};
+ cpuMonitorTenant.Run();
+ cpuMonitorMsgQueue.Receive(cpuMonitorData, K_FOREVER);
+ printk("Uptime: %u ms, Utilization: %u%%, Die Temperature: %d °C\n",
+ cpuMonitorData.Uptime, cpuMonitorData.Utilization, cpuMonitorData.DieTemperature);
+ k_msleep(1000);
+ }
+
+ return 0;
+}
diff --git a/boards/arm/deployment_module/deployment_module.dts b/boards/arm/deployment_module/deployment_module.dts
index a7af0b42c..db84cada5 100644
--- a/boards/arm/deployment_module/deployment_module.dts
+++ b/boards/arm/deployment_module/deployment_module.dts
@@ -59,6 +59,8 @@
pyro-ctrl-1 = &pyro_ctrl_1;
pyro-ctrl-2 = &pyro_ctrl_2;
pyro-ctrl-3 = &pyro_ctrl_3;
+
+ die-temp = &die_temp;
};
leds: leds {
@@ -251,6 +253,18 @@
status = "okay";
};
+&adc1 {
+ pinctrl-0 = <&adc1_in0_pa0>;
+ pinctrl-names = "default";
+ st,adc-prescaler = <4>;
+ status = "okay";
+};
+
+
+&die_temp {
+ status = "okay";
+};
+
&flash0 {
partitions {
compatible = "fixed-partitions";
diff --git a/boards/arm/deployment_module/deployment_module_defconfig b/boards/arm/deployment_module/deployment_module_defconfig
index 2ff101b57..0abb9a431 100644
--- a/boards/arm/deployment_module/deployment_module_defconfig
+++ b/boards/arm/deployment_module/deployment_module_defconfig
@@ -9,6 +9,7 @@ CONFIG_ENTROPY_STM32_RNG=y
CONFIG_ENTROPY_DEVICE_RANDOM_GENERATOR=y
# IO
+CONFIG_ADC=y
CONFIG_GPIO=y
CONFIG_I2C=y
CONFIG_PINCTRL=y
@@ -16,4 +17,8 @@ CONFIG_RTC=y
CONFIG_SERIAL=y
CONFIG_SPI=y
CONFIG_SPI_ASYNC=y
-CONFIG_LED=y
\ No newline at end of file
+CONFIG_LED=y
+
+# Sensors
+CONFIG_SENSOR=y
+CONFIG_STM32_TEMP=y
\ No newline at end of file
diff --git a/boards/arm/power_module/power_module.dts b/boards/arm/power_module/power_module.dts
index eb099e447..857e0531b 100644
--- a/boards/arm/power_module/power_module.dts
+++ b/boards/arm/power_module/power_module.dts
@@ -19,14 +19,17 @@
storage = &w25q128;
logfs = &lfs1;
+ rtc = &rtc;
};
aliases {
inabatt = &ina0;
ina3v3 = &ina1;
ina5v0 = &ina2;
+
vin = &adc1;
rtc = &rtc;
+ die-temp = &die_temp;
};
leds: leds {
@@ -214,6 +217,17 @@
status = "okay";
};
+&adc1 {
+ pinctrl-0 = <&adc1_in0_pa0>;
+ pinctrl-names = "default";
+ st,adc-prescaler = <4>;
+ status = "okay";
+};
+
+&die_temp {
+ status = "okay";
+};
+
&flash0 {
partitions {
diff --git a/boards/arm/power_module/power_module_defconfig b/boards/arm/power_module/power_module_defconfig
index c92e299d3..5fba2fd20 100644
--- a/boards/arm/power_module/power_module_defconfig
+++ b/boards/arm/power_module/power_module_defconfig
@@ -9,6 +9,7 @@ CONFIG_ENTROPY_STM32_RNG=y
CONFIG_ENTROPY_DEVICE_RANDOM_GENERATOR=y
# IO
+CONFIG_ADC=y
CONFIG_GPIO=y
CONFIG_I2C=y
#CONFIG_I2C_CALLBACK=y
@@ -26,4 +27,5 @@ CONFIG_LED=y
CONFIG_SENSOR=y
CONFIG_SENSOR_ASYNC_API=y
CONFIG_SENSOR_INFO=y
-CONFIG_INA219=y
\ No newline at end of file
+CONFIG_INA219=y
+CONFIG_STM32_TEMP=y
diff --git a/boards/arm/radio_module/radio_module.dts b/boards/arm/radio_module/radio_module.dts
index 5611ef3f6..4462faaca 100644
--- a/boards/arm/radio_module/radio_module.dts
+++ b/boards/arm/radio_module/radio_module.dts
@@ -23,6 +23,7 @@
storage = &w25q128;
logfs = &lfs1;
+ rtc = &rtc;
};
fstab {
@@ -94,6 +95,8 @@
lora = &lora0;
gnss = &maxm10s;
+
+ rtc = &rtc;
};
};
@@ -241,6 +244,17 @@
status = "okay";
};
+&adc1 {
+ pinctrl-0 = <&adc1_in0_pa0>;
+ pinctrl-names = "default";
+ st,adc-prescaler = <4>;
+ status = "okay";
+};
+
+&die_temp {
+ status = "okay";
+};
+
&flash0 {
partitions {
compatible = "fixed-partitions";
diff --git a/boards/arm/radio_module/radio_module_defconfig b/boards/arm/radio_module/radio_module_defconfig
index 6f042b340..144324de2 100644
--- a/boards/arm/radio_module/radio_module_defconfig
+++ b/boards/arm/radio_module/radio_module_defconfig
@@ -8,6 +8,7 @@ CONFIG_FPU=y
CONFIG_HW_STACK_PROTECTION=y
# IO
+CONFIG_ADC=y
CONFIG_GPIO=y
CONFIG_I2C=y
#CONFIG_I2C_CALLBACK=y
@@ -26,3 +27,7 @@ CONFIG_POSIX_MAX_FDS=10
# Devices
CONFIG_LORA=y
CONFIG_LORA_SX127X=y
+
+# Sensors
+CONFIG_SENSOR=y
+CONFIG_STM32_TEMP=y
diff --git a/boards/arm/sensor_module/sensor_module.dts b/boards/arm/sensor_module/sensor_module.dts
index 4ec1ca545..c6229e58f 100644
--- a/boards/arm/sensor_module/sensor_module.dts
+++ b/boards/arm/sensor_module/sensor_module.dts
@@ -19,11 +19,13 @@
logfs = &lfs1;
rtc = &rtc;
+ die-temp = &die_temp;
};
aliases {
led0 = &red_led_1;
led1 = &red_led_2;
+ die-temp = &die_temp;
};
leds: leds {
@@ -248,6 +250,17 @@
status = "okay";
};
+&adc1 {
+ pinctrl-0 = <&adc1_in0_pa0>;
+ pinctrl-names = "default";
+ st,adc-prescaler = <4>;
+ status = "okay";
+};
+
+&die_temp {
+ status = "okay";
+};
+
&flash0 {
partitions {
compatible = "fixed-partitions";
diff --git a/boards/arm/sensor_module/sensor_module_defconfig b/boards/arm/sensor_module/sensor_module_defconfig
index c7b41cdce..92bc335fb 100644
--- a/boards/arm/sensor_module/sensor_module_defconfig
+++ b/boards/arm/sensor_module/sensor_module_defconfig
@@ -27,6 +27,7 @@ CONFIG_SPI_NOR_SLEEP_WHILE_WAITING_UNTIL_READY=y
CONFIG_LED=y
# Sensors
+CONFIG_ADC=y
CONFIG_SENSOR=y
CONFIG_SENSOR_ASYNC_API=y
CONFIG_SENSOR_INFO=y
@@ -34,4 +35,5 @@ CONFIG_TMP116=y
CONFIG_BMP388=y
CONFIG_LSM6DSL=y
CONFIG_ADXL375=y
-CONFIG_MS5611=y
\ No newline at end of file
+CONFIG_MS5611=y
+CONFIG_STM32_TEMP=y
\ No newline at end of file
diff --git a/data/autocoder_inputs/types.yaml b/data/autocoder_inputs/types.yaml
index fc64cb29d..bba5e7eac 100644
--- a/data/autocoder_inputs/types.yaml
+++ b/data/autocoder_inputs/types.yaml
@@ -220,4 +220,4 @@ LoRaReceiveStatistics:
- name: ReceivedSignalStrengthIndicator
type: int16_t
- name: SignalToNoiseRatio
- type: int8_t
+ type: int8_t
\ No newline at end of file
diff --git a/include/f_core/os/tenants/c_cpu_monitor_tenant.h b/include/f_core/os/tenants/c_cpu_monitor_tenant.h
new file mode 100644
index 000000000..91d36332b
--- /dev/null
+++ b/include/f_core/os/tenants/c_cpu_monitor_tenant.h
@@ -0,0 +1,46 @@
+#ifndef C_CPU_MONITOR_TENANT_H
+#define C_CPU_MONITOR_TENANT_H
+
+#include "f_core/messaging/c_message_port.h"
+#include "f_core/os/c_tenant.h"
+
+#include
+
+struct __attribute__((packed)) CpuMonitorData {
+ uint32_t Uptime;
+ int32_t DieTemperature;
+ uint8_t Utilization;
+};
+
+class CCpuMonitorTenant : public CTenant {
+public:
+ CCpuMonitorTenant(CMessagePort& outputPort) :
+ CTenant("CPU Monitor Tenant"), outputPort(outputPort) {
+ };
+
+ void Startup() override;
+
+ void PostStartup() override;
+
+ void Run() override;
+
+ void Cleanup() override;
+
+private:
+ CMessagePort& outputPort;
+ uint64_t prevExecutionCycles = 0; // All cycles (active + idle) - Based on Zephyr naming
+ uint64_t prevTotalCycles = 0; // Active cycles only - Based on Zephyr naming
+ uint32_t prevUptime = 0;
+
+#if DT_NODE_EXISTS(DT_ALIAS(die_temp))
+ const device *dieTempSensor = DEVICE_DT_GET(DT_ALIAS(die_temp));
+#endif
+
+ uint8_t getUtilization();
+
+ uint32_t getUptime();
+
+ int32_t getDieTemperature();
+};
+
+#endif //C_CPU_MONITOR_TENANT_H
diff --git a/lib/f_core/Kconfig b/lib/f_core/Kconfig
index 817a97786..9cda21859 100644
--- a/lib/f_core/Kconfig
+++ b/lib/f_core/Kconfig
@@ -35,6 +35,8 @@ config F_CORE_OS
bool "OS"
help
This option enables OS functionality for F-Core
+ select THREAD_RUNTIME_STATS
+ select SCHED_THREAD_USAGE
config F_CORE_UTILS
bool "Utility"
diff --git a/lib/f_core/os/CMakeLists.txt b/lib/f_core/os/CMakeLists.txt
index 7ef6af7b3..4efe81815 100644
--- a/lib/f_core/os/CMakeLists.txt
+++ b/lib/f_core/os/CMakeLists.txt
@@ -2,6 +2,6 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
-FILE(GLOB sources *.cpp)
+FILE(GLOB sources *.cpp tenants/*.cpp)
zephyr_library_sources(${sources})
diff --git a/lib/f_core/os/tenants/c_cpu_monitor_tenant.cpp b/lib/f_core/os/tenants/c_cpu_monitor_tenant.cpp
new file mode 100644
index 000000000..1f72f5ac3
--- /dev/null
+++ b/lib/f_core/os/tenants/c_cpu_monitor_tenant.cpp
@@ -0,0 +1,86 @@
+#include "f_core/os/tenants/c_cpu_monitor_tenant.h"
+
+#include "zephyr/logging/log.h"
+
+#include
+
+LOG_MODULE_REGISTER(CCpuMonitorTenant);
+
+void CCpuMonitorTenant::Startup() {
+ k_thread_runtime_stats stats{0};
+ k_thread_runtime_stats_all_get(&stats);
+
+ prevExecutionCycles = stats.execution_cycles;
+ prevTotalCycles = stats.total_cycles;
+ prevUptime = k_uptime_get_32();
+}
+
+void CCpuMonitorTenant::PostStartup() {
+}
+
+void CCpuMonitorTenant::Run() {
+ CpuMonitorData cpuMonitorData{0};
+
+ cpuMonitorData.Uptime = getUptime();
+ cpuMonitorData.Utilization = getUtilization();
+ cpuMonitorData.DieTemperature = getDieTemperature();
+
+ if (outputPort.Send(cpuMonitorData, K_NO_WAIT) != 0) {
+ outputPort.Clear();
+ }
+}
+
+void CCpuMonitorTenant::Cleanup() {
+}
+
+int32_t CCpuMonitorTenant::getDieTemperature() {
+ int32_t result = 0;
+#if DT_NODE_EXISTS(DT_ALIAS(die_temp))
+ if (device_is_ready(dieTempSensor)) {
+ sensor_value dieTempValue{0};
+ if (sensor_sample_fetch(dieTempSensor) == 0 &&
+ sensor_channel_get(dieTempSensor, SENSOR_CHAN_DIE_TEMP, &dieTempValue) == 0) {
+ result = dieTempValue.val1;
+ }
+ }
+#else
+ LOG_WRN_ONCE("No die temperature sensor available. Defaulting to 0 °C.");
+#endif
+ return result;
+}
+
+uint32_t CCpuMonitorTenant::getUptime() {
+ return k_uptime_get_32();
+}
+
+uint8_t CCpuMonitorTenant::getUtilization() {
+ k_thread_runtime_stats stats{0};
+ k_thread_runtime_stats_all_get(&stats);
+ uint32_t currentUptime = k_uptime_get_32();
+
+ // Zephyr's naming is confusing! Based on kernel/thread.h comments:
+ // execution_cycles = total # of cycles (cpu: non-idle + idle) = ALL cycles
+ // total_cycles = total # of non-idle cycles = ACTIVE/BUSY cycles only
+ uint64_t deltaAllCycles = stats.execution_cycles - prevExecutionCycles; // All cycles (active + idle)
+ uint64_t deltaActiveCycles = stats.total_cycles - prevTotalCycles; // Active cycles only
+ uint32_t deltaTime = currentUptime - prevUptime;
+
+ prevExecutionCycles = stats.execution_cycles;
+ prevTotalCycles = stats.total_cycles;
+ prevUptime = currentUptime;
+
+ if (deltaAllCycles == 0 || deltaTime == 0) {
+ return 0; // Avoid division by zero
+ }
+
+ if (deltaTime < 10) {
+ LOG_WRN_ONCE("CPU utilization measurement interval too short for accuracy");
+ }
+
+ // CPU Utilization = (Active cycles / All cycles) × 100
+ // gives the percentage of time the CPU is NOT idle
+ uint64_t utilization = (deltaActiveCycles * 100) / deltaAllCycles;
+
+ // Clamp to 100% in case of any edge cases
+ return static_cast(utilization > 100 ? 100 : utilization);
+}