10 Commits

Author SHA1 Message Date
84e7d02db8 defunct: playing around with bootloaders 2025-07-08 14:11:22 +02:00
cc6b4488ee Fix MCUboot and app flash partitioning
- Corrected device tree overlays to prevent MCUboot and app overlap
- MCUboot now at 0x8000000 (32KB), app at 0x8008000 (96KB)
- Successfully boots MCUboot which chains to application
- Shell and reset command working properly
- Black Magic Probe flashing confirmed working for both domains
2025-07-07 16:04:29 +02:00
928a176e7c Step 2 complete: MCUboot integration with single-slot configuration
- Created sysbuild configuration for MCUboot bootloader
- Added device tree overlays for correct partition layout (32KB MCUboot, 96KB app)
- Fixed MCUboot partition addressing to use boot_partition instead of slot0_partition
- MCUboot successfully boots and chains to application
- Application runs with shell and custom reset command
- Disabled signature validation for testing purposes
2025-07-07 15:59:41 +02:00
8255b2a672 Add firmware_node app - Step 1: Shell with reset command
- Create new Zephyr app for firmware management
- Target: weact_stm32g431_core board
- Features: Shell interface with custom reset command
- Tested on Zephyr 4.1.99
- Flash usage: 55,400 bytes (42.27% of 128KB)
- RAM usage: 12,864 bytes (39.26% of 32KB)
- Black Magic Probe flash runner support
2025-07-07 13:49:03 +02:00
d48281436e Fix ADC devicetree compilation error for voltage divider
- Fix voltage divider devicetree configuration to reference ADC controller directly instead of channel node
- Switch from ADC API to sensor API for voltage divider usage
- Add required sensor and voltage divider configuration options
- Remove unnecessary zephyr,user node that was causing compilation issues
- The voltage divider now properly uses sensor framework and builds successfully

Hardware setup:
- Uses ADC1 channel 1 on pin PA0
- Voltage divider with 2.2kΩ output and 3.2kΩ total resistance
- Provides voltage readings through sensor API accounting for divider ratio
2025-07-07 13:36:44 +02:00
dcb73c0a25 Working DT ADC sample WITHOUT resistor divider 2025-07-07 12:32:53 +02:00
2c21f1f9cb feat: Revert ADC changes and set channel 12 for VREFINT 2025-07-06 15:27:14 +02:00
a2afec52e2 fix(adc_test): correct ADC reference configuration
- Revert ADC_GAIN to ADC_GAIN_1 due to driver limitation.
- Revert ADC_REF to ADC_REF_INTERNAL due to driver limitation.
- Set zephyr,vref-mv to 3300 to match observed hardware behavior.
2025-07-06 10:24:37 +02:00
2cc258e8e2 feat(adc_test): use devicetree for adc configuration
- Use named adc channel 'multisense' from devicetree
- Enable adc calibration
2025-07-06 09:55:10 +02:00
a77298b3a6 Implement VND7050AJ supply voltage reading function
- Added devicetree overlay for VND7050AJ with GPIO and ADC configuration
- Created custom devicetree binding for VND7050AJ valve controller
- Implemented valve_get_supply_voltage() function with proper pin control:
  - RST=HIGH to enable VND7050AJ
  - S0=1, S1=1 for supply voltage sensing mode
  - SEN=1 to enable MULTISENSE output
  - ADC reading on PA0 (ADC1_IN1) with 12-bit resolution
- Fixed supply voltage calculation (VCC/8 per datasheet)
- Added comprehensive debug logging for all steps
- Tested and verified ADC functionality
- Current reading: 5.1V (may be limited by hardware power supply)

Files modified:
- software/lib/valve/valve.c: Main implementation
- software/apps/slave_node/boards/weact_stm32g431_core.overlay: DT config
- software/apps/slave_node/dts/bindings/vnd7050aj-valve-controller.yaml: DT binding
- software/apps/slave_node/src/main.c: Test code
- software/apps/slave_node/prj.conf: ADC driver enablement
2025-07-04 08:54:47 +02:00
70 changed files with 1937 additions and 1535 deletions

View File

@@ -1,56 +0,0 @@
#!/bin/sh
# This script sets up a Git pre-commit hook to automatically format C/C++ files
# in the 'software/' subdirectory using clang-format.
# Define the path for the pre-commit hook
HOOK_DIR=".git/hooks"
HOOK_FILE="$HOOK_DIR/pre-commit"
# Create the hooks directory if it doesn't exist
mkdir -p "$HOOK_DIR"
# Create the pre-commit hook script using a 'here document'
cat > "$HOOK_FILE" << 'EOF'
#!/bin/sh
# --- Pre-commit hook for clang-format ---
#
# This hook formats staged C, C++, and Objective-C files in the 'software/'
# subdirectory before a commit is made.
# It automatically finds the .clang-format file in the software/ directory.
#
# Directory to be formatted
TARGET_DIR="software/"
# Use git diff to find staged files that are Added (A), Copied (C), or Modified (M).
# We filter for files only within the TARGET_DIR.
# The grep regex matches common C/C++ and Objective-C file extensions.
FILES_TO_FORMAT=$(git diff --cached --name-only --diff-filter=ACM "$TARGET_DIR" | grep -E '\.(c|h|cpp|hpp|cxx|hxx|cc|hh|m|mm)$')
if [ -z "$FILES_TO_FORMAT" ]; then
# No relevant files to format, exit successfully.
exit 0
fi
echo " Running clang-format on staged files in '$TARGET_DIR'..."
# Run clang-format in-place on the identified files.
# clang-format will automatically find the .clang-format file in the software/ directory
# or any of its parent directories.
echo "$FILES_TO_FORMAT" | xargs clang-format -i
# Since clang-format may have changed the files, we need to re-stage them.
echo "$FILES_TO_FORMAT" | xargs git add
echo " Formatting complete."
exit 0
EOF
# Make the hook executable
chmod +x "$HOOK_FILE"
echo "✅ Git pre-commit hook has been set up successfully."
echo " It will now automatically format files in the '$PWD/software' directory before each commit."

View File

@@ -1,5 +0,0 @@
# .clang-format
BasedOnStyle: Google
#IndentWidth: 4
#ColumnLimit: 100
#AllowShortFunctionsOnASingleLine: None

View File

@@ -1,15 +1,17 @@
{
// Hush CMake
"cmake.configureOnOpen": false,
// IntelliSense
"C_Cpp.default.compilerPath": "${userHome}/zephyr-sdk-0.17.1/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc.exe",
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json",
// File Associations
"files.associations": {
"app_version.h": "c"
},
"C_Cpp.clang_format_style": "file",
"nrf-connect.applications": [
"${workspaceFolder}/apps/slave_node"
],
// Hush CMake
"cmake.configureOnOpen": false,
// IntelliSense
"C_Cpp.default.compilerPath": "${userHome}/zephyr-sdk-0.17.1/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc.exe",
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json",
// File Associations
"files.associations": {
"array": "c",
"string_view": "c",
"initializer_list": "c",
"span": "c",
"format": "c"
}
}

View File

@@ -2,20 +2,32 @@
"version": "2.0.0",
"tasks": [
{
"label": "Format All C/C++ Files",
"type": "shell",
"command": "find . -name \"*.c\" -o -name \"*.h\" | xargs clang-format -i",
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "silent",
"clear": true,
"panel": "shared"
}
},
"label": "West Build",
"type": "shell",
"group": {
"kind": "build",
"isDefault": true
},
"linux": {
"command": "${userHome}/zephyrproject/.venv/bin/west"
},
"windows": {
"command": "${userHome}/zephyrproject/.venv/Scripts/west.exe"
},
"osx": {
"command": "${userHome}/zephyrproject/.venv/bin/west"
},
"args": [
"build",
"-p",
"auto",
"-b",
"valve_node"
],
"problemMatcher": [
"$gcc"
]
},
{
"label": "West Configurable Build",
"type": "shell",

View File

@@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(ADC)
target_sources(app PRIVATE src/main.c)

View File

@@ -0,0 +1,62 @@
.. zephyr:code-sample:: adc_dt
:name: Analog-to-Digital Converter (ADC) with devicetree
:relevant-api: adc_interface
Read analog inputs from ADC channels.
Overview
********
This sample demonstrates how to use the :ref:`ADC driver API <adc_api>`.
Depending on the target board, it reads ADC samples from one or more channels
and prints the readings on the console. If voltage of the used reference can
be obtained, the raw readings are converted to millivolts.
The pins of the ADC channels are board-specific. Please refer to the board
or MCU datasheet for further details.
Building and Running
********************
The ADC peripheral and pinmux is configured in the board's ``.dts`` file. Make
sure that the ADC is enabled (``status = "okay";``).
In addition to that, this sample requires an ADC channel specified in the
``io-channels`` property of the ``zephyr,user`` node. This is usually done with
a devicetree overlay. The example overlay in the ``boards`` subdirectory for
the ``nucleo_l073rz`` board can be easily adjusted for other boards.
Configuration of channels (settings like gain, reference, or acquisition time)
also needs to be specified in devicetree, in ADC controller child nodes. Also
the ADC resolution and oversampling setting (if used) need to be specified
there. See :zephyr_file:`boards/nrf52840dk_nrf52840.overlay
<samples/drivers/adc/adc_dt/boards/nrf52840dk_nrf52840.overlay>` for an example of
such setup.
Building and Running for ST Nucleo L073RZ
=========================================
The sample can be built and executed for the
:zephyr:board:`nucleo_l073rz` as follows:
.. zephyr-app-commands::
:zephyr-app: samples/drivers/adc/adc_dt
:board: nucleo_l073rz
:goals: build flash
:compact:
To build for another board, change "nucleo_l073rz" above to that board's name
and provide a corresponding devicetree overlay.
Sample output
=============
You should get a similar output as below, repeated every second:
.. code-block:: console
ADC reading:
- ADC_0, channel 7: 36 = 65mV
.. note:: If the ADC is not supported, the output will be an error message.

View File

@@ -0,0 +1,38 @@
/ {
vdd_sense: voltage-divider {
compatible = "voltage-divider";
/*
* This reference must provide one argument (the channel number)
* because of the "#io-channel-cells = <1>" in the &adc1 node.
*/
io-channels = <&adc1 1>;
output-ohms = <2200>;
full-ohms = <3200>;
};
};
&adc1 {
status = "okay";
pinctrl-0 = <&adc1_in1_pa0>;
pinctrl-names = "default";
st,adc-clock-source = "SYNC";
st,adc-prescaler = <4>;
#address-cells = <1>;
#size-cells = <0>;
/*
* This line is required by the st,stm32-adc driver binding.
* It declares that references to its channels need one extra argument.
*/
#io-channel-cells = <1>;
adc_channel_1: channel@1 {
reg = <1>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};

View File

@@ -0,0 +1,4 @@
CONFIG_ADC=y
CONFIG_SENSOR=y
CONFIG_VOLTAGE_DIVIDER=y
CONFIG_LOG=y

View File

@@ -0,0 +1,53 @@
sample:
name: ADC devicetree driver sample
tests:
sample.drivers.adc.adc_dt:
tags:
- adc
depends_on: adc
platform_allow:
- nucleo_l073rz
- disco_l475_iot1
- cc3220sf_launchxl
- cc3235sf_launchxl
- cy8cproto_063_ble
- stm32l496g_disco
- stm32h735g_disco
- nrf51dk/nrf51822
- nrf52840dk/nrf52840
- nrf54l15dk/nrf54l15/cpuapp
- nrf54h20dk/nrf54h20/cpuapp
- ophelia4ev/nrf54l15/cpuapp
- mec172xevb_assy6906
- gd32f350r_eval
- gd32f450i_eval
- gd32vf103v_eval
- gd32f403z_eval
- esp32_devkitc/esp32/procpu
- esp32s2_saola
- esp32c3_devkitm
- gd32l233r_eval
- lpcxpresso55s36
- mr_canhubk3
- longan_nano
- longan_nano/gd32vf103/lite
- rd_rw612_bga
- frdm_mcxn947/mcxn947/cpu0
- mcx_n9xx_evk/mcxn947/cpu0
- frdm_mcxc242
- ucans32k1sic
- xg24_rb4187c
- xg29_rb4412a
- raytac_an54l15q_db/nrf54l15/cpuapp
- frdm_mcxa166
- frdm_mcxa276
integration_platforms:
- nucleo_l073rz
- nrf52840dk/nrf52840
harness: console
timeout: 10
harness_config:
type: multi_line
regex:
- "ADC reading\\[\\d+\\]:"
- "- .+, channel \\d+: -?\\d+"

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 Wolter HV <wolterhv@gmx.de>
*
* SPDX-License-Identifier: Apache-2.0
*/
/ {
zephyr,user {
io-channels = <&adc0 0>;
};
};
&adc0 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1_4";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 Wolter HV <wolterhv@gmx.de>
*
* SPDX-License-Identifier: Apache-2.0
*/
/ {
zephyr,user {
io-channels = <&adc0 0>;
};
};
&adc0 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1_4";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 Wolter HV <wolterhv@gmx.de>
*
* SPDX-License-Identifier: Apache-2.0
*/
/ {
zephyr,user {
io-channels = <&adc0 0>;
};
};
&adc0 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1_4";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 Wolter HV <wolterhv@gmx.de>
*
* SPDX-License-Identifier: Apache-2.0
*/
/ {
zephyr,user {
io-channels = <&adc0 0>;
};
};
&adc0 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1_4";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};

View File

@@ -0,0 +1,45 @@
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(adc_dt_example, LOG_LEVEL_DBG);
/* Get the voltage divider device */
#define VOLTAGE_DIVIDER_NODE DT_NODELABEL(vdd_sense)
int main(void)
{
const struct device *vdd_dev = DEVICE_DT_GET(VOLTAGE_DIVIDER_NODE);
struct sensor_value val;
int err;
if (!device_is_ready(vdd_dev)) {
LOG_ERR("Voltage divider device not ready");
return 0;
}
LOG_INF("Voltage divider device ready!");
while (1) {
err = sensor_sample_fetch(vdd_dev);
if (err < 0) {
LOG_ERR("Could not fetch sample (%d)", err);
k_sleep(K_MSEC(1000));
continue;
}
err = sensor_channel_get(vdd_dev, SENSOR_CHAN_VOLTAGE, &val);
if (err < 0) {
LOG_ERR("Could not get channel (%d)", err);
k_sleep(K_MSEC(1000));
continue;
}
LOG_INF("Voltage reading: %d.%06d V", val.val1, val.val2);
k_sleep(K_MSEC(1000));
}
return 0;
}

View File

@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.20)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(adc_test)
target_sources(app PRIVATE src/main.c)

View File

@@ -0,0 +1,8 @@
&adc1 {
pinctrl-0 = <&adc1_in1_pa0>;
pinctrl-names = "default";
status = "okay";
st,adc-clock-source = "SYNC";
st,adc-prescaler = <4>;
};

View File

@@ -0,0 +1,3 @@
CONFIG_ADC=y
CONFIG_ADC_STM32=y
CONFIG_LOG=y

View File

@@ -0,0 +1,73 @@
#include <zephyr/kernel.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/device.h>
#include <zephyr/sys/printk.h>
// ADC-Knoten holen
static const struct device *adc_dev = DEVICE_DT_GET(DT_NODELABEL(adc1));
// Kanaldefinitionen
#define MY_SIGNAL_CHANNEL 1 // PA0
#define ADC_VREFINT_CHANNEL 18 // Intern
// Puffer für ZWEI Messwerte
static int16_t sample_buffer[2];
void main(void)
{
int err;
// Die VREFINT-Spannung in mV aus dem Datenblatt deines Controllers
#define VREFINT_MV 1212
printk("*** ADC Ratiometric Measurement (Single Sequence) ***\n");
if (!device_is_ready(adc_dev)) {
printk("ADC device not ready!\n");
return;
}
// --- Einmaliges Setup der beiden Kanäle ---
const struct adc_channel_cfg signal_channel_cfg = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME_DEFAULT, // Kurz für niederohmige Quellen
.channel_id = MY_SIGNAL_CHANNEL,
};
const struct adc_channel_cfg vrefint_channel_cfg = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME_MAX, // Lang für VREFINT
.channel_id = ADC_VREFINT_CHANNEL,
};
adc_channel_setup(adc_dev, &signal_channel_cfg);
adc_channel_setup(adc_dev, &vrefint_channel_cfg);
// --- EINE Sequenz, die BEIDE Kanäle enthält ---
const struct adc_sequence sequence = {
.channels = BIT(MY_SIGNAL_CHANNEL) | BIT(ADC_VREFINT_CHANNEL),
.buffer = sample_buffer,
.buffer_size = sizeof(sample_buffer),
.resolution = 12,
};
while (1) {
err = adc_read(adc_dev, &sequence);
if (err != 0) {
printk("ADC read failed with code %d\n", err);
} else {
// Die Ergebnisse sind in der Reihenfolge der Kanalnummern im Puffer
// Kanal 1 (MY_SIGNAL_CHANNEL) kommt vor Kanal 18 (ADC_VREFINT_CHANNEL)
int16_t signal_raw = sample_buffer[0];
int16_t vrefint_raw = sample_buffer[1];
// Ratiometrische Berechnung
int32_t signal_mv = (int32_t)signal_raw * VREFINT_MV / vrefint_raw;
printk("Signal: raw=%4d | VREFINT: raw=%4d | Calculated Voltage: %d mV\n",
signal_raw, vrefint_raw, signal_mv);
}
k_msleep(2000);
}
}

View File

@@ -0,0 +1,80 @@
#include <zephyr/kernel.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/device.h>
// Definiere die Kanäle
#define ADC_VREFINT_CHANNEL 18 // Muss mit dem DTS übereinstimmen
#define MY_SIGNAL_CHANNEL 1 // Muss mit dem pinctrl im DTS übereinstimmen
// ADC Device
static const struct device *adc_dev = DEVICE_DT_GET(DT_NODELABEL(adc1));
// ADC Kanal Konfigurationen
static const struct adc_channel_cfg vrefint_channel_cfg = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL, // Bedeutet VDDA
.acquisition_time = ADC_ACQ_TIME_MAX,
.channel_id = ADC_VREFINT_CHANNEL,
.differential = 0,
};
static const struct adc_channel_cfg signal_channel_cfg = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL, // Bedeutet VDDA
.acquisition_time = ADC_ACQ_TIME_MAX,
.channel_id = MY_SIGNAL_CHANNEL,
.differential = 0,
};
// Puffer für die Messwerte
#define BUFFER_SIZE 1
static int16_t sample_buffer[BUFFER_SIZE];
// Sequenz für die Messungen
struct adc_sequence sequence_vrefint = {
.channels = BIT(ADC_VREFINT_CHANNEL),
.buffer = sample_buffer,
.buffer_size = sizeof(sample_buffer),
.resolution = 12, // STM32G4 hat 12-bit
};
struct adc_sequence sequence_signal = {
.channels = BIT(MY_SIGNAL_CHANNEL),
.buffer = sample_buffer,
.buffer_size = sizeof(sample_buffer),
.resolution = 12,
};
void main(void) {
if (!device_is_ready(adc_dev)) {
printk("ADC device not found\n");
return;
}
// Kanäle konfigurieren
adc_channel_setup(adc_dev, &vrefint_channel_cfg);
adc_channel_setup(adc_dev, &signal_channel_cfg);
while (1) {
// 1. VREFINT messen zur Kalibrierung
adc_read(adc_dev, &sequence_vrefint);
int16_t vrefint_raw = sample_buffer[0];
// 2. Dein eigentliches Signal messen
adc_read(adc_dev, &sequence_signal);
int16_t signal_raw = sample_buffer[0];
// 3. Spannung berechnen
// VREFINT Wert für STM32G431 bei 3.0V Vdda ist typ. 1.212V (1212 mV)
// Überprüfe den genauen Wert im Datenblatt für deinen Controller!
#define VREFINT_MV 1212
int32_t signal_mv = (int32_t)signal_raw * VREFINT_MV / vrefint_raw;
printk("VREFINT raw: %d, Signal raw: %d, Calculated Voltage: %d mV\n",
vrefint_raw, signal_raw, signal_mv);
k_msleep(1000);
}
}

View File

@@ -0,0 +1,38 @@
#include <zephyr.h>
#include <drivers/adc.h>
#define PA0_PIN 0x04
#define ADC_CHANNEL 0x03
int main(void) {
int16_t adc_value = 0;
// Initialize the ADC
adc_config_t adc_config;
adc_config.mode = ADC_MODE_SINGLE_SHOT;
adc_config.channel = ADC_CHANNEL_PA0;
adc_config.sampling_rate = ADC_SAMP_RATE_1MS;
adc_config.data_rate = ADC_DATA_RATE_4MS;
adc_config.aux = ADC_AUX_ALL;
adc_config.atten = ADC_ATTEN_DB_11;
adc_config.ref = ADC_REF_INTERNAL;
adc_config.cal = ADC_CAL_ALL;
if (adc_config_data(&adc_config, &adc_context) < 0) {
zephyr_printf("Failed to configure ADC\n");
return -1;
}
// Read the analog input value
if (adc_read(&adc_context, &adc_value) < 0) {
zephyr_printf("Failed to read ADC value\n");
return -1;
}
zephyr_printf("ADC Value: %d\n", adc_value);
return 0;
}

View File

@@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.20)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(firmware_node LANGUAGES C)
zephyr_include_directories(../../include)
add_subdirectory(../../lib lib)
target_sources(app PRIVATE src/main.c)

View File

@@ -0,0 +1,34 @@
# Firmware Node Application
This Zephyr application provides firmware management capabilities for the irrigation system.
**Tested on Zephyr 4.1.99**
## Features
### Step 1: Shell with Reset Command
- Shell interface with custom "reset" command
- Warm reboot functionality
### Planned Features
- MCUboot support with partition manager
- Firmware version display
- MCUmgr support for OTA updates
## Building
```bash
west build -p auto -b weact_stm32g431_core apps/firmware_node -- -DBOARD_FLASH_RUNNER=blackmagicprobe
```
## Flashing
```bash
west flash
```
## Usage
Connect to the device via serial console and use the shell:
- `reset` - Reboot the system
- `help` - Show available commands

View File

@@ -0,0 +1,29 @@
/*
* Flash partition layout for STM32G431 (128KB total flash)
* MCUboot + single application slot configuration
*/
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x0000A000>; /* 40 KB for MCUboot */
read-only;
};
slot0_partition: partition@A000 {
label = "image-0";
reg = <0x0000A000 0x00016000>; /* 88 KB for application */
};
};
};
/ {
chosen {
zephyr,code-partition = &slot0_partition;
};
};

View File

@@ -0,0 +1,2 @@
# Board specific configuration for weact_stm32g431_core
# This file can be used for board-specific overrides if needed

View File

@@ -0,0 +1,7 @@
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "flash_partitions_128kb.dtsi"

View File

@@ -0,0 +1,18 @@
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x00008000>; /* 32 KB */
read-only;
};
slot0_partition: partition@8000 {
label = "image-0";
reg = <0x00008000 0x00018000>; /* 96 KB */
};
};
};

View File

@@ -0,0 +1,25 @@
# Partition manager configuration for firmware_node
# Boot partition (MCUboot)
mcuboot_primary:
address: 0x00000000
size: 0x8000
region: flash_primary
# Application partition (primary slot)
mcuboot_primary_app:
address: 0x00008000
size: 0x18000
region: flash_primary
# Secondary slot for updates
mcuboot_secondary:
address: 0x00020000
size: 0x18000
region: flash_primary
# Settings partition
settings_partition:
address: 0x00038000
size: 0x8000
region: flash_primary

View File

@@ -0,0 +1,21 @@
# Enable Console and printk for logging
CONFIG_CONSOLE=y
CONFIG_LOG=y
CONFIG_LOG_PROCESS_THREAD=y
# Enable Shell
CONFIG_SHELL=y
CONFIG_REBOOT=y
# Enable the reset command
CONFIG_KERNEL_SHELL=y
# Enable settings for persistent storage
CONFIG_SETTINGS=y
CONFIG_SETTINGS_NVS=y
CONFIG_NVS=y
# Enable Flash and Flash Map for image trailer manipulation
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_FLASH_PAGE_LAYOUT=y

View File

@@ -0,0 +1,167 @@
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/shell/shell.h>
#include <zephyr/sys/reboot.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/storage/flash_map.h>
#include <zephyr/devicetree.h>
LOG_MODULE_REGISTER(firmware_node, LOG_LEVEL_INF);
// Image header magic number (from MCUboot)
#define IMAGE_MAGIC 0x96f3b83d
#define IMAGE_HEADER_SIZE 32
// Function to invalidate current image and trigger serial recovery
static int invalidate_current_image(void)
{
const struct flash_area *fa;
int rc;
// Get the flash area for the current image slot (slot0_partition)
rc = flash_area_open(FIXED_PARTITION_ID(slot0_partition), &fa);
if (rc != 0) {
LOG_ERR("Failed to open flash area: %d", rc);
return rc;
}
// Ensure the flash area is valid
if (fa->fa_id != FIXED_PARTITION_ID(slot0_partition)) {
LOG_ERR("Invalid flash area ID: %d", fa->fa_id);
flash_area_close(fa);
return -EINVAL;
}
// Get the flash device associated with this area
// This is necessary to perform erase operations
const struct device *flash_dev = flash_area_get_device(fa);
if (flash_dev == NULL) {
LOG_ERR("Failed to get flash device for area");
flash_area_close(fa);
return -ENODEV;
}
struct flash_pages_info page_info;
off_t last_block_offset;
// Find the last block of the flash area
rc = flash_get_page_info_by_offs(flash_dev, fa->fa_off + fa->fa_size - 1, &page_info);
if (rc != 0) {
LOG_ERR("Failed to get page info: %d", rc);
flash_area_close(fa);
return rc;
}
// Calculate the last block offset
rc = flash_get_page_info_by_offs(flash_dev, fa->fa_off + fa->fa_size - 1, &page_info);
if (rc != 0) {
LOG_ERR("Failed to get page info: %d", rc);
flash_area_close(fa);
return rc;
}
last_block_offset = page_info.start_offset;
// Convert absolute flash offset to relative offset within the flash area
off_t relative_offset = last_block_offset - fa->fa_off;
// Erase the image trailer/metadata at the end of the partition
LOG_INF("Erasing image trailer at absolute offset: %ld, relative offset: %ld, size: %d bytes",
last_block_offset, relative_offset, page_info.size);
rc = flash_area_erase(fa, relative_offset, page_info.size);
if (rc != 0) {
LOG_ERR("Failed to erase image trailer: %d", rc);
} else {
LOG_INF("Image trailer erased successfully");
}
flash_area_close(fa);
return rc;
}
// Custom reset command handler
static int cmd_reset(const struct shell *shell, size_t argc, char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
shell_print(shell, "Resetting system...");
k_msleep(100); // Give time for the message to be sent
sys_reboot(SYS_REBOOT_COLD);
return 0;
}
// MCUboot serial recovery command handler
static int cmd_recovery(const struct shell *shell, size_t argc, char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
shell_print(shell, "Entering MCUboot serial recovery mode...");
shell_print(shell, "Corrupting current image magic to trigger recovery...");
// Invalidate the current image by corrupting its header
int rc = invalidate_current_image();
if (rc != 0) {
shell_error(shell, "Failed to invalidate image: %d", rc);
return rc;
}
shell_print(shell, "Image magic corrupted. System will reset and MCUboot will detect bad image.");
shell_print(shell, "MCUboot should show error and wait for recovery.");
k_msleep(100); // Give time for the message to be sent
// Reset the system - MCUboot will detect invalid image and enter serial recovery
// log_process(true);
// sys_reboot(SYS_REBOOT_COLD);
return 0;
}
// Command to show firmware info
static int cmd_info(const struct shell *shell, size_t argc, char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
const struct flash_area *fa;
int rc = flash_area_open(FIXED_PARTITION_ID(slot0_partition), &fa);
if (rc != 0) {
shell_error(shell, "Failed to open flash area: %d", rc);
return rc;
}
// Read the first few bytes to check the image header
uint32_t magic;
rc = flash_area_read(fa, 0, &magic, sizeof(magic));
if (rc == 0) {
shell_print(shell, "Image magic: 0x%08x", magic);
if (magic == IMAGE_MAGIC) {
shell_print(shell, "Image header is valid");
shell_print(shell, "Image starts at flash offset: 0x%lx", (unsigned long)fa->fa_off);
shell_print(shell, "Image partition size: %d bytes", fa->fa_size);
} else {
shell_print(shell, "Image header is INVALID (expected 0x%08x)", IMAGE_MAGIC);
}
} else {
shell_error(shell, "Failed to read image header: %d", rc);
}
flash_area_close(fa);
return 0;
}
SHELL_CMD_REGISTER(reset, NULL, "Reset the system", cmd_reset);
SHELL_CMD_REGISTER(recovery, NULL, "Enter MCUboot serial recovery mode", cmd_recovery);
SHELL_CMD_REGISTER(info, NULL, "Show firmware info", cmd_info);
int main(void)
{
LOG_INF("Firmware Node starting up");
LOG_INF("Shell with reset command available");
LOG_INF("Serial recovery command available");
return 0;
}

View File

@@ -0,0 +1,4 @@
# Sysbuild configuration for firmware_node with MCUboot
# Enable MCUboot as bootloader
set(SB_CONFIG_BOOTLOADER_MCUBOOT TRUE)

View File

@@ -0,0 +1,5 @@
# Sysbuild configuration for firmware_node with MCUboot
# Enable MCUboot as bootloader
SB_CONFIG_BOOTLOADER_MCUBOOT=y
SB_CONFIG_MCUBOOT_MODE_SINGLE_APP=y

View File

@@ -0,0 +1,13 @@
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "../boards/flash_partitions_128kb.dtsi"
/ {
chosen {
zephyr,code-partition = &slot0_partition;
};
};

View File

@@ -0,0 +1,31 @@
# MCUboot configuration for firmware_node
# Enable basic console and logging for debugging
CONFIG_LOG=y
CONFIG_BOOT_BANNER=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
CONFIG_PRINTK=y
# Single slot configuration (no upgrades)
CONFIG_SINGLE_APPLICATION_SLOT=y
# Enable serial recovery mode (temporarily commented out for debugging)
# CONFIG_MCUBOOT_SERIAL=y
# CONFIG_BOOT_SERIAL_UART=y
# CONFIG_BOOT_SERIAL_DETECT_PORT=y
# Disable signature validation for testing to save space
CONFIG_BOOT_SIGNATURE_TYPE_NONE=y
# Size optimizations to fit in 40KB flash
CONFIG_SIZE_OPTIMIZATIONS=y
CONFIG_CBPRINTF_NANO=y
CONFIG_MINIMAL_LIBC=y
CONFIG_ASSERT=n
# Disable debug features for size
CONFIG_DEBUG_INFO=n
CONFIG_DEBUG_OPTIMIZATIONS=n
# Minimal heap for size optimization
CONFIG_HEAP_MEM_POOL_SIZE=0

View File

@@ -0,0 +1,12 @@
/*
* MCUboot device tree overlay for firmware_node
* Uses shared flash partition layout
*/
#include "../boards/flash_partitions_128kb.dtsi"
/ {
chosen {
zephyr,code-partition = &boot_partition;
};
};

View File

@@ -0,0 +1,33 @@
/*
* MCUboot specific overlay for weact_stm32g431_core
* This overlay defines flash partitions for MCUboot
*/
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x00008000>;
};
slot0_partition: partition@8000 {
label = "image-0";
reg = <0x00008000 0x0000E000>;
};
slot1_partition: partition@16000 {
label = "image-1";
reg = <0x00016000 0x0000E000>;
};
storage_partition: partition@24000 {
label = "storage";
reg = <0x00024000 0x00004000>;
};
};
};
&chosen {
zephyr,boot-partition = &boot_partition;
};

View File

@@ -6,7 +6,8 @@
#include <zephyr/kernel.h>
int main(void) {
printk("Hello from Gateway!\n");
return 0;
int main(void)
{
printk("Hello from Gateway!\n");
return 0;
}

View File

@@ -1,12 +0,0 @@
{
"configurations": [
{
"name": "Linux",
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
"cStandard": "c99",
"cppStandard": "gnu++17",
"intelliSenseMode": "linux-gcc-arm"
}
],
"version": 4
}

View File

@@ -11,63 +11,8 @@
s0-gpios = <&gpiob 6 GPIO_ACTIVE_HIGH>; // S0 (PB6) - Status/Select 0 output from VND7050AJ
s1-gpios = <&gpiob 5 GPIO_ACTIVE_HIGH>; // S1 (PB5) - Status/Select 1 output from VND7050AJ
};
adc_sensors {
compatible = "adc-sensors";
supply_voltage: supply-voltage {
compatible = "custom,supply-voltage";
io-channels = <&adc1 1>; /* ADC1 channel 1 (PA0) */
io-channel-names = "voltage";
reference-mv = <3300>;
voltage-divider-ratio = <4>; /* Adjust based on your voltage divider */
/* GPIO control pins using VND7050AJ pins */
sen-gpios = <&gpiob 4 GPIO_ACTIVE_HIGH>; /* SEN (PB4) - enable sensor */
s0-gpios = <&gpiob 6 GPIO_ACTIVE_HIGH>; /* S0 (PB6) - mux select bit 0 */
s1-gpios = <&gpiob 5 GPIO_ACTIVE_HIGH>; /* S1 (PB5) - mux select bit 1 */
measurement-delay-ms = <5>; /* 5ms delay after GPIO setup */
};
motor_current: motor-current {
compatible = "custom,motor-current";
io-channels = <&adc1 1>; /* Same ADC channel, different mux setting */
io-channel-names = "current";
reference-mv = <3300>;
current-sense-resistor-mohm = <100>; /* 100mΩ sense resistor */
/* GPIO control pins using VND7050AJ pins */
sen-gpios = <&gpiob 4 GPIO_ACTIVE_HIGH>; /* SEN (PB4) - enable sensor */
s0-gpios = <&gpiob 6 GPIO_ACTIVE_HIGH>; /* S0 (PB6) - mux select bit 0 */
s1-gpios = <&gpiob 5 GPIO_ACTIVE_HIGH>; /* S1 (PB5) - mux select bit 1 */
measurement-delay-ms = <10>; /* 10ms delay for current settling */
};
};
};
// Clock configuration: Uncomment the following section to use calibrated HSI instead of HSE
//&clk_hse {
// status = "disabled"; // Disable external crystal oscillator
//};
//
//&clk_hsi {
// status = "okay"; // Enable internal high-speed oscillator (16 MHz, calibrated)
//};
//
//&pll {
// // Change PLL source from HSE to HSI
// clocks = <&clk_hsi>;
// // Adjust multipliers to maintain 144 MHz system clock with 16 MHz HSI input
// // HSI = 16 MHz, div-m = 4, mul-n = 72, div-r = 2
// // PLL_VCO = (16 MHz / 4) * 72 = 288 MHz
// // SYSCLK = 288 MHz / 2 = 144 MHz
// div-m = <4>; // Divide HSI by 4 (16 MHz / 4 = 4 MHz)
// mul-n = <72>; // Multiply by 72 (4 MHz * 72 = 288 MHz)
// div-r = <2>; // Divide by 2 for system clock (288 MHz / 2 = 144 MHz)
//};
&usart1 {
modbus0 {
compatible = "zephyr,modbus-serial";
@@ -78,22 +23,31 @@
pinctrl-names = "default";
};
&adc1 { // ADC1 wird für PA0 verwendet
status = "okay"; // ADC1 aktivieren
pinctrl-0 = <&adc1_in1_pa0>; // Pinmux für PA0 als ADC1_IN1
&adc1 {
status = "okay";
pinctrl-0 = <&adc1_in1_pa0 &adc1_in15_pb0>;
pinctrl-names = "default";
st,adc-clock-source = "SYNC";
st,adc-prescaler = <4>;
st,adc-prescaler = <1>;
#address-cells = <1>;
#size-cells = <0>;
// Definition des ADC-Kanals für MULTISENSE (PA0)
channel@1 { // ADC1_IN1 ist Kanal 1
reg = <1>; // Kanalnummer
channel@1 {
reg = <1>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_MAX>; // Use maximum acquisition time for stability
zephyr,resolution = <12>;
zephyr,vref-mv = <2048>; // STM32G431 VREFBUF at 2.048V
};
channel@15 {
reg = <15>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
zephyr,vref-mv = <2048>; // STM32G431 VREFBUF at 2.048V
};
};
@@ -102,4 +56,9 @@
adc1_in1_pa0: adc1_in1_pa0 {
pinmux = <STM32_PINMUX('A', 0, ANALOG)>; // PA0 in den Analogmodus setzen
};
// Pinmux für PB0 als ADC1_IN15 (Analogmodus) - for lab supply testing
adc1_in15_pb0: adc1_in15_pb0 {
pinmux = <STM32_PINMUX('B', 0, ANALOG)>; // PB0 in den Analogmodus setzen
};
};

View File

@@ -1,48 +0,0 @@
description: Custom motor current measurement with GPIO control
compatible: "custom,motor-current"
properties:
io-channels:
type: phandle-array
required: true
description: ADC channel for current measurement
io-channel-names:
type: string-array
description: Names for the ADC channels
current-sense-resistor-mohm:
type: int
required: true
description: Current sense resistor value in milliohms
amplifier-gain:
type: int
default: 1
description: Current sense amplifier gain
reference-mv:
type: int
default: 3300
description: ADC reference voltage in millivolts
sen-gpios:
type: phandle-array
required: true
description: GPIO to enable/disable the current measurement sensor
s0-gpios:
type: phandle-array
required: true
description: GPIO for multiplexer control bit 0
s1-gpios:
type: phandle-array
required: true
description: GPIO for multiplexer control bit 1
measurement-delay-ms:
type: int
default: 10
description: Delay in milliseconds after setting GPIOs before ADC measurement

View File

@@ -1,63 +0,0 @@
description: Custom supply voltage measurement with GPIO control
compatible: "custom,supply-voltage"
properties:
io-channels:
type: phandle-array
required: true
description: ADC channel for voltage measurement
io-channel-names:
type: string-array
description: Names for the ADC channels
voltage-divider-ratio:
type: int
required: true
description: Voltage divider ratio for scaling
reference-mv:
type: int
default: 3300
description: ADC reference voltage in millivolts
sen-gpios:
type: phandle-array
required: true
description: GPIO to enable/disable the voltage measurement sensor
s0-gpios:
type: phandle-array
required: true
description: GPIO for multiplexer control bit 0
s1-gpios:
type: phandle-array
required: true
description: GPIO for multiplexer control bit 1
measurement-delay-ms:
type: int
default: 10
description: Delay in milliseconds after setting GPIOs before ADC measurement
sen-gpios:
type: phandle-array
required: true
description: GPIO for SEN (Sense Enable) pin
s0-gpios:
type: phandle-array
required: true
description: GPIO for S0 (Select 0) pin
s1-gpios:
type: phandle-array
required: true
description: GPIO for S1 (Select 1) pin
measurement-delay-ms:
type: int
default: 10
description: Delay in milliseconds after setting control pins before ADC reading

View File

@@ -26,10 +26,10 @@ properties:
s0-gpios:
type: phandle-array
description: GPIO for status/select 0 pin
description: GPIO for select 0 pin
required: true
s1-gpios:
type: phandle-array
description: GPIO for status/select 1 pin
description: GPIO for select 1 pin
required: true

View File

@@ -13,6 +13,7 @@ CONFIG_NVS=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_SETTINGS_LOG_LEVEL_DBG=y
# Config modbus
CONFIG_UART_INTERRUPT_DRIVEN=y
@@ -20,7 +21,7 @@ CONFIG_MODBUS=y
CONFIG_MODBUS_ROLE_SERVER=y
CONFIG_MODBUS_BUFFER_SIZE=256
# ADC Sensor Configuration - Use real ADC readings
CONFIG_ADC_SENSOR_SIMULATED=n
# Enable ADC driver
CONFIG_ADC=y
CONFIG_ADC_STM32=y

View File

@@ -1,31 +1,35 @@
#include <app_version.h>
#include <lib/fwu.h>
#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <zephyr/logging/log.h>
#include <lib/modbus_server.h>
#include <lib/valve.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/settings/settings.h>
#include <lib/fwu.h>
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
int main(void) {
int rc;
LOG_INF("Starting Irrigation System Slave Node version %s (Build version %s)",
APP_VERSION_STRING, STRINGIFY(APP_BUILD_VERSION));
int main(void)
{
LOG_INF("Starting Irrigation System Slave Node");
if (settings_subsys_init() || settings_load()) {
LOG_ERR("Settings initialization or loading failed");
}
if (settings_subsys_init() || settings_load()) {
LOG_ERR("Settings initialization or loading failed");
}
valve_init();
fwu_init();
valve_init();
fwu_init();
rc = modbus_server_init();
if (rc < 0) {
LOG_ERR("Modbus RTU server initialization failed: %d", rc);
return 0;
}
LOG_INF("Irrigation System Slave Node started successfully");
return 0;
if (modbus_server_init()) {
LOG_ERR("Modbus RTU server initialization failed");
return 0;
}
// Test supply voltage reading periodically
while (1) {
uint16_t supply_voltage = valve_get_supply_voltage();
LOG_INF("Supply voltage: %u mV", supply_voltage);
k_msleep(5000); // Read every 5 seconds
}
LOG_INF("Irrigation System Slave Node started successfully");
return 0;
}

View File

@@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.20)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(bootloader LANGUAGES C)
zephyr_include_directories(../../../include)
add_subdirectory(../../../lib lib)
target_sources(app PRIVATE src/main.c)

View File

@@ -0,0 +1,34 @@
# Firmware Node Application
This Zephyr application provides firmware management capabilities for the irrigation system.
**Tested on Zephyr 4.1.99**
## Features
### Step 1: Shell with Reset Command
- Shell interface with custom "reset" command
- Warm reboot functionality
### Planned Features
- MCUboot support with partition manager
- Firmware version display
- MCUmgr support for OTA updates
## Building
```bash
west build -p auto -b weact_stm32g431_core apps/firmware_node -- -DBOARD_FLASH_RUNNER=blackmagicprobe
```
## Flashing
```bash
west flash
```
## Usage
Connect to the device via serial console and use the shell:
- `reset` - Reboot the system
- `help` - Show available commands

View File

@@ -1,5 +1,5 @@
VERSION_MAJOR = 0
VERSION_MINOR = 0
PATCHLEVEL = 1
VERSION_TWEAK = 1
EXTRAVERSION = devel
VERSION_TWEAK = 0
EXTRAVERSION = testing

View File

@@ -0,0 +1,8 @@
#!/bin/bash
/home/edi/zephyr-sdk-0.17.1/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb \
-ex 'target extended-remote /dev/ttyACM0' \
-ex 'monitor swdp_scan' \
-ex 'attach 1' \
-ex 'monitor erase_mass' \
-ex 'detach' \
-ex 'quit' \

View File

@@ -0,0 +1,16 @@
CONFIG_SHELL=y
CONFIG_REBOOT=y
# MCUboot support for recovery request function
CONFIG_MCUBOOT_BOOTUTIL_LIB=y
CONFIG_MCUBOOT_IMG_MANAGER=y
CONFIG_IMG_MANAGER=y
# Flash and Stream Configuration (required for IMG_MANAGER)
CONFIG_FLASH=y
CONFIG_STREAM_FLASH=y
# Retention system
CONFIG_RETENTION=y
CONFIG_RETENTION_BOOT_MODE=y
CONFIG_RETAINED_MEM=y

View File

@@ -0,0 +1,42 @@
#include <zephyr/kernel.h>
#include <app_version.h>
#include <zephyr/shell/shell.h>
#include <zephyr/sys/reboot.h>
#include <zephyr/dfu/mcuboot.h>
#include <zephyr/retention/bootmode.h>
#include <stdlib.h>
/* Shell command handler for "reset" */
static int cmd_reset(const struct shell *sh, size_t argc, char **argv)
{
shell_print(sh, "Rebooting system...");
k_sleep(K_MSEC(100)); // Optional delay for user to see the message
sys_reboot(SYS_REBOOT_WARM);
return 0;
}
static int cmd_download(const struct shell *sh, size_t argc, char **argv)
{
int rc;
/* Set boot mode to serial recovery */
rc = bootmode_set(BOOT_MODE_TYPE_BOOTLOADER);
if (rc < 0) {
shell_error(sh, "Failed to set boot mode: %d", rc);
return rc;
}
shell_print(sh, "Boot mode set to recovery. Rebooting to bootloader...");
k_sleep(K_MSEC(100));
sys_reboot(SYS_REBOOT_WARM);
return 0;
}
/* Register the shell command */
SHELL_CMD_REGISTER(reset, NULL, "Reboot the system", cmd_reset);
SHELL_CMD_REGISTER(download, NULL, "Download firmware", cmd_download);
int main(void){
printk("Bootloader test version %s\n", APP_VERSION_EXTENDED_STRING);
return 0;
}

View File

@@ -0,0 +1,5 @@
# Sysbuild configuration for firmware_node with MCUboot
# Enable MCUboot as bootloader
SB_CONFIG_BOOTLOADER_MCUBOOT=y
SB_CONFIG_MCUBOOT_MODE_SINGLE_APP=y

View File

@@ -0,0 +1,13 @@
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "flash_partitions_128kb.dtsi"
/ {
chosen {
zephyr,code-partition = &slot0_partition;
};
};

View File

@@ -0,0 +1,62 @@
/*
* Devicetree Overlay for 128KB Flash
* - MCUboot Bootloader (32KB)
* - Application Slot (96KB)
*/
&flash0 {
/delete-node/ partitions;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 DT_SIZE_K(32)>;
read-only;
};
slot0_partition: partition@8000 {
label = "image-0";
reg = <0x00008000 DT_SIZE_K(96)>;
};
};
};
/* Add retention memory to the existing SRAM node */
&sram0 {
#address-cells = <1>;
#size-cells = <1>;
retainedmem {
compatible = "zephyr,retained-ram";
status = "okay";
#address-cells = <1>;
#size-cells = <1>;
boot_mode: retention@7f00 {
compatible = "zephyr,retention";
status = "okay";
reg = <0x7f00 0x100>;
prefix = [08 04];
checksum = <1>;
};
};
};
/ {
chosen {
zephyr,boot-mode = &boot_mode;
zephyr,console = &cdc_acm_uart0;
};
};
&zephyr_udc0 {
status = "okay";
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
label = "CDC_ACM_0";
};
};

View File

@@ -0,0 +1,46 @@
#
# MCUboot Configuration for Serial Recovery over USB-CDC
#
# Enables serial recovery mode in MCUboot.
CONFIG_MCUBOOT_SERIAL=y
# Tell MCUboot to check for a trigger to enter recovery
CONFIG_BOOT_SERIAL_BOOT_MODE=y
# --- USB Stack Configuration ---
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_PRODUCT="MCUboot Serial Recovery"
# Use USB CDC ACM for MCUboot serial recovery (not UART)
CONFIG_BOOT_SERIAL_CDC_ACM=y
# --- Disable Zephyr Console to avoid conflicts ---
# MCUboot's serial_adapter doesn't work well with the general console subsystem.
CONFIG_UART_CONSOLE=n
CONFIG_CONSOLE_HANDLER=n
CONFIG_CONSOLE=n
# --- Flash and Stream Configuration (required for IMG_MANAGER) ---
CONFIG_FLASH=y
CONFIG_STREAM_FLASH=y
# --- mcumgr Configuration ---
# MCUMGR requires NET_BUF, even for serial transport.
CONFIG_NET_BUF=y
CONFIG_NET_LOG=n
# Enables the mcumgr library and necessary command handlers
CONFIG_MCUMGR=y
CONFIG_IMG_MANAGER=y
CONFIG_MCUMGR_GRP_IMG=y
CONFIG_MCUMGR_GRP_OS=y
# --- Retention Configuration ---
CONFIG_RETAINED_MEM=y
CONFIG_RETENTION=y
CONFIG_RETENTION_BOOT_MODE=y
# --- Optional: Reduce memory usage ---
CONFIG_MAIN_STACK_SIZE=2048
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=1024

View File

@@ -0,0 +1,17 @@
#include "flash_partitions_128kb.dtsi"
/ {
chosen {
zephyr,code-partition = &boot_partition;
zephyr,console = &cdc_acm_uart0;
};
};
&zephyr_udc0 {
status = "okay";
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
label = "CDC_ACM_0";
};
};

View File

@@ -1,46 +0,0 @@
#ifndef ADC_SENSOR_H
#define ADC_SENSOR_H
#include <stdint.h>
/**
* @file adc_sensor.h
* @brief API for the ADC sensor library.
*
* This library provides functions to initialize and read from the ADC sensors,
* specifically for measuring supply voltage and motor current.
* It can operate in a real or simulated mode.
*/
/**
* @brief Initializes the ADC sensor system.
*
* This function sets up the necessary ADC channels and configurations.
* It should be called once before any other function in this library.
* In simulated mode, it logs the simulated values.
*
* @return 0 on success, or a negative error code on failure.
*/
int adc_sensor_init(void);
/**
* @brief Gets the current supply voltage reading.
*
* This function reads the value from the corresponding ADC channel and converts
* it to millivolts.
*
* @return The supply voltage in millivolts (mV).
*/
uint16_t adc_sensor_get_voltage_mv(void);
/**
* @brief Gets the current motor current reading.
*
* This function reads the value from the motor driver's sense pin via ADC
* and converts it to milliamps. This is used for end-stop detection.
*
* @return The motor current in milliamps (mA).
*/
uint16_t adc_sensor_get_current_ma(void);
#endif /* ADC_SENSOR_H */

View File

@@ -3,45 +3,8 @@
#include <stdint.h>
/**
* @file fwu.h
* @brief API for the Firmware Update (FWU) library.
*
* This library provides the core logic for handling the over-the-air firmware
* update process via Modbus. It manages the data buffer, processes commands,
* and calculates CRC checksums for data verification.
*/
/**
* @brief Initializes the firmware update module.
*
* This function currently does nothing but is a placeholder for future
* initialization logic.
*/
void fwu_init(void);
/**
* @brief Handles incoming Modbus register writes related to firmware updates.
*
* This function is the main entry point for the FWU process. It parses the
* address and value from a Modbus write operation and takes appropriate action,
* such as storing metadata (offset, size) or data chunks, and processing
* commands (verify, finalize).
*
* @param addr The Modbus register address being written to.
* @param reg The 16-bit value being written to the register.
*/
void fwu_handler(uint16_t addr, uint16_t reg);
/**
* @brief Gets the CRC16-CCITT of the last received firmware chunk.
*
* After a data chunk is fully received into the buffer, this function can be
* called to retrieve the calculated CRC checksum. The master can then compare
* this with its own calculated CRC to verify data integrity.
*
* @return The 16-bit CRC of the last chunk.
*/
uint16_t fwu_get_last_chunk_crc(void);
#endif // FWU_H
#endif // FWU_H

View File

@@ -4,161 +4,51 @@
#include <stdint.h>
/**
* @file modbus_server.h
* @brief API for the Modbus server implementation.
*
* This file defines the Modbus register map and provides functions to
* initialize and manage the Modbus server.
* @brief Modbus Input Register Addresses.
*/
/**
* @brief Modbus Input Register Addresses (Read-Only).
* @see docs/modbus-registers.de.md
*/
enum {
/**
* @brief Kombiniertes Status-Register für das Ventil.
* High-Byte: Bewegung (0=Idle, 1=Öffnet, 2=Schliesst, 3=Fehler).
* Low-Byte: Zustand (0=Geschlossen, 1=Geöffnet).
*/
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000,
/**
* @brief Aktueller Motorstrom in Milliampere (mA).
*/
REG_INPUT_MOTOR_CURRENT_MA = 0x0001,
/**
* @brief Bitmaske der digitalen Eingänge. Bit 0: Eingang 1, Bit 1: Eingang 2.
* 1=Aktiv.
*/
REG_INPUT_DIGITAL_INPUTS_STATE = 0x0020,
/**
* @brief Event-Flags für Taster (Clear-on-Read). Bit 0: Taster 1 gedrückt.
* Bit 1: Taster 2 gedrückt.
*/
REG_INPUT_BUTTON_EVENTS = 0x0021,
/**
* @brief Firmware-Version, z.B. 0x0102 für v1.2.
*/
REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR = 0x00F0,
/**
* @brief Firmware-Version Patch-Level, z.B. 3 für v1.2.3.
*/
REG_INPUT_FIRMWARE_VERSION_PATCH = 0x00F1,
/**
* @brief Gerätestatus (0=OK, 1=Allgemeiner Fehler).
*/
REG_INPUT_DEVICE_STATUS = 0x00F2,
/**
* @brief Untere 16 Bit der Uptime in Sekunden.
*/
REG_INPUT_UPTIME_SECONDS_LOW = 0x00F3,
/**
* @brief Obere 16 Bit der Uptime in Sekunden.
*/
REG_INPUT_UPTIME_SECONDS_HIGH = 0x00F4,
/**
* @brief Aktuelle Versorgungsspannung in Millivolt (mV).
*/
REG_INPUT_SUPPLY_VOLTAGE_MV = 0x00F5,
/**
* @brief CRC16 des zuletzt im Puffer empfangenen Daten-Chunks für das
* Firmware-Update.
*/
REG_INPUT_FWU_LAST_CHUNK_CRC = 0x0100
enum
{
/* Valve Control & Status */
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000,
REG_INPUT_MOTOR_CURRENT_MA = 0x0001,
/* Digital Inputs */
REG_INPUT_DIGITAL_INPUTS_STATE = 0x0020,
REG_INPUT_BUTTON_EVENTS = 0x0021,
/* System Config & Status */
REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR = 0x00F0,
REG_INPUT_FIRMWARE_VERSION_PATCH = 0x00F1,
REG_INPUT_DEVICE_STATUS = 0x00F2,
REG_INPUT_UPTIME_SECONDS_LOW = 0x00F3,
REG_INPUT_UPTIME_SECONDS_HIGH = 0x00F4,
REG_INPUT_SUPPLY_VOLTAGE_MV = 0x00F5,
REG_INPUT_FWU_LAST_CHUNK_CRC = 0x0100
};
/**
* @brief Modbus Holding Register Addresses (Read/Write).
* @see docs/modbus-registers.de.md
* @brief Modbus Holding Register Addresses.
*/
enum {
/**
* @brief Ventilsteuerungsbefehl (1=Öffnen, 2=Schliessen, 0=Bewegung stoppen).
*/
REG_HOLDING_VALVE_COMMAND = 0x0000,
/**
* @brief Sicherheits-Timeout in Sekunden für den Öffnen-Vorgang.
*/
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001,
/**
* @brief Sicherheits-Timeout in Sekunden für den Schliessen-Vorgang.
*/
REG_HOLDING_MAX_CLOSING_TIME_S = 0x0002,
/**
* @brief Bitmaske zum Lesen und Schreiben der digitalen Ausgänge. Bit 0:
* Ausgang 1, Bit 1: Ausgang 2. 1=AN, 0=AUS.
*/
REG_HOLDING_DIGITAL_OUTPUTS_STATE = 0x0010,
/**
* @brief Timeout des Fail-Safe-Watchdogs in Sekunden. 0=Deaktiviert.
*/
REG_HOLDING_WATCHDOG_TIMEOUT_S = 0x00F0,
/**
* @brief Schreiben von 1 startet das Gerät neu.
*/
REG_HOLDING_DEVICE_RESET = 0x00F1,
/**
* @brief Befehl für das Firmware-Update.
* 1: Verify Chunk - Slave schreibt den letzten Chunk ins Flash.
* 2: Finalize Update - Installation abschliessen und neu starten.
*/
REG_HOLDING_FWU_COMMAND = 0x0100,
/**
* @brief Untere 16 Bit des 32-Bit-Offsets für den nächsten
* Firmware-Update-Chunk.
*/
REG_HOLDING_FWU_CHUNK_OFFSET_LOW = 0x0101,
/**
* @brief Obere 16 Bit des 32-Bit-Offsets für den nächsten
* Firmware-Update-Chunk.
*/
REG_HOLDING_FWU_CHUNK_OFFSET_HIGH = 0x0102,
/**
* @brief Grösse des nächsten Firmware-Update-Chunks in Bytes (max. 256).
*/
REG_HOLDING_FWU_CHUNK_SIZE = 0x0103,
/**
* @brief Startadresse des 256-Byte-Puffers für Firmware-Update-Daten.
*/
REG_HOLDING_FWU_DATA_BUFFER = 0x0180,
enum
{
/* Valve Control */
REG_HOLDING_VALVE_COMMAND = 0x0000,
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001,
REG_HOLDING_MAX_CLOSING_TIME_S = 0x0002,
/* Digital Outputs */
REG_HOLDING_DIGITAL_OUTPUTS_STATE = 0x0010,
/* System Config */
REG_HOLDING_WATCHDOG_TIMEOUT_S = 0x00F0,
REG_HOLDING_DEVICE_RESET = 0x00F1,
/* Firmware Update */
REG_HOLDING_FWU_COMMAND = 0x0100,
REG_HOLDING_FWU_CHUNK_OFFSET_LOW = 0x0101,
REG_HOLDING_FWU_CHUNK_OFFSET_HIGH = 0x0102,
REG_HOLDING_FWU_CHUNK_SIZE = 0x0103,
REG_HOLDING_FWU_DATA_BUFFER = 0x0180,
};
/**
* @brief Initializes the Modbus server.
*
* This function sets up the Modbus RTU server interface, loads saved settings
* (baudrate, unit ID), and starts listening for requests.
*
* @return 0 on success, or a negative error code on failure.
*/
int modbus_server_init(void);
/**
* @brief Reconfigures the Modbus server at runtime.
*
* Updates the baudrate and unit ID of the server. If the reconfiguration
* fails, the settings are saved and will be applied after a device reset.
*
* @param baudrate The new baudrate to set.
* @param unit_id The new Modbus unit ID (slave address).
* @return 0 on success, or a negative error code if immediate reconfiguration
* fails. Returns 0 even on failure if settings could be saved for the next
* boot.
*/
int modbus_reconfigure(uint32_t baudrate, uint8_t unit_id);
/**
* @brief Gets the current baudrate of the Modbus server.
*
* @return The current baudrate.
*/
uint32_t modbus_get_baudrate(void);
/**
* @brief Gets the current unit ID of the Modbus server.
*
* @return The current unit ID.
*/
uint8_t modbus_get_unit_id(void);
#endif // MODBUS_SERVER_H
#endif // MODBUS_SERVER_H

View File

@@ -4,120 +4,34 @@
#include <stdint.h>
#include <zephyr/drivers/gpio.h>
/**
* @file valve.h
* @brief API for controlling the motorized valve.
*
* This library provides functions to initialize, open, close, and stop the
* valve. It also allows getting the valve's state and movement status, and
* configuring the maximum opening and closing times.
*/
/**
* @brief Defines the GPIO pins used for the valve controller.
*/
struct valve_gpios {
const struct gpio_dt_spec
in0; /**< Control input 0 for the VND7050AJ driver. */
const struct gpio_dt_spec
in1; /**< Control input 1 for the VND7050AJ driver. */
const struct gpio_dt_spec rst; /**< Reset pin for the VND7050AJ driver. */
const struct gpio_dt_spec sen; /**< Sense (current measurement) pin. */
const struct gpio_dt_spec s0; /**< S0 select pin. */
const struct gpio_dt_spec s1; /**< S1 select pin. */
const struct gpio_dt_spec in0;
const struct gpio_dt_spec in1;
const struct gpio_dt_spec rst;
const struct gpio_dt_spec sen;
const struct gpio_dt_spec s0;
const struct gpio_dt_spec s1;
};
/**
* @brief Represents the static state of the valve (open or closed).
*/
enum valve_state {
VALVE_STATE_CLOSED, /**< The valve is fully closed. */
VALVE_STATE_OPEN, /**< The valve is fully open. */
VALVE_STATE_CLOSED,
VALVE_STATE_OPEN,
};
enum valve_movement { VALVE_MOVEMENT_IDLE, VALVE_MOVEMENT_OPENING, VALVE_MOVEMENT_CLOSING, VALVE_MOVEMENT_ERROR };
/**
* @brief Represents the dynamic movement status of the valve.
*/
enum valve_movement {
VALVE_MOVEMENT_IDLE, /**< The valve is not moving. */
VALVE_MOVEMENT_OPENING, /**< The valve is currently opening. */
VALVE_MOVEMENT_CLOSING, /**< The valve is currently closing. */
VALVE_MOVEMENT_ERROR /**< An error occurred during movement. */
};
/**
* @brief Initializes the valve control system.
*
* Configures the GPIOs and loads saved settings for timeouts.
*/
void valve_init(void);
/**
* @brief Starts opening the valve.
*
* The valve will open for the configured maximum opening time.
*/
void valve_open(void);
/**
* @brief Starts closing the valve.
*
* The valve will close for the configured maximum closing time.
*/
void valve_close(void);
/**
* @brief Stops any ongoing valve movement immediately.
*/
void valve_stop(void);
/**
* @brief Gets the current static state of the valve.
*
* @return The current valve state (VALVE_STATE_CLOSED or VALVE_STATE_OPEN).
*/
enum valve_state valve_get_state(void);
/**
* @brief Gets the current movement status of the valve.
*
* @return The current movement status.
*/
enum valve_movement valve_get_movement(void);
/**
* @brief Gets the motor current.
*
* @return The motor current in milliamps (currently simulated).
*/
uint16_t valve_get_motor_current(void);
uint16_t valve_get_supply_voltage(void);
/**
* @brief Sets the maximum time for the valve to open.
*
* @param seconds The timeout in seconds.
*/
void valve_set_max_open_time(uint16_t seconds);
/**
* @brief Sets the maximum time for the valve to close.
*
* @param seconds The timeout in seconds.
*/
void valve_set_max_close_time(uint16_t seconds);
/**
* @brief Gets the configured maximum opening time.
*
* @return The timeout in seconds.
*/
uint16_t valve_get_max_open_time(void);
/**
* @brief Gets the configured maximum closing time.
*
* @return The timeout in seconds.
*/
uint16_t valve_get_max_close_time(void);
#endif // VALVE_H
#endif // VALVE_H

View File

@@ -1,4 +1,3 @@
add_subdirectory_ifdef(CONFIG_ADC_SENSOR adc_sensor)
add_subdirectory_ifdef(CONFIG_LIB_FWU fwu)
add_subdirectory_ifdef(CONFIG_LIB_MODBUS_SERVER modbus_server)
add_subdirectory_ifdef(CONFIG_LIB_VALVE valve)

View File

@@ -1,6 +1,5 @@
menu "Irrigation system software libraries"
rsource "adc_sensor/Kconfig"
rsource "fwu/Kconfig"
rsource "modbus_server/Kconfig"
rsource "valve/Kconfig"

View File

@@ -1 +0,0 @@
zephyr_library_sources(adc_sensor.c)

View File

@@ -1,16 +0,0 @@
config ADC_SENSOR
bool "ADC sensor library"
default y
help
Enable ADC sensor library for voltage and current measurements.
if ADC_SENSOR
config ADC_SENSOR_SIMULATED
bool "Use simulated ADC readings"
default n
help
Use simulated values instead of real ADC readings.
Voltage: 12000mV, Current: 45mA
endif # ADC_SENSOR

View File

@@ -1,347 +0,0 @@
/**
* @file adc_sensor.c
* @brief Implementation of the ADC sensor library.
*
* This file contains the implementation for initializing and reading from ADC
* sensors. It currently provides simulated values for voltage and current, with
* placeholders for real hardware ADC implementation including GPIO control.
*/
#include <lib/adc_sensor.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(adc_sensor, LOG_LEVEL_INF);
// Simulated values
#define SIMULATED_VOLTAGE_MV 12000
#define SIMULATED_CURRENT_MA 45
// Devicetree node checks
#define VOLTAGE_SENSOR_NODE DT_NODELABEL(supply_voltage)
#define CURRENT_SENSOR_NODE DT_NODELABEL(motor_current)
#ifndef CONFIG_ADC_SENSOR_SIMULATED
// ADC device reference
#if DT_NODE_EXISTS(VOLTAGE_SENSOR_NODE)
#define ADC_NODE DT_PHANDLE(VOLTAGE_SENSOR_NODE, io_channels)
#define ADC_CHANNEL DT_PHA(VOLTAGE_SENSOR_NODE, io_channels, input)
#define ADC_RESOLUTION 12
#define ADC_REFERENCE_MV DT_PROP(VOLTAGE_SENSOR_NODE, reference_mv)
#define VOLTAGE_DIVIDER_RATIO \
DT_PROP(VOLTAGE_SENSOR_NODE, voltage_divider_ratio)
static const struct device *adc_dev;
static struct adc_channel_cfg adc_channel_cfg = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME_DEFAULT,
.channel_id = ADC_CHANNEL,
.differential = 0};
static struct adc_sequence adc_sequence = {
.channels = BIT(ADC_CHANNEL),
.buffer_size = sizeof(uint16_t),
.resolution = ADC_RESOLUTION,
};
static uint16_t adc_buffer;
#endif
#endif
static bool initialized = false;
#ifndef CONFIG_ADC_SENSOR_SIMULATED
// GPIO specs for voltage sensor (if devicetree nodes exist)
#if DT_NODE_EXISTS(VOLTAGE_SENSOR_NODE)
static const struct gpio_dt_spec voltage_sen_gpio =
GPIO_DT_SPEC_GET(VOLTAGE_SENSOR_NODE, sen_gpios);
static const struct gpio_dt_spec voltage_s0_gpio =
GPIO_DT_SPEC_GET(VOLTAGE_SENSOR_NODE, s0_gpios);
static const struct gpio_dt_spec voltage_s1_gpio =
GPIO_DT_SPEC_GET(VOLTAGE_SENSOR_NODE, s1_gpios);
#endif
// GPIO specs for current sensor (if devicetree nodes exist)
#if DT_NODE_EXISTS(CURRENT_SENSOR_NODE)
static const struct gpio_dt_spec current_sen_gpio =
GPIO_DT_SPEC_GET(CURRENT_SENSOR_NODE, sen_gpios);
static const struct gpio_dt_spec current_s0_gpio =
GPIO_DT_SPEC_GET(CURRENT_SENSOR_NODE, s0_gpios);
static const struct gpio_dt_spec current_s1_gpio =
GPIO_DT_SPEC_GET(CURRENT_SENSOR_NODE, s1_gpios);
#endif
/**
* @brief Configure GPIO pins for ADC sensor control
*/
static int configure_sensor_gpios(void) {
int ret = 0;
#if DT_NODE_EXISTS(VOLTAGE_SENSOR_NODE)
// Configure voltage sensor GPIOs
if (gpio_is_ready_dt(&voltage_sen_gpio)) {
ret = gpio_pin_configure_dt(&voltage_sen_gpio, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure voltage sen GPIO: %d", ret);
return ret;
}
}
if (gpio_is_ready_dt(&voltage_s0_gpio)) {
ret = gpio_pin_configure_dt(&voltage_s0_gpio, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure voltage s0 GPIO: %d", ret);
return ret;
}
}
if (gpio_is_ready_dt(&voltage_s1_gpio)) {
ret = gpio_pin_configure_dt(&voltage_s1_gpio, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure voltage s1 GPIO: %d", ret);
return ret;
}
}
#endif
#if DT_NODE_EXISTS(CURRENT_SENSOR_NODE)
// Configure current sensor GPIOs
if (gpio_is_ready_dt(&current_sen_gpio)) {
ret = gpio_pin_configure_dt(&current_sen_gpio, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure current sen GPIO: %d", ret);
return ret;
}
}
if (gpio_is_ready_dt(&current_s0_gpio)) {
ret = gpio_pin_configure_dt(&current_s0_gpio, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure current s0 GPIO: %d", ret);
return ret;
}
}
if (gpio_is_ready_dt(&current_s1_gpio)) {
ret = gpio_pin_configure_dt(&current_s1_gpio, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure current s1 GPIO: %d", ret);
return ret;
}
}
#endif
return 0;
}
/**
* @brief Set GPIO pins for voltage measurement
* @param s0_state State for S0 pin (multiplexer bit 0)
* @param s1_state State for S1 pin (multiplexer bit 1)
*/
static int set_voltage_sensor_gpios(bool enable, bool s0_state, bool s1_state) {
#if DT_NODE_EXISTS(VOLTAGE_SENSOR_NODE)
if (gpio_is_ready_dt(&voltage_sen_gpio)) {
gpio_pin_set_dt(&voltage_sen_gpio, enable ? 1 : 0);
}
if (gpio_is_ready_dt(&voltage_s0_gpio)) {
gpio_pin_set_dt(&voltage_s0_gpio, s0_state ? 1 : 0);
}
if (gpio_is_ready_dt(&voltage_s1_gpio)) {
gpio_pin_set_dt(&voltage_s1_gpio, s1_state ? 1 : 0);
}
// Delay for GPIO settling (from devicetree or default)
#if DT_NODE_HAS_PROP(VOLTAGE_SENSOR_NODE, measurement_delay_ms)
k_msleep(DT_PROP(VOLTAGE_SENSOR_NODE, measurement_delay_ms));
#else
k_msleep(5); // Default 5ms delay
#endif
#endif
return 0;
}
/**
* @brief Set GPIO pins for current measurement
* @param s0_state State for S0 pin (multiplexer bit 0)
* @param s1_state State for S1 pin (multiplexer bit 1)
*/
static int set_current_sensor_gpios(bool enable, bool s0_state, bool s1_state) {
#if DT_NODE_EXISTS(CURRENT_SENSOR_NODE)
if (gpio_is_ready_dt(&current_sen_gpio)) {
gpio_pin_set_dt(&current_sen_gpio, enable ? 1 : 0);
}
if (gpio_is_ready_dt(&current_s0_gpio)) {
gpio_pin_set_dt(&current_s0_gpio, s0_state ? 1 : 0);
}
if (gpio_is_ready_dt(&current_s1_gpio)) {
gpio_pin_set_dt(&current_s1_gpio, s1_state ? 1 : 0);
}
// Delay for GPIO settling (from devicetree or default)
#if DT_NODE_HAS_PROP(CURRENT_SENSOR_NODE, measurement_delay_ms)
k_msleep(DT_PROP(CURRENT_SENSOR_NODE, measurement_delay_ms));
#else
k_msleep(10); // Default 10ms delay
#endif
#endif
return 0;
}
#endif /* !CONFIG_ADC_SENSOR_SIMULATED */
#ifndef CONFIG_ADC_SENSOR_SIMULATED
/**
* @brief Read ADC value and convert to millivolts
* @return ADC reading in millivolts, or 0 on error
*/
static uint16_t read_adc_voltage_mv(void) {
#if DT_NODE_EXISTS(VOLTAGE_SENSOR_NODE)
int ret = adc_read(adc_dev, &adc_sequence);
if (ret < 0) {
LOG_ERR("ADC read failed: %d", ret);
return 0;
}
// Convert ADC reading to millivolts
// ADC reading is 12-bit (0-4095) representing 0 to ADC_REFERENCE_MV
uint32_t adc_value = adc_buffer;
uint32_t voltage_mv = (adc_value * ADC_REFERENCE_MV) / 4095;
// Apply voltage divider scaling
voltage_mv *= VOLTAGE_DIVIDER_RATIO;
LOG_DBG("ADC raw: %u, voltage: %u mV", adc_value, (uint16_t)voltage_mv);
return (uint16_t)voltage_mv;
#else
return 0;
#endif
}
/**
* @brief Read ADC value and convert to milliamps (for current sensor)
* @return ADC reading in milliamps, or 0 on error
*/
static uint16_t read_adc_current_ma(void) {
#if DT_NODE_EXISTS(CURRENT_SENSOR_NODE)
int ret = adc_read(adc_dev, &adc_sequence);
if (ret < 0) {
LOG_ERR("ADC read failed: %d", ret);
return 0;
}
// Convert ADC reading to millivolts first
uint32_t adc_value = adc_buffer;
uint32_t voltage_mv = (adc_value * ADC_REFERENCE_MV) / 4095;
// Convert voltage to current based on current sensor characteristics
// Assuming a linear current sensor with specific mV/mA ratio
// This will need to be calibrated for your specific current sensor
uint32_t current_ma = voltage_mv / 10; // Example: 10mV per mA
LOG_DBG("ADC raw: %u, current: %u mA", adc_value, (uint16_t)current_ma);
return (uint16_t)current_ma;
#else
return 0;
#endif
}
#endif
int adc_sensor_init(void) {
if (initialized) {
return 0;
}
#ifdef CONFIG_ADC_SENSOR_SIMULATED
LOG_INF("ADC sensor initialized (simulated mode)");
LOG_INF("Simulated values: %dmV, %dmA", SIMULATED_VOLTAGE_MV,
SIMULATED_CURRENT_MA);
#else
// Initialize GPIO pins for sensor control
int ret = configure_sensor_gpios();
if (ret < 0) {
LOG_ERR("Failed to configure sensor GPIOs: %d", ret);
return ret;
}
// Initialize ADC hardware
#if DT_NODE_EXISTS(VOLTAGE_SENSOR_NODE)
adc_dev = DEVICE_DT_GET(ADC_NODE);
if (!device_is_ready(adc_dev)) {
LOG_ERR("ADC device not ready");
return -ENODEV;
}
adc_sequence.buffer = &adc_buffer;
ret = adc_channel_setup(adc_dev, &adc_channel_cfg);
if (ret < 0) {
LOG_ERR("Failed to setup ADC channel: %d", ret);
return ret;
}
LOG_INF("ADC device ready: %s", adc_dev->name);
#endif
LOG_INF("ADC sensor initialized (real ADC mode with GPIO control)");
#if DT_NODE_EXISTS(VOLTAGE_SENSOR_NODE)
LOG_INF("Voltage sensor found in devicetree");
#endif
#if DT_NODE_EXISTS(CURRENT_SENSOR_NODE)
LOG_INF("Current sensor found in devicetree");
#endif
#endif
initialized = true;
return 0;
}
uint16_t adc_sensor_get_voltage_mv(void) {
if (!initialized) {
LOG_WRN("ADC sensor not initialized, calling adc_sensor_init()");
adc_sensor_init();
}
#ifdef CONFIG_ADC_SENSOR_SIMULATED
return SIMULATED_VOLTAGE_MV;
#else
// Set GPIOs for voltage measurement (example: s0=0, s1=0 for channel 0)
set_voltage_sensor_gpios(true, false, false);
// Read real ADC value for voltage
uint16_t voltage = read_adc_voltage_mv();
// Disable sensor after measurement to save power
set_voltage_sensor_gpios(false, false, false);
return voltage;
#endif
}
uint16_t adc_sensor_get_current_ma(void) {
if (!initialized) {
LOG_WRN("ADC sensor not initialized, calling adc_sensor_init()");
adc_sensor_init();
}
#ifdef CONFIG_ADC_SENSOR_SIMULATED
return SIMULATED_CURRENT_MA;
#else
// Set GPIOs for current measurement (example: s0=1, s1=0 for channel 1)
set_current_sensor_gpios(true, true, false);
// Read real ADC value for current
uint16_t current = read_adc_current_ma();
// Disable sensor after measurement to save power
set_current_sensor_gpios(false, false, false);
return current;
#endif
}

View File

@@ -1,58 +1,45 @@
/**
* @file fwu.c
* @brief Implementation of the Firmware Update (FWU) library.
*
* This file implements the logic for receiving a new firmware image in chunks
* over Modbus. It maintains a buffer for the incoming data, calculates the CRC
* of the received chunk, and handles commands to verify the chunk and finalize
* the update process. The actual writing to flash is simulated.
*/
#include <lib/fwu.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/crc.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/logging/log.h>
#include <lib/fwu.h>
LOG_MODULE_REGISTER(fwu, LOG_LEVEL_INF);
#define FWU_BUFFER_SIZE 256
static uint8_t fwu_buffer[FWU_BUFFER_SIZE]; // Buffer to store incoming
// firmware data chunks
static uint32_t fwu_chunk_offset =
0; // Offset for the current firmware chunk in the overall image
static uint16_t fwu_chunk_size = 0; // Size of the current firmware chunk
static uint16_t fwu_last_chunk_crc =
0; // CRC16 of the last received firmware chunk
static uint8_t fwu_buffer[FWU_BUFFER_SIZE];
static uint32_t fwu_chunk_offset = 0;
static uint16_t fwu_chunk_size = 0;
static uint16_t fwu_last_chunk_crc = 0;
void fwu_init(void) {}
void fwu_handler(uint16_t addr, uint16_t reg) {
// This is a simplified handler. In a real scenario, you would have a proper
// mapping between register addresses and actions.
if (addr == 0x0100) { // FWU_COMMAND
if (reg == 1) {
LOG_INF("FWU: Chunk at offset %u (size %u) verified.", fwu_chunk_offset,
fwu_chunk_size);
} else if (reg == 2) {
LOG_INF("FWU: Finalize command received. Rebooting (simulated).");
void fwu_handler(uint16_t addr, uint16_t reg)
{
// This is a simplified handler. In a real scenario, you would have a proper mapping
// between register addresses and actions.
if (addr == 0x0100) { // FWU_COMMAND
if (reg == 1) { LOG_INF("FWU: Chunk at offset %u (size %u) verified.", fwu_chunk_offset, fwu_chunk_size); }
else if (reg == 2) { LOG_INF("FWU: Finalize command received. Rebooting (simulated)."); }
} else if (addr == 0x0101) { // FWU_CHUNK_OFFSET_LOW
fwu_chunk_offset = (fwu_chunk_offset & 0xFFFF0000) | reg;
} else if (addr == 0x0102) { // FWU_CHUNK_OFFSET_HIGH
fwu_chunk_offset = (fwu_chunk_offset & 0x0000FFFF) | ((uint32_t)reg << 16);
} else if (addr == 0x0103) { // FWU_CHUNK_SIZE
fwu_chunk_size = (reg > FWU_BUFFER_SIZE) ? FWU_BUFFER_SIZE : reg;
} else if (addr >= 0x0180 && addr < (0x0180 + (FWU_BUFFER_SIZE / 2))) {
uint16_t index = (addr - 0x0180) * 2;
if (index < sizeof(fwu_buffer)) {
sys_put_be16(reg, &fwu_buffer[index]);
if (index + 2 >= fwu_chunk_size) {
fwu_last_chunk_crc = crc16_ccitt(0xffff, fwu_buffer, fwu_chunk_size);
LOG_INF("FWU: Chunk received, CRC is 0x%04X", fwu_last_chunk_crc);
}
}
}
} else if (addr == 0x0101) { // FWU_CHUNK_OFFSET_LOW
fwu_chunk_offset = (fwu_chunk_offset & 0xFFFF0000) | reg;
} else if (addr == 0x0102) { // FWU_CHUNK_OFFSET_HIGH
fwu_chunk_offset = (fwu_chunk_offset & 0x0000FFFF) | ((uint32_t)reg << 16);
} else if (addr == 0x0103) { // FWU_CHUNK_SIZE
fwu_chunk_size = (reg > FWU_BUFFER_SIZE) ? FWU_BUFFER_SIZE : reg;
} else if (addr >= 0x0180 && addr < (0x0180 + (FWU_BUFFER_SIZE / 2))) {
uint16_t index = (addr - 0x0180) * 2;
if (index < sizeof(fwu_buffer)) {
sys_put_be16(reg, &fwu_buffer[index]);
if (index + 2 >= fwu_chunk_size) {
fwu_last_chunk_crc = crc16_ccitt(0xffff, fwu_buffer, fwu_chunk_size);
LOG_INF("FWU: Chunk received, CRC is 0x%04X", fwu_last_chunk_crc);
}
}
}
}
uint16_t fwu_get_last_chunk_crc(void) { return fwu_last_chunk_crc; }
uint16_t fwu_get_last_chunk_crc(void)
{
return fwu_last_chunk_crc;
}

View File

@@ -1,263 +1,232 @@
/**
* @file modbus_server.c
* @brief Modbus RTU server implementation for the irrigation system slave node.
*
* This file implements the Modbus server logic, including register callbacks,
* watchdog handling, and dynamic reconfiguration. It interfaces with other
* libraries like valve control, ADC sensors, and firmware updates.
*/
#include <app_version.h>
#include <lib/adc_sensor.h>
#include <lib/fwu.h>
#include <lib/modbus_server.h>
#include <lib/valve.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/device.h>
#include <zephyr/modbus/modbus.h>
#include <zephyr/logging/log.h>
#include <zephyr/settings/settings.h>
#include <zephyr/sys/reboot.h>
#include <lib/modbus_server.h>
#include <lib/valve.h>
#include <lib/fwu.h>
#include <zephyr/usb/usb_device.h>
LOG_MODULE_REGISTER(modbus_server, LOG_LEVEL_INF);
static int modbus_iface;
static struct modbus_iface_param server_param = {
.mode = MODBUS_MODE_RTU,
.server = {.user_cb = NULL, .unit_id = 1},
.serial = {.baud = 19200, .parity = UART_CFG_PARITY_NONE},
.mode = MODBUS_MODE_RTU,
.server = {.user_cb = NULL, .unit_id = 1},
.serial = {.baud = 19200, .parity = UART_CFG_PARITY_NONE},
};
static uint16_t watchdog_timeout_s = 0;
static struct k_timer watchdog_timer;
/**
* @brief Timer handler for the Modbus watchdog.
*
* This function is called when the watchdog timer expires, indicating a loss
* of communication with the Modbus master. It triggers a fail-safe action,
* which is to close the valve.
*
* @param timer_id Pointer to the timer instance.
*/
static void watchdog_timer_handler(struct k_timer *timer_id) {
LOG_WRN("Modbus watchdog expired! Closing valve as a fail-safe.");
valve_close();
static void watchdog_timer_handler(struct k_timer *timer_id)
{
LOG_WRN("Modbus watchdog expired! Closing valve as a fail-safe.");
valve_close();
}
/**
* @brief Resets the Modbus watchdog timer.
*
* This function should be called upon receiving any valid Modbus request
* to prevent the watchdog from expiring.
*/
static inline void reset_watchdog(void) {
if (watchdog_timeout_s > 0) {
k_timer_start(&watchdog_timer, K_SECONDS(watchdog_timeout_s), K_NO_WAIT);
}
static inline void reset_watchdog(void)
{
if (watchdog_timeout_s > 0)
{
k_timer_start(&watchdog_timer, K_SECONDS(watchdog_timeout_s), K_NO_WAIT);
}
}
/**
* @brief Callback for reading Modbus holding registers.
*
* @param addr Register address.
* @param reg Pointer to store the read value.
* @return 0 on success.
*/
static int holding_reg_rd(uint16_t addr, uint16_t *reg) {
reset_watchdog();
switch (addr) {
case REG_HOLDING_MAX_OPENING_TIME_S:
*reg = valve_get_max_open_time();
break;
case REG_HOLDING_MAX_CLOSING_TIME_S:
*reg = valve_get_max_close_time();
break;
case REG_HOLDING_WATCHDOG_TIMEOUT_S:
*reg = watchdog_timeout_s;
break;
default:
*reg = 0;
break;
}
return 0;
static int holding_reg_rd(uint16_t addr, uint16_t *reg)
{
reset_watchdog();
switch (addr)
{
case REG_HOLDING_MAX_OPENING_TIME_S:
*reg = valve_get_max_open_time();
break;
case REG_HOLDING_MAX_CLOSING_TIME_S:
*reg = valve_get_max_close_time();
break;
case REG_HOLDING_WATCHDOG_TIMEOUT_S:
*reg = watchdog_timeout_s;
break;
default:
*reg = 0;
break;
}
return 0;
}
/**
* @brief Callback for writing Modbus holding registers.
*
* @param addr Register address.
* @param reg Value to write.
* @return 0 on success.
*/
static int holding_reg_wr(uint16_t addr, uint16_t reg) {
reset_watchdog();
switch (addr) {
case REG_HOLDING_VALVE_COMMAND:
if (reg == 1) {
valve_open();
} else if (reg == 2) {
valve_close();
} else if (reg == 0) {
valve_stop();
}
break;
case REG_HOLDING_MAX_OPENING_TIME_S:
valve_set_max_open_time(reg);
break;
case REG_HOLDING_MAX_CLOSING_TIME_S:
valve_set_max_close_time(reg);
break;
case REG_HOLDING_WATCHDOG_TIMEOUT_S:
watchdog_timeout_s = reg;
if (watchdog_timeout_s > 0) {
LOG_INF("Watchdog enabled with %u s timeout.", watchdog_timeout_s);
reset_watchdog();
} else {
LOG_INF("Watchdog disabled.");
k_timer_stop(&watchdog_timer);
}
break;
case REG_HOLDING_DEVICE_RESET:
if (reg == 1) {
LOG_WRN("Modbus reset command received. Rebooting...");
sys_reboot(SYS_REBOOT_WARM);
}
break;
default:
fwu_handler(addr, reg);
break;
}
return 0;
static int holding_reg_wr(uint16_t addr, uint16_t reg)
{
reset_watchdog();
switch (addr)
{
case REG_HOLDING_VALVE_COMMAND:
if (reg == 1)
{
valve_open();
}
else if (reg == 2)
{
valve_close();
}
else if (reg == 0)
{
valve_stop();
}
break;
case REG_HOLDING_MAX_OPENING_TIME_S:
valve_set_max_open_time(reg);
break;
case REG_HOLDING_MAX_CLOSING_TIME_S:
valve_set_max_close_time(reg);
break;
case REG_HOLDING_WATCHDOG_TIMEOUT_S:
watchdog_timeout_s = reg;
if (watchdog_timeout_s > 0)
{
LOG_INF("Watchdog enabled with %u s timeout.", watchdog_timeout_s);
reset_watchdog();
}
else
{
LOG_INF("Watchdog disabled.");
k_timer_stop(&watchdog_timer);
}
break;
case REG_HOLDING_DEVICE_RESET:
if (reg == 1)
{
LOG_WRN("Modbus reset command received. Rebooting...");
sys_reboot(SYS_REBOOT_WARM);
}
break;
default:
fwu_handler(addr, reg);
break;
}
return 0;
}
/**
* @brief Callback for reading Modbus input registers.
*
* @param addr Register address.
* @param reg Pointer to store the read value.
* @return 0 on success.
*/
static int input_reg_rd(uint16_t addr, uint16_t *reg) {
reset_watchdog();
uint32_t uptime_s = k_uptime_get_32() / 1000;
switch (addr) {
case REG_INPUT_VALVE_STATE_MOVEMENT:
*reg = (valve_get_movement() << 8) | (valve_get_state() & 0xFF);
break;
case REG_INPUT_MOTOR_CURRENT_MA:
*reg = adc_sensor_get_current_ma();
break;
case REG_INPUT_UPTIME_SECONDS_LOW:
*reg = (uint16_t)(uptime_s & 0xFFFF);
break;
case REG_INPUT_UPTIME_SECONDS_HIGH:
*reg = (uint16_t)(uptime_s >> 16);
break;
case REG_INPUT_SUPPLY_VOLTAGE_MV:
*reg = adc_sensor_get_voltage_mv();
break;
case REG_INPUT_FWU_LAST_CHUNK_CRC:
*reg = fwu_get_last_chunk_crc();
break;
case REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR:
*reg = (APP_VERSION_MAJOR << 8) | APP_VERSION_MINOR;
break;
case REG_INPUT_FIRMWARE_VERSION_PATCH:
*reg = APP_PATCHLEVEL;
break;
default:
*reg = 0;
break;
}
return 0;
static int input_reg_rd(uint16_t addr, uint16_t *reg)
{
reset_watchdog();
uint32_t uptime_s = k_uptime_get_32() / 1000;
switch (addr)
{
case REG_INPUT_VALVE_STATE_MOVEMENT:
*reg = (valve_get_movement() << 8) | (valve_get_state() & 0xFF);
break;
case REG_INPUT_MOTOR_CURRENT_MA:
*reg = valve_get_motor_current();
break;
case REG_INPUT_UPTIME_SECONDS_LOW:
*reg = (uint16_t)(uptime_s & 0xFFFF);
break;
case REG_INPUT_UPTIME_SECONDS_HIGH:
*reg = (uint16_t)(uptime_s >> 16);
break;
case REG_INPUT_SUPPLY_VOLTAGE_MV:
*reg = 12300;
break;
case REG_INPUT_FWU_LAST_CHUNK_CRC:
*reg = fwu_get_last_chunk_crc();
break;
case REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR:
*reg = (0 << 8) | 0;
break;
case REG_INPUT_FIRMWARE_VERSION_PATCH:
*reg = 2;
break;
default:
*reg = 0;
break;
}
return 0;
}
static struct modbus_user_callbacks mbs_cbs = {
// Modbus server callback functions
.holding_reg_rd = holding_reg_rd,
.holding_reg_wr = holding_reg_wr,
.input_reg_rd = input_reg_rd,
.holding_reg_rd = holding_reg_rd,
.holding_reg_wr = holding_reg_wr,
.input_reg_rd = input_reg_rd,
};
#define MODBUS_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(zephyr_modbus_serial)
int modbus_server_init(void) {
k_timer_init(&watchdog_timer, watchdog_timer_handler, NULL);
// Initialize ADC sensor
int ret = adc_sensor_init();
if (ret < 0) {
LOG_ERR("Failed to initialize ADC sensor: %d", ret);
return ret;
}
// Load saved settings
uint32_t saved_baudrate = 19200;
uint8_t saved_unit_id = 1;
settings_load_one("modbus/baudrate", &saved_baudrate, sizeof(saved_baudrate));
settings_load_one("modbus/unit_id", &saved_unit_id, sizeof(saved_unit_id));
// Apply loaded settings
server_param.serial.baud = saved_baudrate;
server_param.server.unit_id = saved_unit_id;
const char iface_name[] = {DEVICE_DT_NAME(MODBUS_NODE)};
int modbus_server_init(void)
{
k_timer_init(&watchdog_timer, watchdog_timer_handler, NULL);
// Load saved settings
uint32_t saved_baudrate = 19200;
uint8_t saved_unit_id = 1;
settings_load_one("modbus/baudrate", &saved_baudrate, sizeof(saved_baudrate));
settings_load_one("modbus/unit_id", &saved_unit_id, sizeof(saved_unit_id));
// Apply loaded settings
server_param.serial.baud = saved_baudrate;
server_param.server.unit_id = saved_unit_id;
const char iface_name[] = {DEVICE_DT_NAME(MODBUS_NODE)};
#if DT_NODE_HAS_COMPAT(DT_PARENT(MODBUS_NODE), zephyr_cdc_acm_uart)
const struct device *const dev = DEVICE_DT_GET(DT_PARENT(MODBUS_NODE));
uint32_t dtr = 0;
const struct device *const dev = DEVICE_DT_GET(DT_PARENT(MODBUS_NODE));
uint32_t dtr = 0;
if (!device_is_ready(dev) || usb_enable(NULL)) {
return 0;
}
if (!device_is_ready(dev) || usb_enable(NULL))
{
return 0;
}
while (!dtr) {
uart_line_ctrl_get(dev, UART_LINE_CTRL_DTR, &dtr);
k_sleep(K_MSEC(100));
}
while (!dtr)
{
uart_line_ctrl_get(dev, UART_LINE_CTRL_DTR, &dtr);
k_sleep(K_MSEC(100));
}
LOG_INF("Client connected to server on %s", dev->name);
LOG_INF("Client connected to server on %s", dev->name);
#endif
modbus_iface = modbus_iface_get_by_name(iface_name);
if (modbus_iface < 0) {
return modbus_iface;
}
server_param.server.user_cb = &mbs_cbs;
LOG_INF("Starting Modbus server: baudrate=%u, unit_id=%u", saved_baudrate,
saved_unit_id);
return modbus_init_server(modbus_iface, server_param);
modbus_iface = modbus_iface_get_by_name(iface_name);
if (modbus_iface < 0)
{
return modbus_iface;
}
server_param.server.user_cb = &mbs_cbs;
LOG_INF("Starting Modbus server: baudrate=%u, unit_id=%u", saved_baudrate, saved_unit_id);
return modbus_init_server(modbus_iface, server_param);
}
int modbus_reconfigure(uint32_t baudrate, uint8_t unit_id) {
// Update parameters
server_param.serial.baud = baudrate;
server_param.server.unit_id = unit_id;
int modbus_reconfigure(uint32_t baudrate, uint8_t unit_id)
{
// Update parameters
server_param.serial.baud = baudrate;
server_param.server.unit_id = unit_id;
// Try to reinitialize - this should work for most cases
int ret = modbus_init_server(modbus_iface, server_param);
// Try to reinitialize - this should work for most cases
int ret = modbus_init_server(modbus_iface, server_param);
if (ret == 0) {
settings_save_one("modbus/baudrate", &baudrate, sizeof(baudrate));
settings_save_one("modbus/unit_id", &unit_id, sizeof(unit_id));
LOG_INF("Modbus reconfigured: baudrate=%u, unit_id=%u", baudrate, unit_id);
} else {
LOG_ERR("Failed to reconfigure Modbus: %d", ret);
LOG_INF("Modbus reconfiguration requires restart to take effect");
if (ret == 0)
{
settings_save_one("modbus/baudrate", &baudrate, sizeof(baudrate));
settings_save_one("modbus/unit_id", &unit_id, sizeof(unit_id));
LOG_INF("Modbus reconfigured: baudrate=%u, unit_id=%u", baudrate, unit_id);
}
else
{
LOG_ERR("Failed to reconfigure Modbus: %d", ret);
LOG_INF("Modbus reconfiguration requires restart to take effect");
// Save settings for next boot
settings_save_one("modbus/baudrate", &baudrate, sizeof(baudrate));
settings_save_one("modbus/unit_id", &unit_id, sizeof(unit_id));
LOG_INF("Settings saved. Type 'reset' to restart the device and apply the change.");
return 0; // Return success since settings are saved
}
// Save settings for next boot
settings_save_one("modbus/baudrate", &baudrate, sizeof(baudrate));
settings_save_one("modbus/unit_id", &unit_id, sizeof(unit_id));
LOG_INF(
"Settings saved. Type 'reset' to restart the device and apply the "
"change.");
return 0; // Return success since settings are saved
}
return ret;
return ret;
}
uint32_t modbus_get_baudrate(void) { return server_param.serial.baud; }

View File

@@ -1,176 +1,119 @@
/**
* @file shell_modbus.c
* @brief Provides shell commands for Modbus and valve configuration.
*
* This file implements a set of commands for the Zephyr shell to allow
* runtime configuration of the Modbus server (baudrate, slave ID) and the
* valve (max opening/closing times). The settings are persisted to non-volatile
* storage.
*/
#include <zephyr/shell/shell.h>
#include <stdlib.h>
#include <lib/modbus_server.h>
#include <lib/valve.h>
#include <stdlib.h>
#include <zephyr/shell/shell.h>
/**
* @brief Shell command to set the Modbus baudrate.
*
* @param sh The shell instance.
* @param argc Argument count.
* @param argv Argument values.
* @return 0 on success, -EINVAL on error.
*/
static int cmd_modbus_set_baud(const struct shell *sh, size_t argc,
char **argv) {
if (argc != 2) {
shell_error(sh, "Usage: set_baud <baudrate>");
return -EINVAL;
}
static int cmd_modbus_set_baud(const struct shell *sh, size_t argc, char **argv)
{
if (argc != 2) {
shell_error(sh, "Usage: set_baud <baudrate>");
return -EINVAL;
}
uint32_t new_baud = (uint32_t)strtoul(argv[1], NULL, 10);
const uint32_t valid_baud_rates[] = {1200, 2400, 4800, 9600,
19200, 38400, 57600, 115200};
bool is_valid = false;
uint32_t new_baud = (uint32_t)strtoul(argv[1], NULL, 10);
const uint32_t valid_baud_rates[] = {1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200};
bool is_valid = false;
for (int i = 0; i < ARRAY_SIZE(valid_baud_rates); i++) {
if (new_baud == valid_baud_rates[i]) {
is_valid = true;
break;
for (int i = 0; i < ARRAY_SIZE(valid_baud_rates); i++) {
if (new_baud == valid_baud_rates[i]) {
is_valid = true;
break;
}
}
if (!is_valid) {
char error_msg[128];
int offset = snprintf(error_msg, sizeof(error_msg), "Invalid baudrate. Valid rates are: ");
for (int i = 0; i < ARRAY_SIZE(valid_baud_rates); i++) {
offset += snprintf(error_msg + offset, sizeof(error_msg) - offset, "%u ", valid_baud_rates[i]);
}
shell_error(sh, "%s", error_msg);
return -EINVAL;
}
if (modbus_reconfigure(new_baud, modbus_get_unit_id()) != 0) {
shell_error(sh, "Failed to apply new baudrate");
} else {
shell_print(sh, "Modbus baudrate set to: %u (and saved)", new_baud);
}
return 0;
}
static int cmd_modbus_set_id(const struct shell *sh, size_t argc, char **argv)
{
if (argc != 2) {
shell_error(sh, "Usage: set_id <slave_id>");
return -EINVAL;
}
uint32_t new_id_u32 = (uint32_t)strtoul(argv[1], NULL, 10);
if (new_id_u32 == 0 || new_id_u32 > 247) {
shell_error(sh, "Invalid slave ID: %s. Must be between 1 and 247.", argv[1]);
return -EINVAL;
}
uint8_t new_id = (uint8_t)new_id_u32;
if (modbus_reconfigure(modbus_get_baudrate(), new_id) != 0) {
shell_error(sh, "Failed to apply new slave ID");
} else {
shell_print(sh, "Modbus slave ID set to: %u (and saved)", new_id);
}
return 0;
}
static int cmd_valve_set_open_time(const struct shell *sh, size_t argc, char **argv)
{
if (argc != 2) {
shell_error(sh, "Usage: set_open_time <seconds>");
return -EINVAL;
}
}
if (!is_valid) {
char error_msg[128];
int offset = snprintf(error_msg, sizeof(error_msg),
"Invalid baudrate. Valid rates are: ");
for (int i = 0; i < ARRAY_SIZE(valid_baud_rates); i++) {
offset += snprintf(error_msg + offset, sizeof(error_msg) - offset, "%u ",
valid_baud_rates[i]);
uint16_t seconds = (uint16_t)strtoul(argv[1], NULL, 10);
valve_set_max_open_time(seconds);
shell_print(sh, "Max opening time set to: %u seconds (and saved)", seconds);
return 0;
}
static int cmd_valve_set_close_time(const struct shell *sh, size_t argc, char **argv)
{
if (argc != 2) {
shell_error(sh, "Usage: set_close_time <seconds>");
return -EINVAL;
}
shell_error(sh, "%s", error_msg);
return -EINVAL;
}
if (modbus_reconfigure(new_baud, modbus_get_unit_id()) != 0) {
shell_error(sh, "Failed to apply new baudrate");
} else {
shell_print(sh, "Modbus baudrate set to: %u (and saved)", new_baud);
}
uint16_t seconds = (uint16_t)strtoul(argv[1], NULL, 10);
valve_set_max_close_time(seconds);
shell_print(sh, "Max closing time set to: %u seconds (and saved)", seconds);
return 0;
return 0;
}
/**
* @brief Shell command to set the Modbus slave ID.
*
* @param sh The shell instance.
* @param argc Argument count.
* @param argv Argument values.
* @return 0 on success, -EINVAL on error.
*/
static int cmd_modbus_set_id(const struct shell *sh, size_t argc, char **argv) {
if (argc != 2) {
shell_error(sh, "Usage: set_id <slave_id>");
return -EINVAL;
}
uint32_t new_id_u32 = (uint32_t)strtoul(argv[1], NULL, 10);
if (new_id_u32 == 0 || new_id_u32 > 247) {
shell_error(sh, "Invalid slave ID: %s. Must be between 1 and 247.",
argv[1]);
return -EINVAL;
}
uint8_t new_id = (uint8_t)new_id_u32;
if (modbus_reconfigure(modbus_get_baudrate(), new_id) != 0) {
shell_error(sh, "Failed to apply new slave ID");
} else {
shell_print(sh, "Modbus slave ID set to: %u (and saved)", new_id);
}
return 0;
}
/**
* @brief Shell command to set the valve's maximum opening time.
*
* @param sh The shell instance.
* @param argc Argument count.
* @param argv Argument values.
* @return 0 on success, -EINVAL on error.
*/
static int cmd_valve_set_open_time(const struct shell *sh, size_t argc,
char **argv) {
if (argc != 2) {
shell_error(sh, "Usage: set_open_time <seconds>");
return -EINVAL;
}
uint16_t seconds = (uint16_t)strtoul(argv[1], NULL, 10);
valve_set_max_open_time(seconds);
shell_print(sh, "Max opening time set to: %u seconds (and saved)", seconds);
return 0;
}
/**
* @brief Shell command to set the valve's maximum closing time.
*
* @param sh The shell instance.
* @param argc Argument count.
* @param argv Argument values.
* @return 0 on success, -EINVAL on error.
*/
static int cmd_valve_set_close_time(const struct shell *sh, size_t argc,
char **argv) {
if (argc != 2) {
shell_error(sh, "Usage: set_close_time <seconds>");
return -EINVAL;
}
uint16_t seconds = (uint16_t)strtoul(argv[1], NULL, 10);
valve_set_max_close_time(seconds);
shell_print(sh, "Max closing time set to: %u seconds (and saved)", seconds);
return 0;
}
/**
* @brief Shell command to show the current configuration.
*
* @param sh The shell instance.
* @param argc Argument count.
* @param argv Argument values.
* @return 0 on success.
*/
static int cmd_config_show(const struct shell *sh, size_t argc, char **argv) {
shell_print(sh, "Current Modbus Configuration:");
shell_print(sh, " Baudrate: %u", modbus_get_baudrate());
shell_print(sh, " Slave ID: %u", modbus_get_unit_id());
shell_print(sh, "Current Valve Configuration:");
shell_print(sh, " Max Opening Time: %u s", valve_get_max_open_time());
shell_print(sh, " Max Closing Time: %u s", valve_get_max_close_time());
return 0;
static int cmd_config_show(const struct shell *sh, size_t argc, char **argv)
{
shell_print(sh, "Current Modbus Configuration:");
shell_print(sh, " Baudrate: %u", modbus_get_baudrate());
shell_print(sh, " Slave ID: %u", modbus_get_unit_id());
shell_print(sh, "Current Valve Configuration:");
shell_print(sh, " Max Opening Time: %u s", valve_get_max_open_time());
shell_print(sh, " Max Closing Time: %u s", valve_get_max_close_time());
return 0;
}
SHELL_STATIC_SUBCMD_SET_CREATE(sub_modbus_cmds,
SHELL_CMD(set_baud, NULL, "Set Modbus baudrate",
cmd_modbus_set_baud),
SHELL_CMD(set_id, NULL, "Set Modbus slave ID",
cmd_modbus_set_id),
SHELL_SUBCMD_SET_END);
SHELL_CMD(set_baud, NULL, "Set Modbus baudrate", cmd_modbus_set_baud),
SHELL_CMD(set_id, NULL, "Set Modbus slave ID", cmd_modbus_set_id),
SHELL_SUBCMD_SET_END
);
SHELL_STATIC_SUBCMD_SET_CREATE(sub_valve_cmds,
SHELL_CMD(set_open_time, NULL,
"Set max valve opening time",
cmd_valve_set_open_time),
SHELL_CMD(set_close_time, NULL,
"Set max valve closing time",
cmd_valve_set_close_time),
SHELL_SUBCMD_SET_END);
SHELL_CMD(set_open_time, NULL, "Set max valve opening time", cmd_valve_set_open_time),
SHELL_CMD(set_close_time, NULL, "Set max valve closing time", cmd_valve_set_close_time),
SHELL_SUBCMD_SET_END
);
SHELL_CMD_REGISTER(modbus, &sub_modbus_cmds, "Modbus configuration", NULL);
SHELL_CMD_REGISTER(valve, &sub_valve_cmds, "Valve configuration", NULL);
SHELL_CMD_REGISTER(show_config, NULL, "Show all configurations",
cmd_config_show);
SHELL_CMD_REGISTER(show_config, NULL, "Show all configurations", cmd_config_show);

View File

@@ -1,30 +1,12 @@
/**
* @file shell_system.c
* @brief Provides basic system-level shell commands.
*
* This file implements essential system commands for the Zephyr shell,
* such as rebooting the device.
*/
#include <zephyr/shell/shell.h>
#include <zephyr/sys/reboot.h>
/**
* @brief Shell command to reset the system.
*
* This command performs a warm reboot of the device after a short delay
* to ensure the shell message is printed.
*
* @param sh The shell instance.
* @param argc Argument count.
* @param argv Argument values.
* @return 0 on success.
*/
static int cmd_reset(const struct shell *sh, size_t argc, char **argv) {
shell_print(sh, "Rebooting system...");
k_sleep(K_MSEC(100)); // Allow the shell to print the message
sys_reboot(SYS_REBOOT_WARM);
return 0;
static int cmd_reset(const struct shell *sh, size_t argc, char **argv)
{
shell_print(sh, "Rebooting system...");
k_sleep(K_MSEC(100)); // Allow the shell to print the message
sys_reboot(SYS_REBOOT_WARM);
return 0;
}
SHELL_CMD_REGISTER(reset, NULL, "Reboot the system", cmd_reset);

View File

@@ -1,20 +1,22 @@
/**
* @file valve.c
* @brief Implementation of the motorized valve control library.
*
* This file contains the logic for controlling a motorized valve using a
* VND7050AJ high-side driver. It uses a delayed work item to handle the
* safety timeouts for opening and closing operations.
*/
#include <lib/valve.h>
#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <zephyr/logging/log.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/settings/settings.h>
#include <zephyr/drivers/adc.h>
#include <lib/valve.h>
LOG_MODULE_REGISTER(valve, LOG_LEVEL_INF);
LOG_MODULE_REGISTER(valve, LOG_LEVEL_DBG);
// ADC configuration for MULTISENSE (PA0)
static const struct device *adc_dev = DEVICE_DT_GET(DT_NODELABEL(adc1));
static const struct adc_channel_cfg adc_channel_cfg = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL, // STM32 only supports internal ref (1.2V)
.acquisition_time = ADC_ACQ_TIME_DEFAULT, // Use default acquisition time
.channel_id = 1, // ADC1_IN1 (PA0)
.differential = 0,
};
static const struct valve_gpios valve_gpios = {
.in0 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), in0_gpios),
@@ -29,93 +31,186 @@ static enum valve_state current_state = VALVE_STATE_CLOSED;
static enum valve_movement current_movement = VALVE_MOVEMENT_IDLE;
static uint16_t max_opening_time_s = 60;
static uint16_t max_closing_time_s = 60;
static struct k_work_delayable
valve_work; // Work item for scheduling valve movement timeouts
static struct k_work_delayable valve_work;
/**
* @brief Work handler for valve movement timeouts.
*
* This function is executed when the valve's movement timer expires.
* It stops the motor to prevent damage and updates the valve's state.
*
* @param work Pointer to the k_work item.
*/
static void valve_work_handler(struct k_work *work) {
gpio_pin_set_dt(&valve_gpios.in0, 0);
gpio_pin_set_dt(&valve_gpios.in1, 0);
gpio_pin_set_dt(&valve_gpios.rst, 0);
if (current_movement == VALVE_MOVEMENT_OPENING) {
LOG_INF("Valve finished opening");
} else if (current_movement == VALVE_MOVEMENT_CLOSING) {
current_state = VALVE_STATE_CLOSED;
LOG_INF("Valve finished closing");
}
current_movement = VALVE_MOVEMENT_IDLE;
}
void valve_init(void) {
k_work_init_delayable(&valve_work, valve_work_handler);
settings_load_one("valve/max_open_time", &max_opening_time_s,
sizeof(max_opening_time_s));
settings_load_one("valve/max_close_time", &max_closing_time_s,
sizeof(max_closing_time_s));
gpio_pin_configure_dt(&valve_gpios.in0, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&valve_gpios.in1, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&valve_gpios.rst,
GPIO_OUTPUT_ACTIVE); // Keep VND7050AJ out of reset
gpio_pin_configure_dt(&valve_gpios.sen, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&valve_gpios.s0,
GPIO_OUTPUT_INACTIVE); // S0 select pin - output
gpio_pin_configure_dt(&valve_gpios.s1,
GPIO_OUTPUT_INACTIVE); // S1 select pin - output
LOG_INF("Valve initialized: max_open=%us, max_close=%us", max_opening_time_s,
max_closing_time_s);
}
void valve_open(void) {
if (current_state == VALVE_STATE_CLOSED) {
gpio_pin_set_dt(&valve_gpios.rst, 1);
gpio_pin_set_dt(&valve_gpios.in1, 0);
gpio_pin_set_dt(&valve_gpios.in0, 1);
current_state = VALVE_STATE_OPEN;
current_movement = VALVE_MOVEMENT_OPENING;
k_work_schedule(&valve_work, K_MSEC(max_opening_time_s * 1000 * 0.9));
}
}
void valve_close(void) {
if (current_state == VALVE_STATE_OPEN) {
gpio_pin_set_dt(&valve_gpios.rst, 1);
static void valve_work_handler(struct k_work *work)
{
gpio_pin_set_dt(&valve_gpios.in0, 0);
gpio_pin_set_dt(&valve_gpios.in1, 1);
current_movement = VALVE_MOVEMENT_CLOSING;
k_work_schedule(&valve_work, K_MSEC(max_closing_time_s * 1000 * 0.9));
}
gpio_pin_set_dt(&valve_gpios.in1, 0);
gpio_pin_set_dt(&valve_gpios.rst, 0);
if (current_movement == VALVE_MOVEMENT_OPENING) {
LOG_INF("Valve finished opening");
} else if (current_movement == VALVE_MOVEMENT_CLOSING) {
current_state = VALVE_STATE_CLOSED;
LOG_INF("Valve finished closing");
}
current_movement = VALVE_MOVEMENT_IDLE;
}
void valve_stop(void) {
k_work_cancel_delayable(&valve_work);
current_movement = VALVE_MOVEMENT_IDLE;
void valve_init(void)
{
k_work_init_delayable(&valve_work, valve_work_handler);
settings_load_one("valve/max_open_time", &max_opening_time_s, sizeof(max_opening_time_s));
settings_load_one("valve/max_close_time", &max_closing_time_s, sizeof(max_closing_time_s));
// Initialize ADC for MULTISENSE
if (!device_is_ready(adc_dev)) {
LOG_ERR("ADC device not ready");
return;
}
int ret = adc_channel_setup(adc_dev, &adc_channel_cfg);
if (ret < 0) {
LOG_ERR("Could not setup ADC channel (%d)", ret);
return;
}
gpio_pin_configure_dt(&valve_gpios.in0, GPIO_OUTPUT_INACTIVE); // IN0 control pin - output, deactivate
gpio_pin_configure_dt(&valve_gpios.in1, GPIO_OUTPUT_INACTIVE); // IN1 control pin - output, deactivate
gpio_pin_configure_dt(&valve_gpios.rst, GPIO_OUTPUT_INACTIVE); // Keep VND7050AJ in reset
gpio_pin_configure_dt(&valve_gpios.sen, GPIO_OUTPUT_INACTIVE); // Sensor enable pin - output, inactive
// S0 and S1 pins are used for selecting the valve state, they are initially inactive
// and will be set to active when the valve is opened or closed.
gpio_pin_configure_dt(&valve_gpios.s0, GPIO_OUTPUT_INACTIVE); // S0 select pin - output
gpio_pin_configure_dt(&valve_gpios.s1, GPIO_OUTPUT_INACTIVE); // S1 select pin - output
LOG_INF("Valve initialized: max_open=%us, max_close=%us", max_opening_time_s, max_closing_time_s);
}
void valve_open(void)
{
if (current_state == VALVE_STATE_CLOSED) {
gpio_pin_set_dt(&valve_gpios.rst, 1);
gpio_pin_set_dt(&valve_gpios.in1, 0);
gpio_pin_set_dt(&valve_gpios.in0, 1);
current_state = VALVE_STATE_OPEN;
current_movement = VALVE_MOVEMENT_OPENING;
k_work_schedule(&valve_work, K_MSEC(max_opening_time_s * 1000 * 0.9));
}
}
void valve_close(void)
{
if (current_state == VALVE_STATE_OPEN) {
gpio_pin_set_dt(&valve_gpios.rst, 1);
gpio_pin_set_dt(&valve_gpios.in0, 0);
gpio_pin_set_dt(&valve_gpios.in1, 1);
current_movement = VALVE_MOVEMENT_CLOSING;
k_work_schedule(&valve_work, K_MSEC(max_closing_time_s * 1000 * 0.9));
}
}
void valve_stop(void)
{
k_work_cancel_delayable(&valve_work);
current_movement = VALVE_MOVEMENT_IDLE;
}
enum valve_state valve_get_state(void) { return current_state; }
enum valve_movement valve_get_movement(void) { return current_movement; }
uint16_t valve_get_motor_current(void) {
return (current_movement != VALVE_MOVEMENT_IDLE) ? 150 : 10;
uint16_t valve_get_motor_current(void) { return (current_movement != VALVE_MOVEMENT_IDLE) ? 150 : 10; }
uint16_t valve_get_supply_voltage(void)
{
LOG_INF("=== ADC TEST MODE - PA0 LAB SUPPLY TEST ===");
LOG_INF("Connect lab supply to PA0. Recommended: 1.0V");
LOG_INF("Expected raw value for 1.0V: ~2007 (using 2.048V VREFBUF)");
LOG_INF("ADC range: 0-2.048V (STM32G431 VREFBUF internal reference)");
LOG_INF("");
// No VND7050AJ configuration - pure ADC test
// Just make sure pins are in safe state
gpio_pin_configure_dt(&valve_gpios.rst, GPIO_OUTPUT);
gpio_pin_configure_dt(&valve_gpios.sen, GPIO_OUTPUT);
gpio_pin_configure_dt(&valve_gpios.s0, GPIO_OUTPUT);
gpio_pin_configure_dt(&valve_gpios.s1, GPIO_OUTPUT);
gpio_pin_configure_dt(&valve_gpios.in0, GPIO_OUTPUT);
gpio_pin_configure_dt(&valve_gpios.in1, GPIO_OUTPUT);
// Set all VND7050AJ pins LOW for safety
gpio_pin_set_dt(&valve_gpios.rst, 0);
gpio_pin_set_dt(&valve_gpios.s0, 0);
gpio_pin_set_dt(&valve_gpios.s1, 0);
gpio_pin_set_dt(&valve_gpios.sen, 0);
gpio_pin_set_dt(&valve_gpios.in0, 0);
gpio_pin_set_dt(&valve_gpios.in1, 0);
LOG_INF("VND7050AJ disabled - all pins LOW");
LOG_INF("PA0 is now isolated for lab supply testing");
k_msleep(100);
// Setup simple ADC sequence
int16_t buf;
struct adc_sequence sequence = {
.buffer = &buf,
.buffer_size = sizeof(buf),
.channels = BIT(adc_channel_cfg.channel_id),
.resolution = 12,
};
LOG_INF("Starting continuous ADC readings every 500ms...");
// Continuous monitoring loop with improved stability
int reading_count = 0;
int32_t samples[10]; // Buffer for averaging
while (1) {
// Take multiple samples and average them for stability
int valid_samples = 0;
int32_t sum = 0;
for (int i = 0; i < 10; i++) {
k_msleep(50); // Longer delay between samples for stability
int adc_ret = adc_read(adc_dev, &sequence);
if (adc_ret == 0 && buf > 100) { // Filter out near-zero readings (floating input)
samples[i] = buf;
sum += buf;
valid_samples++;
} else {
LOG_WRN("Sample %d invalid: raw=%d, ret=%d", i, buf, adc_ret);
samples[i] = 0; // Mark as invalid
}
}
if (valid_samples > 0) {
// Calculate average
int32_t avg_raw = sum / valid_samples;
// Calculate voltage using the correct VREFBUF reference (2.048V)
int32_t pa0_mv = (avg_raw * 2048) / 4096; // Using 2.048V VREFBUF
// Calculate standard deviation to show stability
int32_t variance = 0;
for (int i = 0; i < valid_samples; i++) {
int32_t diff = samples[i] - avg_raw;
variance += diff * diff;
}
int32_t std_dev = (valid_samples > 1) ? variance / (valid_samples - 1) : 0;
// Find min/max for this sample set
int32_t min_raw = samples[0], max_raw = samples[0];
for (int i = 1; i < valid_samples; i++) {
if (samples[i] < min_raw) min_raw = samples[i];
if (samples[i] > max_raw) max_raw = samples[i];
}
LOG_INF("Reading %d: avg_raw=%d (%dmV) | range=%d-%d | std_dev=%d | samples=%d/10",
reading_count, (int)avg_raw, (int)pa0_mv,
(int)min_raw, (int)max_raw, (int)std_dev, valid_samples);
} else {
LOG_ERR("Reading %d: All ADC samples failed", reading_count);
}
reading_count++;
k_msleep(400); // Wait before next reading set
}
return 0; // Never reached
}
void valve_set_max_open_time(uint16_t seconds) {
max_opening_time_s = seconds;
settings_save_one("valve/max_open_time", &max_opening_time_s,
sizeof(max_opening_time_s));
}
void valve_set_max_close_time(uint16_t seconds) {
max_closing_time_s = seconds;
settings_save_one("valve/max_close_time", &max_closing_time_s,
sizeof(max_closing_time_s));
}
void valve_set_max_open_time(uint16_t seconds) { max_opening_time_s = seconds; settings_save_one("valve/max_open_time", &max_opening_time_s, sizeof(max_opening_time_s)); }
void valve_set_max_close_time(uint16_t seconds) { max_closing_time_s = seconds; settings_save_one("valve/max_close_time", &max_closing_time_s, sizeof(max_closing_time_s)); }
uint16_t valve_get_max_open_time(void) { return max_opening_time_s; }
uint16_t valve_get_max_close_time(void) { return max_closing_time_s; }

View File

@@ -0,0 +1,43 @@
#!/usr/bin/env python3
import serial
import time
import sys
import argparse
def monitor_serial(port):
try:
# Open serial connection
ser = serial.Serial(port, 115200, timeout=1)
print(f"Connected to {port}")
# Send reset command
ser.write(b'reset\n')
print("Sent reset command")
# Wait a bit and then read output
time.sleep(0.5)
# Read output for 10 seconds
start_time = time.time()
while 1: #time.time() - start_time < 10:
if ser.in_waiting > 0:
data = ser.read(ser.in_waiting)
try:
text = data.decode('utf-8', errors='ignore')
print(text, end='')
except:
print(f"Raw bytes: {data}")
time.sleep(0.1)
ser.close()
print("\nSerial monitor closed")
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Serial monitor.')
parser.add_argument('-p', '--port', help='Serial port to connect to', required=True)
args = parser.parse_args()
monitor_serial(args.port)

View File

@@ -0,0 +1,55 @@
#!/usr/bin/env python3
import serial
import time
import sys
def monitor_serial_with_reset():
try:
# Open serial port
ser = serial.Serial('/dev/ttyACM1', 115200, timeout=1)
print("Serial port opened successfully")
# Clear any existing data
ser.flushInput()
ser.flushOutput()
# Send reset command
print("Sending reset command...")
ser.write(b"reset\n")
time.sleep(0.1)
# Read output for 10 seconds
print("Reading serial output...")
start_time = time.time()
output_lines = []
while time.time() - start_time < 10:
if ser.in_waiting > 0:
try:
line = ser.readline().decode('utf-8', errors='replace').strip()
if line:
print(f"[{time.time() - start_time:.3f}s] {line}")
output_lines.append(line)
except Exception as e:
print(f"Error reading line: {e}")
time.sleep(0.01)
ser.close()
print("\nSerial monitoring complete")
# Summary
print("\n=== SUMMARY ===")
supply_voltage_lines = [line for line in output_lines if "Supply voltage" in line]
if supply_voltage_lines:
print("Supply voltage readings:")
for line in supply_voltage_lines:
print(f" {line}")
else:
print("No supply voltage readings found")
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__":
monitor_serial_with_reset()