Compare commits
10 Commits
c3c23efc95
...
adc-testin
| Author | SHA1 | Date | |
|---|---|---|---|
| 84e7d02db8 | |||
| cc6b4488ee | |||
| 928a176e7c | |||
| 8255b2a672 | |||
| d48281436e | |||
| dcb73c0a25 | |||
| 2c21f1f9cb | |||
| a2afec52e2 | |||
| 2cc258e8e2 | |||
| a77298b3a6 |
@@ -39,7 +39,7 @@ Die Slave-Nodes sind die Arbeitseinheiten im Feld. Um bei der Fertigung kleiner
|
||||
|
||||
* **Mikrocontroller:** Ein `STM32G431PB`. Dieser ist zwar leistungsstark, bietet aber alle nötigen Peripherien (mehrere UARTs, ADCs, CAN) und ermöglicht ein einheitliches Hardware- und Software-Design.
|
||||
* **Peripherie pro Node:**
|
||||
* **Zwei High-Side Ausgänge (+12V):** Realisiert über einen `VND7050AJ`. Perfekt zur Ansteuerung der 12V-Motorventile (`Öffnen`/`Schliessen`). Die `Sense`-Leitung des Treibers wird über einen AD-Wandler ausgelesen, um durch Messung des Motorstroms eine Endlagen-Erkennung ohne physische Endschalter zu realisieren (Motorstrom im Stillstand ≈ 0). Zusätzlich können die Temperatur und die Versorgungsspannung des Treibers ausgelesen werden.
|
||||
* **Zwei High-Side Ausgänge (+12V):** Realisiert über einen `VND7050AJ`. Perfekt zur Ansteuerung der 12V-Motorventile (`Öffnen`/`Schliessen`). Die `Sense`-Leitung des Treibers wird über einen AD-Wandler ausgelesen, um durch Messung des Motorstroms eine Endlagen-Erkennung ohne physische Endschalter zu realisieren (Motorstrom im Stillstand ≈ 0).
|
||||
* **Zwei Low-Side Ausgänge (0V):** Über N-Kanal-MOSFETs geschaltete Ausgänge. Nutzbar zur Ansteuerung von 12V-LEDs in Tastern oder zum Schalten des Halbleiter-Relais für die Pumpe.
|
||||
* **Zwei digitale Eingänge:** Direkte, geschützte Eingänge am Controller zum Anschluss von Tastern oder den kapazitiven NPN-Sensoren.
|
||||
|
||||
|
||||
@@ -29,11 +29,10 @@ Alle Register sind in einer einzigen, durchgehenden Liste pro Register-Typ (`Inp
|
||||
|
||||
| Adresse (hex) | Name | Zugehörigkeit | Beschreibung |
|
||||
| :------------ | :----------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **0x0000** | `VALVE_STATE_MOVEMENT` | Ventil | Kombiniertes Status-Register. **High-Byte**: Bewegung (`0`=Idle, `1`=Öffnet, `2`=Schliesst, `3`=Fehler). **Low-Byte**: Zustand (`0`=Geschlossen, `1`=Geöffnet). |
|
||||
| **0x0001** | `MOTORSTROM_OPEN_MA` | Ventil | Motorstrom beim Öffnen in Milliampere (mA). |
|
||||
| **0x0002** | `MOTORSTROM_CLOSE_MA` | Ventil | Motorstrom beim Schließen in Milliampere (mA). |
|
||||
| **0x0020** | `DIGITAL_INPUTS_STATE` | Eingänge | Bitmaske der digitalen Eingänge. Bit 0: Eingang 1, Bit 1: Eingang 2. `1`=Aktiv. |
|
||||
| **0x0021** | `BUTTON_EVENTS` | Eingänge | Event-Flags für Taster (Clear-on-Read). Bit 0: Taster 1 gedrückt. Bit 1: Taster 2 gedrückt. |
|
||||
| **0x0000** | `VENTIL_ZUSTAND_BEWEGUNG` | Ventil | Kombiniertes Status-Register. **High-Byte**: Bewegung (`0`=Idle, `1`=Öffnet, `2`=Schliesst, `3`=Fehler). **Low-Byte**: Zustand (`0`=Geschlossen, `1`=Geöffnet). |
|
||||
| **0x0001** | `MOTORSTROM_MA` | Ventil | Aktueller Motorstrom in Milliampere (mA). |
|
||||
| **0x0020** | `DIGITAL_EINGAENGE_ZUSTAND` | Eingänge | Bitmaske der digitalen Eingänge. Bit 0: Eingang 1, Bit 1: Eingang 2. `1`=Aktiv. |
|
||||
| **0x0021** | `TASTER_EVENTS` | Eingänge | Event-Flags für Taster (Clear-on-Read). Bit 0: Taster 1 gedrückt. Bit 1: Taster 2 gedrückt. |
|
||||
| **0x00F0** | `FIRMWARE_VERSION_MAJOR_MINOR` | System | z.B. `0x0102` für v1.2. |
|
||||
| **0x00F1** | `FIRMWARE_VERSION_PATCH` | System | z.B. `3` für v1.2.3. |
|
||||
| **0x00F2** | `DEVICE_STATUS` | System | `0`=OK, `1`=Allgemeiner Fehler. |
|
||||
@@ -46,12 +45,10 @@ Alle Register sind in einer einzigen, durchgehenden Liste pro Register-Typ (`Inp
|
||||
|
||||
| Adresse (hex) | Name | Zugehörigkeit | Beschreibung |
|
||||
| :------------ | :---------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **0x0000** | `VALVE_COMMAND` | Ventil | `1`=Öffnen, `2`=Schliessen, `0`=Bewegung stoppen. |
|
||||
| **0x0001** | `MAX_OPENING_TIME_S` | Ventil | Sicherheits-Timeout in Sekunden für den Öffnen-Vorgang. |
|
||||
| **0x0002** | `MAX_CLOSING_TIME_S` | Ventil | Sicherheits-Timeout in Sekunden für den Schliessen-Vorgang. |
|
||||
| **0x0003** | `END_CURRENT_THRESHOLD_OPEN_MA` | Ventil | Minimaler Stromschwellenwert in mA zur Endlagenerkennung beim Öffnen. |
|
||||
| **0x0004** | `END_CURRENT_THRESHOLD_CLOSE_MA` | Ventil | Minimaler Stromschwellenwert in mA zur Endlagenerkennung beim Schliessen. |
|
||||
| **0x0010** | `DIGITAL_OUTPUTS_STATE` | Ausgänge | Bitmaske zum Lesen und Schreiben der Ausgänge. Bit 0: Ausgang 1, Bit 1: Ausgang 2. `1`=AN, `0`=AUS. |
|
||||
| **0x0000** | `VENTIL_BEFEHL` | Ventil | `1`=Öffnen, `2`=Schliessen, `0`=Bewegung stoppen. |
|
||||
| **0x0001** | `MAX_OEFFNUNGSZEIT_S` | Ventil | Sicherheits-Timeout in Sekunden für den Öffnen-Vorgang. |
|
||||
| **0x0002** | `MAX_SCHLIESSZEIT_S` | Ventil | Sicherheits-Timeout in Sekunden für den Schliessen-Vorgang. |
|
||||
| **0x0010** | `DIGITAL_AUSGAENGE_ZUSTAND` | Ausgänge | Bitmaske zum Lesen und Schreiben der Ausgänge. Bit 0: Ausgang 1, Bit 1: Ausgang 2. `1`=AN, `0`=AUS. |
|
||||
| **0x00F0** | `WATCHDOG_TIMEOUT_S` | System | Timeout des Fail-Safe-Watchdogs in Sekunden. `0`=Deaktiviert. |
|
||||
| **0x00F1** | `DEVICE_RESET` | System | Schreibt `1` um das Gerät neu zu starten. |
|
||||
| **0x0100** | `FWU_COMMAND` | Firmware-Update | `1`: **Verify Chunk**: Der zuletzt übertragene Chunk wurde vom Client als gültig befunden. Der Slave soll ihn nun ins Flash schreiben. `2`: **Finalize Update**: Alle Chunks sind übertragen. Installation abschliessen und neu starten. |
|
||||
@@ -83,10 +80,10 @@ Diese Register gehören zum externen Füllstandsensor und können auf dem Bus eb
|
||||
|
||||
| Adresse (hex) | Name | R/W | Beschreibung |
|
||||
| :------------ | :------------------------- | :-- | :---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **0x0000** | `NODE_ADDRESS` | R/W | Geräteadresse des Sensors (1-255). |
|
||||
| **0x0000** | `NODE_ADRESSE` | R/W | Geräteadresse des Sensors (1-255). |
|
||||
| **0x0001** | `BAUDRATE` | R/W | `0`=1200, `1`=2400, `2`=4800, `3`=9600, `4`=19200, `5`=38400, `6`=57600, `7`=115200. |
|
||||
| **0x0002** | `UNIT` | R/W | `0`=Keine, `1`=cm, `2`=mm, `3`=MPa, `4`=Pa, `5`=kPa. |
|
||||
| **0x0003** | `DECIMAL_PLACES` | R/W | Anzahl der Dezimalstellen für den Messwert (0-3). |
|
||||
| **0x0004** | `CURRENT_MEASUREMENT` | R | Der skalierte Messwert als vorzeichenbehafteter 16-Bit-Integer. |
|
||||
| **0x0005** | `MEASUREMENT_RANGE_ZERO_POINT` | R/W | Rohwert für den Nullpunkt der Skala. |
|
||||
| **0x0006** | `MEASUREMENT_RANGE_END_POINT` | R/W | Rohwert für den Endpunkt der Skala. |
|
||||
| **0x0002** | `EINHEIT` | R/W | `0`=Keine, `1`=cm, `2`=mm, `3`=MPa, `4`=Pa, `5`=kPa. |
|
||||
| **0x0003** | `NACHKOMMASTELLEN` | R/W | Anzahl der Dezimalstellen für den Messwert (0-3). |
|
||||
| **0x0004** | `MESSWERT_AKTUELL` | R | Der skalierte Messwert als vorzeichenbehafteter 16-Bit-Integer. |
|
||||
| **0x0005** | `MESSBEREICH_NULLPUNKT` | R/W | Rohwert für den Nullpunkt der Skala. |
|
||||
| **0x0006** | `MESSBEREICH_ENDPUNKT` | R/W | Rohwert für den Endpunkt der Skala. |
|
||||
|
||||
@@ -9,13 +9,11 @@
|
||||
| ✅ | **Phase 0: Planung & Definition** | | |
|
||||
| ✅ | Konzept erstellen und finalisieren | 30.06.2025 | Architektur, Komponenten und grundlegende Architektur sind festgelegt. |
|
||||
| ✅ | MODBUS Register Map definieren | 30.06.2025 | Die "API" der Slaves ist definiert und bildet die Grundlage für die Software-Entwicklung. |
|
||||
| ✅ | Header- und deutsche Dokumentation aktualisiert | 10.07.2025 | Doxygen-Kommentare in Headern und deutsche .md-Dateien auf den neuesten Stand gebracht und übersetzt. |
|
||||
| ☐ | **Phase 1: Slave-Node Prototyp (STM32 Eval-Board)** | | **Ziel:** Ein einzelner Slave wird auf dem Eval-Board zum Leben erweckt. |
|
||||
| ✅ | 1.1 Entwicklungsumgebung für STM32/Zephyr einrichten | 30.06.2025 | Toolchain, VS Code, Zephyr-SDK, MCUBoot etc. installieren und ein "Hello World" zum Laufen bringen. |
|
||||
| ✅ | 1.2 Hardware-Abstraktion (VND7050AJ, RS485) | 10.07.2025 | Implementierung der Treiber für den VND7050AJ und die RS485-Kommunikation. |
|
||||
| ✅ | 1.3 Basis-Firmware für Slave-Node erstellen | 10.07.2025 | Hardware-Abstraktion (GPIOs) implementiert. |
|
||||
| ✅ | 1.3 MODBUS-RTU Stack auf dem Slave implementieren | 10.07.2025 | Basierend auf der definierten Register-Map. Zuerst nur lesende Funktionen (Status, Version). |
|
||||
| ✅ | 1.4 Kernlogik implementieren (z.B. Ventilsteuerung) | 10.07.2025 | Umsetzung der `VALVE_STATE_MOVEMENT` Logik, Strommessung für Endlagen etc. |
|
||||
| ☐ | 1.2 Basis-Firmware für Slave-Node erstellen | | Hardware-Abstraktion (GPIOs, ADC, UART für RS485) implementieren. |
|
||||
| ☐ | 1.3 MODBUS-RTU Stack auf dem Slave implementieren | | Basierend auf der definierten Register-Map. Zuerst nur lesende Funktionen (Status, Version). |
|
||||
| ☐ | 1.4 Kernlogik implementieren (z.B. Ventilsteuerung) | | Umsetzung der `VENTIL_ZUSTAND_BEWEGUNG` Logik, Strommessung für Endlagen etc. |
|
||||
| ☐ | **Phase 2: Verifikation der Slave-Firmware** | | **Ziel:** Nachweisen, dass der Slave sich exakt an die MODBUS-Spezifikation hält. |
|
||||
| ☐ | 2.1 Slave-Node mit PC via USB-MODBUS-Adapter testen | | **Kritischer Meilenstein.** Mit Tools wie "QModMaster" oder einem Python-Skript die Register lesen & schreiben. Die Slave-Firmware wird so unabhängig vom Gateway validiert. |
|
||||
| ☐ | 2.2 Firmware-Update Mechanismus testen | | Den kompletten Update-Prozess (Chunking, CRC-Check) mit einem Skript vom PC aus testen. Der Slave schreibt die Firmware dabei vorerst nur in einen ungenutzten RAM-Bereich. |
|
||||
|
||||
@@ -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."
|
||||
@@ -1,142 +0,0 @@
|
||||
# Zephyr Project .clang-format configuration
|
||||
# Based on Linux kernel style with Zephyr-specific adaptations
|
||||
|
||||
# Use LLVM as the base style and customize from there
|
||||
BasedOnStyle: LLVM
|
||||
|
||||
# Language settings
|
||||
Language: Cpp
|
||||
|
||||
# Indentation settings
|
||||
IndentWidth: 8
|
||||
TabWidth: 8
|
||||
UseTab: ForIndentation
|
||||
|
||||
# Line length
|
||||
ColumnLimit: 100
|
||||
|
||||
# Brace settings
|
||||
BreakBeforeBraces: Linux
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterControlStatement: false
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
BeforeCatch: true
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
|
||||
# Always add braces for control statements (Zephyr requirement)
|
||||
RemoveBracesLLVM: false
|
||||
|
||||
# Control statement settings
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpacesInParentheses: false
|
||||
|
||||
# Function settings
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
|
||||
# Pointer and reference alignment
|
||||
PointerAlignment: Right
|
||||
ReferenceAlignment: Right
|
||||
|
||||
# Spacing settings
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInSquareBrackets: false
|
||||
|
||||
# Alignment settings
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Right
|
||||
AlignOperands: false
|
||||
AlignTrailingComments: false
|
||||
|
||||
# Breaking settings
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: false
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
|
||||
# Penalties (used for line breaking decisions)
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
|
||||
# Comment settings
|
||||
ReflowComments: true
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
|
||||
# Sorting settings
|
||||
SortIncludes: true
|
||||
SortUsingDeclarations: true
|
||||
|
||||
# Preprocessor settings
|
||||
IndentPPDirectives: None
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
|
||||
# Misc settings
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros: ['LISTIFY', 'FOR_EACH', 'FOR_EACH_FIXED_ARG', 'FOR_EACH_IDX', 'FOR_EACH_IDX_FIXED_ARG', 'FOR_EACH_NONEMPTY_TERM', 'Z_FOR_EACH', 'Z_FOR_EACH_FIXED_ARG', 'Z_FOR_EACH_IDX', 'Z_FOR_EACH_IDX_FIXED_ARG']
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '^<zephyr/.*\.h>'
|
||||
Priority: 1
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 2
|
||||
- Regex: '^<.*'
|
||||
Priority: 3
|
||||
- Regex: '.*'
|
||||
Priority: 4
|
||||
IndentCaseLabels: false
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
28
software/.vscode/settings.json
vendored
28
software/.vscode/settings.json
vendored
@@ -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"
|
||||
}
|
||||
}
|
||||
40
software/.vscode/tasks.json
vendored
40
software/.vscode/tasks.json
vendored
@@ -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",
|
||||
|
||||
8
software/apps/adc_dt/CMakeLists.txt
Normal file
8
software/apps/adc_dt/CMakeLists.txt
Normal 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)
|
||||
62
software/apps/adc_dt/README.rst
Normal file
62
software/apps/adc_dt/README.rst
Normal 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.
|
||||
38
software/apps/adc_dt/boards/weact_stm32g431_core.overlay
Normal file
38
software/apps/adc_dt/boards/weact_stm32g431_core.overlay
Normal 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>;
|
||||
};
|
||||
};
|
||||
4
software/apps/adc_dt/prj.conf
Normal file
4
software/apps/adc_dt/prj.conf
Normal file
@@ -0,0 +1,4 @@
|
||||
CONFIG_ADC=y
|
||||
CONFIG_SENSOR=y
|
||||
CONFIG_VOLTAGE_DIVIDER=y
|
||||
CONFIG_LOG=y
|
||||
53
software/apps/adc_dt/sample.yaml
Normal file
53
software/apps/adc_dt/sample.yaml
Normal 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+"
|
||||
25
software/apps/adc_dt/socs/esp32_procpu.overlay
Normal file
25
software/apps/adc_dt/socs/esp32_procpu.overlay
Normal 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>;
|
||||
};
|
||||
};
|
||||
25
software/apps/adc_dt/socs/esp32c3.overlay
Normal file
25
software/apps/adc_dt/socs/esp32c3.overlay
Normal 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>;
|
||||
};
|
||||
};
|
||||
25
software/apps/adc_dt/socs/esp32s2.overlay
Normal file
25
software/apps/adc_dt/socs/esp32s2.overlay
Normal 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>;
|
||||
};
|
||||
};
|
||||
25
software/apps/adc_dt/socs/esp32s3_procpu.overlay
Normal file
25
software/apps/adc_dt/socs/esp32s3_procpu.overlay
Normal 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>;
|
||||
};
|
||||
};
|
||||
45
software/apps/adc_dt/src/main.c
Normal file
45
software/apps/adc_dt/src/main.c
Normal 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;
|
||||
}
|
||||
6
software/apps/adc_test/CMakeLists.txt
Normal file
6
software/apps/adc_test/CMakeLists.txt
Normal 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)
|
||||
@@ -0,0 +1,8 @@
|
||||
&adc1 {
|
||||
pinctrl-0 = <&adc1_in1_pa0>;
|
||||
pinctrl-names = "default";
|
||||
status = "okay";
|
||||
|
||||
st,adc-clock-source = "SYNC";
|
||||
st,adc-prescaler = <4>;
|
||||
};
|
||||
3
software/apps/adc_test/prj.conf
Normal file
3
software/apps/adc_test/prj.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
CONFIG_ADC=y
|
||||
CONFIG_ADC_STM32=y
|
||||
CONFIG_LOG=y
|
||||
73
software/apps/adc_test/src/main.c
Normal file
73
software/apps/adc_test/src/main.c
Normal 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);
|
||||
}
|
||||
}
|
||||
80
software/apps/adc_test/src/main.c2
Normal file
80
software/apps/adc_test/src/main.c2
Normal 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);
|
||||
}
|
||||
}
|
||||
38
software/apps/adc_test/src/main.tabby
Normal file
38
software/apps/adc_test/src/main.tabby
Normal 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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Board specific configuration for weact_stm32g431_core
|
||||
# This file can be used for board-specific overrides if needed
|
||||
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) 2021 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "flash_partitions_128kb.dtsi"
|
||||
|
||||
@@ -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 */
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Sysbuild configuration for firmware_node with MCUboot
|
||||
|
||||
# Enable MCUboot as bootloader
|
||||
set(SB_CONFIG_BOOTLOADER_MCUBOOT TRUE)
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Linux",
|
||||
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
|
||||
"cStandard": "c99",
|
||||
"cppStandard": "gnu++17",
|
||||
"intelliSenseMode": "linux-gcc-arm"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
@@ -1,40 +1,16 @@
|
||||
/ {
|
||||
aliases {
|
||||
vnd7050aj = &vnd7050aj;
|
||||
};
|
||||
vnd7050aj: vnd7050aj {
|
||||
compatible = "vnd7050aj-valve-controller";
|
||||
status = "okay";
|
||||
|
||||
vnd7050aj: vnd7050aj {
|
||||
compatible = "st,vnd7050aj";
|
||||
status = "okay";
|
||||
|
||||
input0-gpios = <&gpiob 7 GPIO_ACTIVE_HIGH>;
|
||||
input1-gpios = <&gpiob 9 GPIO_ACTIVE_HIGH>;
|
||||
select0-gpios = <&gpiob 5 GPIO_ACTIVE_HIGH>;
|
||||
select1-gpios = <&gpiob 6 GPIO_ACTIVE_HIGH>;
|
||||
sense-enable-gpios = <&gpiob 4 GPIO_ACTIVE_HIGH>;
|
||||
fault-reset-gpios = <&gpiob 3 GPIO_ACTIVE_LOW>;
|
||||
io-channels = <&adc1 1>;
|
||||
r-sense-ohms = <1500>;
|
||||
k-vcc = <4000>;
|
||||
};
|
||||
};
|
||||
|
||||
&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>;
|
||||
|
||||
channel@1 {
|
||||
reg = <1>;
|
||||
zephyr,gain = "ADC_GAIN_1";
|
||||
zephyr,reference = "ADC_REF_INTERNAL";
|
||||
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
|
||||
zephyr,resolution = <12>;
|
||||
};
|
||||
// VND7050AJ GPIO pin definitions
|
||||
in0-gpios = <&gpiob 7 GPIO_ACTIVE_HIGH>; // IN0 (PB7) - Input 0 control signal
|
||||
in1-gpios = <&gpiob 9 GPIO_ACTIVE_HIGH>; // IN1 (PB9) - Input 1 control signal
|
||||
rst-gpios = <&gpiob 3 GPIO_ACTIVE_HIGH>; // RST (PB3) - Reset pin for VND7050AJ
|
||||
sen-gpios = <&gpiob 4 GPIO_ACTIVE_HIGH>; // SEN (PB4) - Sense Enable for current monitoring
|
||||
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
|
||||
};
|
||||
};
|
||||
|
||||
&usart1 {
|
||||
@@ -46,3 +22,43 @@
|
||||
pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>; // PA9=TX, PA10=RX for Modbus communication
|
||||
pinctrl-names = "default";
|
||||
};
|
||||
|
||||
&adc1 {
|
||||
status = "okay";
|
||||
pinctrl-0 = <&adc1_in1_pa0 &adc1_in15_pb0>;
|
||||
pinctrl-names = "default";
|
||||
st,adc-clock-source = "SYNC";
|
||||
st,adc-prescaler = <1>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
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
|
||||
};
|
||||
};
|
||||
|
||||
&pinctrl {
|
||||
// Pinmux für PA0 als ADC1_IN1 (Analogmodus)
|
||||
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
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,3 @@
|
||||
#include <zephyr/dt-bindings/gpio/gpio.h>
|
||||
|
||||
&zephyr_udc0 {
|
||||
cdc_acm_uart0: cdc_acm_uart0 {
|
||||
compatible = "zephyr,cdc-acm-uart";
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# VND7050AJ Valve Controller binding
|
||||
description: VND7050AJ valve controller GPIO configuration
|
||||
|
||||
compatible: "vnd7050aj-valve-controller"
|
||||
|
||||
properties:
|
||||
in0-gpios:
|
||||
type: phandle-array
|
||||
description: GPIO for IN0 control signal
|
||||
required: true
|
||||
|
||||
in1-gpios:
|
||||
type: phandle-array
|
||||
description: GPIO for IN1 control signal
|
||||
required: true
|
||||
|
||||
rst-gpios:
|
||||
type: phandle-array
|
||||
description: GPIO for reset pin
|
||||
required: true
|
||||
|
||||
sen-gpios:
|
||||
type: phandle-array
|
||||
description: GPIO for sense enable pin
|
||||
required: true
|
||||
|
||||
s0-gpios:
|
||||
type: phandle-array
|
||||
description: GPIO for select 0 pin
|
||||
required: true
|
||||
|
||||
s1-gpios:
|
||||
type: phandle-array
|
||||
description: GPIO for select 1 pin
|
||||
required: true
|
||||
@@ -5,9 +5,6 @@ CONFIG_LOG=y
|
||||
# Enable Shell
|
||||
CONFIG_SHELL=y
|
||||
CONFIG_REBOOT=y
|
||||
CONFIG_SHELL_MODBUS=y
|
||||
CONFIG_SHELL_VALVE=y
|
||||
CONFIG_SHELL_SYSTEM=y
|
||||
|
||||
# Enable Settings Subsystem
|
||||
CONFIG_SETTINGS=y
|
||||
@@ -24,5 +21,7 @@ CONFIG_MODBUS=y
|
||||
CONFIG_MODBUS_ROLE_SERVER=y
|
||||
CONFIG_MODBUS_BUFFER_SIZE=256
|
||||
|
||||
# Enable VND7050AJ
|
||||
CONFIG_VND7050AJ=y
|
||||
# Enable ADC driver
|
||||
CONFIG_ADC=y
|
||||
CONFIG_ADC_STM32=y
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/settings/settings.h>
|
||||
#include <lib/fwu.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <lib/modbus_server.h>
|
||||
#include <lib/valve.h>
|
||||
#include <lib/fwu.h>
|
||||
|
||||
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
|
||||
|
||||
@@ -23,6 +23,13 @@ int main(void)
|
||||
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;
|
||||
}
|
||||
|
||||
8
software/apps/snippets/bootloader/CMakeLists.txt
Normal file
8
software/apps/snippets/bootloader/CMakeLists.txt
Normal 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)
|
||||
34
software/apps/snippets/bootloader/README.md
Normal file
34
software/apps/snippets/bootloader/README.md
Normal 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
|
||||
@@ -1,5 +1,5 @@
|
||||
VERSION_MAJOR = 0
|
||||
VERSION_MINOR = 0
|
||||
PATCHLEVEL = 1
|
||||
VERSION_TWEAK = 1
|
||||
EXTRAVERSION = devel
|
||||
VERSION_TWEAK = 0
|
||||
EXTRAVERSION = testing
|
||||
8
software/apps/snippets/bootloader/erase.sh
Executable file
8
software/apps/snippets/bootloader/erase.sh
Executable 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' \
|
||||
16
software/apps/snippets/bootloader/prj.conf
Normal file
16
software/apps/snippets/bootloader/prj.conf
Normal 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
|
||||
42
software/apps/snippets/bootloader/src/main.c
Normal file
42
software/apps/snippets/bootloader/src/main.c
Normal 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;
|
||||
}
|
||||
5
software/apps/snippets/bootloader/sysbuild.conf
Normal file
5
software/apps/snippets/bootloader/sysbuild.conf
Normal 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
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
@@ -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";
|
||||
};
|
||||
};
|
||||
46
software/apps/snippets/bootloader/sysbuild/mcuboot.conf
Normal file
46
software/apps/snippets/bootloader/sysbuild/mcuboot.conf
Normal 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
|
||||
17
software/apps/snippets/bootloader/sysbuild/mcuboot.overlay
Normal file
17
software/apps/snippets/bootloader/sysbuild/mcuboot.overlay
Normal 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";
|
||||
};
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -4,182 +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 Combined status register for the valve.
|
||||
* High-Byte: Movement (0=Idle, 1=Opening, 2=Closing, 3=Error).
|
||||
* Low-Byte: State (0=Closed, 1=Open).
|
||||
*/
|
||||
enum
|
||||
{
|
||||
/* Valve Control & Status */
|
||||
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000,
|
||||
/**
|
||||
* @brief Motor current during opening in milliamperes (mA).
|
||||
*/
|
||||
REG_INPUT_MOTOR_OPEN_CURRENT_MA = 0x0001,
|
||||
/**
|
||||
* @brief Motor current during closing in milliamperes (mA).
|
||||
*/
|
||||
REG_INPUT_MOTOR_CLOSE_CURRENT_MA = 0x0002,
|
||||
/**
|
||||
* @brief Bitmask of digital inputs. Bit 0: Input 1, Bit 1: Input 2.
|
||||
* 1=Active.
|
||||
*/
|
||||
REG_INPUT_MOTOR_CURRENT_MA = 0x0001,
|
||||
/* Digital Inputs */
|
||||
REG_INPUT_DIGITAL_INPUTS_STATE = 0x0020,
|
||||
/**
|
||||
* @brief Event flags for buttons (Clear-on-Read). Bit 0: Button 1 pressed.
|
||||
* Bit 1: Button 2 pressed.
|
||||
*/
|
||||
REG_INPUT_BUTTON_EVENTS = 0x0021,
|
||||
/**
|
||||
* @brief Firmware version, e.g., 0x0102 for v1.2.
|
||||
*/
|
||||
/* System Config & Status */
|
||||
REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR = 0x00F0,
|
||||
/**
|
||||
* @brief Firmware version patch level, e.g., 3 for v1.2.3.
|
||||
*/
|
||||
REG_INPUT_FIRMWARE_VERSION_PATCH = 0x00F1,
|
||||
/**
|
||||
* @brief Device status (0=OK, 1=General Error).
|
||||
*/
|
||||
REG_INPUT_DEVICE_STATUS = 0x00F2,
|
||||
/**
|
||||
* @brief Lower 16 bits of uptime in seconds.
|
||||
*/
|
||||
REG_INPUT_UPTIME_SECONDS_LOW = 0x00F3,
|
||||
/**
|
||||
* @brief Upper 16 bits of uptime in seconds.
|
||||
*/
|
||||
REG_INPUT_UPTIME_SECONDS_HIGH = 0x00F4,
|
||||
/**
|
||||
* @brief Current supply voltage in millivolts (mV).
|
||||
*/
|
||||
REG_INPUT_SUPPLY_VOLTAGE_MV = 0x00F5,
|
||||
/**
|
||||
* @brief CRC16 of the last received data chunk in the buffer for firmware
|
||||
* update.
|
||||
*/
|
||||
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 Valve control command (1=Open, 2=Close, 0=Stop movement).
|
||||
*/
|
||||
enum
|
||||
{
|
||||
/* Valve Control */
|
||||
REG_HOLDING_VALVE_COMMAND = 0x0000,
|
||||
/**
|
||||
* @brief Safety timeout in seconds for the opening process.
|
||||
*/
|
||||
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001,
|
||||
/**
|
||||
* @brief Safety timeout in seconds for the closing process.
|
||||
*/
|
||||
REG_HOLDING_MAX_CLOSING_TIME_S = 0x0002,
|
||||
/**
|
||||
* @brief Minimum current threshold in mA for end-position detection.
|
||||
*/
|
||||
REG_HOLDING_END_CURRENT_THRESHOLD_OPEN_MA = 0x0003,
|
||||
/**
|
||||
* @brief Minimum current threshold in mA for end-position detection during
|
||||
* closing.
|
||||
*/
|
||||
REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA = 0x0004,
|
||||
/**
|
||||
* @brief Current threshold in mA for obstacle detection during opening.
|
||||
*/
|
||||
REG_HOLDING_OBSTACLE_THRESHOLD_OPEN_MA = 0x0005,
|
||||
/**
|
||||
* @brief Current threshold in mA for obstacle detection during closing.
|
||||
*/
|
||||
REG_HOLDING_OBSTACLE_THRESHOLD_CLOSE_MA = 0x0006,
|
||||
/**
|
||||
* @brief Bitmask for reading and writing digital outputs. Bit 0: Output 1,
|
||||
* Bit 1: Output 2. 1=ON, 0=OFF.
|
||||
*/
|
||||
/* Digital Outputs */
|
||||
REG_HOLDING_DIGITAL_OUTPUTS_STATE = 0x0010,
|
||||
/**
|
||||
* @brief Fail-safe watchdog timeout in seconds. 0=Disabled.
|
||||
*/
|
||||
/* System Config */
|
||||
REG_HOLDING_WATCHDOG_TIMEOUT_S = 0x00F0,
|
||||
/**
|
||||
* @brief Writing 1 restarts the device.
|
||||
*/
|
||||
REG_HOLDING_DEVICE_RESET = 0x00F1,
|
||||
/**
|
||||
* @brief Command for firmware update.
|
||||
* 1: Verify Chunk - Slave writes the last chunk to flash.
|
||||
* 2: Finalize Update - Complete installation and restart.
|
||||
*/
|
||||
/* Firmware Update */
|
||||
REG_HOLDING_FWU_COMMAND = 0x0100,
|
||||
/**
|
||||
* @brief Lower 16 bits of the 32-bit offset for the next firmware update
|
||||
* chunk.
|
||||
*/
|
||||
REG_HOLDING_FWU_CHUNK_OFFSET_LOW = 0x0101,
|
||||
/**
|
||||
* @brief Upper 16 bits of the 32-bit offset for the next firmware update
|
||||
* chunk.
|
||||
*/
|
||||
REG_HOLDING_FWU_CHUNK_OFFSET_HIGH = 0x0102,
|
||||
/**
|
||||
* @brief Size of the next firmware update chunk in bytes (max. 256).
|
||||
*/
|
||||
REG_HOLDING_FWU_CHUNK_SIZE = 0x0103,
|
||||
/**
|
||||
* @brief Start address of the 256-byte buffer for firmware update data.
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -1,194 +1,37 @@
|
||||
#ifndef VALVE_H
|
||||
#define VALVE_H
|
||||
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#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.
|
||||
*/
|
||||
struct valve_gpios {
|
||||
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;
|
||||
};
|
||||
|
||||
#define VALVE_CHANNEL_OPEN 0
|
||||
#define VALVE_CHANNEL_CLOSE 1
|
||||
#define VALVE_CURRENT_CHECK_INTERVAL K_MSEC(CONFIG_VALVE_INTERVALL_CURRENT_CHECK_MS)
|
||||
#define VALVE_INITIAL_CURRENT_CHECK_INTERVAL K_MSEC(CONFIG_VALVE_INITIAL_INTERVALL_CURRENT_CHECK_MS)
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* This function must be called before any other valve functions.
|
||||
*
|
||||
* @return 0 on success, or a negative error code on failure.
|
||||
*/
|
||||
int valve_init(void);
|
||||
|
||||
/**
|
||||
* @brief Starts opening the valve.
|
||||
*
|
||||
* The valve will open for the configured maximum opening time.
|
||||
*/
|
||||
void valve_init(void);
|
||||
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);
|
||||
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 Sets the current threshold for end-position detection during opening.
|
||||
*
|
||||
* @param current_ma The current threshold in milliamps.
|
||||
*/
|
||||
void valve_set_end_current_threshold_open(uint16_t current_ma);
|
||||
|
||||
/**
|
||||
* @brief Sets the current threshold for end-position detection during closing.
|
||||
*
|
||||
* @param current_ma The current threshold in milliamps.
|
||||
*/
|
||||
void valve_set_end_current_threshold_close(uint16_t current_ma);
|
||||
|
||||
/**
|
||||
* @brief Gets the current threshold for end-position detection during opening.
|
||||
*
|
||||
* @return The current threshold in milliamps.
|
||||
*/
|
||||
uint16_t valve_get_end_current_threshold_open(void);
|
||||
|
||||
/**
|
||||
* @brief Gets the current threshold for end-position detection during closing.
|
||||
*
|
||||
* @return The current threshold in milliamps.
|
||||
*/
|
||||
uint16_t valve_get_end_current_threshold_close(void);
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @brief Gets the current drawn by the valve motor during opening.
|
||||
*
|
||||
* @return The motor current in milliamps.
|
||||
*/
|
||||
int32_t valve_get_opening_current(void);
|
||||
|
||||
/**
|
||||
* @brief Gets the current drawn by the valve motor during closing.
|
||||
*
|
||||
* @return The motor current in milliamps.
|
||||
*/
|
||||
int32_t valve_get_closing_current(void);
|
||||
|
||||
/**
|
||||
* @brief Gets the temperature of the valve motor driver.
|
||||
*
|
||||
* @return The temperature in degrees Celsius.
|
||||
*/
|
||||
int32_t valve_get_vnd_temp(void);
|
||||
|
||||
/**
|
||||
* @brief Gets the voltage supplied to the valve motor driver.
|
||||
*
|
||||
* @return The voltage in millivolts.
|
||||
*/
|
||||
int32_t valve_get_vnd_voltage(void);
|
||||
|
||||
/**
|
||||
* @brief Sets the current threshold for obstacle detection during opening.
|
||||
*
|
||||
* @param current_ma The current threshold in milliamps.
|
||||
*/
|
||||
void valve_set_obstacle_threshold_open(uint16_t current_ma);
|
||||
|
||||
/**
|
||||
* @brief Sets the current threshold for obstacle detection during closing.
|
||||
*
|
||||
* @param current_ma The current threshold in milliamps.
|
||||
*/
|
||||
void valve_set_obstacle_threshold_close(uint16_t current_ma);
|
||||
|
||||
/**
|
||||
* @brief Gets the current threshold for obstacle detection during opening.
|
||||
*
|
||||
* @return The current threshold in milliamps.
|
||||
*/
|
||||
uint16_t valve_get_obstacle_threshold_open(void);
|
||||
|
||||
/**
|
||||
* @brief Gets the current threshold for obstacle detection during closing.
|
||||
*
|
||||
* @return The current threshold in milliamps.
|
||||
*/
|
||||
uint16_t valve_get_obstacle_threshold_close(void);
|
||||
|
||||
#endif // VALVE_H
|
||||
@@ -3,4 +3,3 @@ add_subdirectory_ifdef(CONFIG_LIB_MODBUS_SERVER modbus_server)
|
||||
add_subdirectory_ifdef(CONFIG_LIB_VALVE valve)
|
||||
add_subdirectory_ifdef(CONFIG_SHELL_SYSTEM shell_system)
|
||||
add_subdirectory_ifdef(CONFIG_SHELL_MODBUS shell_modbus)
|
||||
add_subdirectory_ifdef(CONFIG_SHELL_VALVE shell_valve)
|
||||
@@ -5,5 +5,4 @@ rsource "modbus_server/Kconfig"
|
||||
rsource "valve/Kconfig"
|
||||
rsource "shell_system/Kconfig"
|
||||
rsource "shell_modbus/Kconfig"
|
||||
rsource "shell_valve/Kconfig"
|
||||
endmenu
|
||||
@@ -1,64 +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 <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_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).");
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t fwu_get_last_chunk_crc(void)
|
||||
{
|
||||
return fwu_last_chunk_crc;
|
||||
return fwu_last_chunk_crc;
|
||||
}
|
||||
|
||||
@@ -1,77 +1,47 @@
|
||||
/**
|
||||
* @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 <zephyr/device.h>
|
||||
#include <zephyr/drivers/misc/vnd7050aj/vnd7050aj.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 <zephyr/usb/usb_device.h>
|
||||
#include <app_version.h>
|
||||
#include <lib/fwu.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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
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) {
|
||||
switch (addr)
|
||||
{
|
||||
case REG_HOLDING_MAX_OPENING_TIME_S:
|
||||
*reg = valve_get_max_open_time();
|
||||
break;
|
||||
@@ -81,18 +51,6 @@ static int holding_reg_rd(uint16_t addr, uint16_t *reg)
|
||||
case REG_HOLDING_WATCHDOG_TIMEOUT_S:
|
||||
*reg = watchdog_timeout_s;
|
||||
break;
|
||||
case REG_HOLDING_END_CURRENT_THRESHOLD_OPEN_MA:
|
||||
*reg = valve_get_end_current_threshold_open();
|
||||
break;
|
||||
case REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA:
|
||||
*reg = valve_get_end_current_threshold_close();
|
||||
break;
|
||||
case REG_HOLDING_OBSTACLE_THRESHOLD_OPEN_MA:
|
||||
*reg = valve_get_obstacle_threshold_open();
|
||||
break;
|
||||
case REG_HOLDING_OBSTACLE_THRESHOLD_CLOSE_MA:
|
||||
*reg = valve_get_obstacle_threshold_close();
|
||||
break;
|
||||
default:
|
||||
*reg = 0;
|
||||
break;
|
||||
@@ -100,23 +58,22 @@ static int holding_reg_rd(uint16_t addr, uint16_t *reg)
|
||||
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) {
|
||||
switch (addr)
|
||||
{
|
||||
case REG_HOLDING_VALVE_COMMAND:
|
||||
if (reg == 1) {
|
||||
if (reg == 1)
|
||||
{
|
||||
valve_open();
|
||||
} else if (reg == 2) {
|
||||
}
|
||||
else if (reg == 2)
|
||||
{
|
||||
valve_close();
|
||||
} else if (reg == 0) {
|
||||
}
|
||||
else if (reg == 0)
|
||||
{
|
||||
valve_stop();
|
||||
}
|
||||
break;
|
||||
@@ -126,30 +83,22 @@ static int holding_reg_wr(uint16_t addr, uint16_t reg)
|
||||
case REG_HOLDING_MAX_CLOSING_TIME_S:
|
||||
valve_set_max_close_time(reg);
|
||||
break;
|
||||
case REG_HOLDING_END_CURRENT_THRESHOLD_OPEN_MA:
|
||||
valve_set_end_current_threshold_open(reg);
|
||||
break;
|
||||
case REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA:
|
||||
valve_set_end_current_threshold_close(reg);
|
||||
break;
|
||||
case REG_HOLDING_OBSTACLE_THRESHOLD_OPEN_MA:
|
||||
valve_set_obstacle_threshold_open(reg);
|
||||
break;
|
||||
case REG_HOLDING_OBSTACLE_THRESHOLD_CLOSE_MA:
|
||||
valve_set_obstacle_threshold_close(reg);
|
||||
break;
|
||||
case REG_HOLDING_WATCHDOG_TIMEOUT_S:
|
||||
watchdog_timeout_s = reg;
|
||||
if (watchdog_timeout_s > 0) {
|
||||
if (watchdog_timeout_s > 0)
|
||||
{
|
||||
LOG_INF("Watchdog enabled with %u s timeout.", watchdog_timeout_s);
|
||||
reset_watchdog();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_INF("Watchdog disabled.");
|
||||
k_timer_stop(&watchdog_timer);
|
||||
}
|
||||
break;
|
||||
case REG_HOLDING_DEVICE_RESET:
|
||||
if (reg == 1) {
|
||||
if (reg == 1)
|
||||
{
|
||||
LOG_WRN("Modbus reset command received. Rebooting...");
|
||||
sys_reboot(SYS_REBOOT_WARM);
|
||||
}
|
||||
@@ -161,26 +110,17 @@ static int holding_reg_wr(uint16_t addr, uint16_t reg)
|
||||
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) {
|
||||
switch (addr)
|
||||
{
|
||||
case REG_INPUT_VALVE_STATE_MOVEMENT:
|
||||
*reg = (valve_get_movement() << 8) | (valve_get_state() & 0xFF);
|
||||
break;
|
||||
case REG_INPUT_MOTOR_OPEN_CURRENT_MA:
|
||||
*reg = (uint16_t)valve_get_opening_current();
|
||||
break;
|
||||
case REG_INPUT_MOTOR_CLOSE_CURRENT_MA:
|
||||
*reg = (uint16_t)valve_get_closing_current();
|
||||
case REG_INPUT_MOTOR_CURRENT_MA:
|
||||
*reg = valve_get_motor_current();
|
||||
break;
|
||||
case REG_INPUT_UPTIME_SECONDS_LOW:
|
||||
*reg = (uint16_t)(uptime_s & 0xFFFF);
|
||||
@@ -189,16 +129,16 @@ static int input_reg_rd(uint16_t addr, uint16_t *reg)
|
||||
*reg = (uint16_t)(uptime_s >> 16);
|
||||
break;
|
||||
case REG_INPUT_SUPPLY_VOLTAGE_MV:
|
||||
*reg = (uint16_t)valve_get_vnd_voltage();
|
||||
*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 = (APP_VERSION_MAJOR << 8) | APP_VERSION_MINOR;
|
||||
*reg = (0 << 8) | 0;
|
||||
break;
|
||||
case REG_INPUT_FIRMWARE_VERSION_PATCH:
|
||||
*reg = APP_PATCHLEVEL;
|
||||
*reg = 2;
|
||||
break;
|
||||
default:
|
||||
*reg = 0;
|
||||
@@ -208,10 +148,9 @@ static int input_reg_rd(uint16_t addr, uint16_t *reg)
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -235,11 +174,13 @@ int modbus_server_init(void)
|
||||
const struct device *const dev = DEVICE_DT_GET(DT_PARENT(MODBUS_NODE));
|
||||
uint32_t dtr = 0;
|
||||
|
||||
if (!device_is_ready(dev) || usb_enable(NULL)) {
|
||||
if (!device_is_ready(dev) || usb_enable(NULL))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (!dtr) {
|
||||
while (!dtr)
|
||||
{
|
||||
uart_line_ctrl_get(dev, UART_LINE_CTRL_DTR, &dtr);
|
||||
k_sleep(K_MSEC(100));
|
||||
}
|
||||
@@ -247,7 +188,8 @@ int modbus_server_init(void)
|
||||
LOG_INF("Client connected to server on %s", dev->name);
|
||||
#endif
|
||||
modbus_iface = modbus_iface_get_by_name(iface_name);
|
||||
if (modbus_iface < 0) {
|
||||
if (modbus_iface < 0)
|
||||
{
|
||||
return modbus_iface;
|
||||
}
|
||||
server_param.server.user_cb = &mbs_cbs;
|
||||
@@ -265,11 +207,14 @@ int modbus_reconfigure(uint32_t baudrate, uint8_t unit_id)
|
||||
// Try to reinitialize - this should work for most cases
|
||||
int ret = modbus_init_server(modbus_iface, server_param);
|
||||
|
||||
if (ret == 0) {
|
||||
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 {
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERR("Failed to reconfigure Modbus: %d", ret);
|
||||
LOG_INF("Modbus reconfiguration requires restart to take effect");
|
||||
|
||||
@@ -277,19 +222,12 @@ int modbus_reconfigure(uint32_t baudrate, uint8_t unit_id)
|
||||
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.");
|
||||
LOG_INF("Settings saved. Type 'reset' to restart the device and apply the change.");
|
||||
return 0; // Return success since settings are saved
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t modbus_get_baudrate(void)
|
||||
{
|
||||
return server_param.serial.baud;
|
||||
}
|
||||
uint8_t modbus_get_unit_id(void)
|
||||
{
|
||||
return server_param.server.unit_id;
|
||||
}
|
||||
uint32_t modbus_get_baudrate(void) { return server_param.serial.baud; }
|
||||
uint8_t modbus_get_unit_id(void) { return server_param.server.unit_id; }
|
||||
@@ -1,7 +1,5 @@
|
||||
config SHELL_MODBUS
|
||||
bool "Enable Shell Modbus"
|
||||
default n
|
||||
depends on SHELL
|
||||
depends on LIB_MODBUS_SERVER
|
||||
default y
|
||||
help
|
||||
Enable the modbus shell commands.
|
||||
Enable the modnbus shell commands.
|
||||
@@ -1,29 +1,12 @@
|
||||
/**
|
||||
* @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 <lib/modbus_server.h>
|
||||
#include <stdlib.h>
|
||||
#include <lib/modbus_server.h>
|
||||
#include <lib/valve.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_setb(const struct shell *sh, size_t argc, char **argv)
|
||||
static int cmd_modbus_set_baud(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
shell_error(sh, "Usage: setb <baudrate>");
|
||||
shell_error(sh, "Usage: set_baud <baudrate>");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -40,13 +23,9 @@ static int cmd_modbus_setb(const struct shell *sh, size_t argc, char **argv)
|
||||
|
||||
if (!is_valid) {
|
||||
char error_msg[128];
|
||||
int offset =
|
||||
snprintf(error_msg, sizeof(error_msg), "Invalid baudrate. Valid rates are: ");
|
||||
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]);
|
||||
offset += snprintf(error_msg + offset, sizeof(error_msg) - offset, "%u ", valid_baud_rates[i]);
|
||||
}
|
||||
shell_error(sh, "%s", error_msg);
|
||||
return -EINVAL;
|
||||
@@ -61,18 +40,10 @@ static int cmd_modbus_setb(const struct shell *sh, size_t argc, char **argv)
|
||||
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_setid(const struct shell *sh, size_t argc, char **argv)
|
||||
static int cmd_modbus_set_id(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
shell_error(sh, "Usage: setid <slave_id>");
|
||||
shell_error(sh, "Usage: set_id <slave_id>");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -92,28 +63,57 @@ static int cmd_modbus_setid(const struct shell *sh, size_t argc, char **argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shell command to show the current Modbus configuration.
|
||||
*
|
||||
* @param sh The shell instance.
|
||||
* @param argc Argument count.
|
||||
* @param argv Argument values.
|
||||
* @return 0 on success.
|
||||
*/
|
||||
static int cmd_modbus_show(const struct shell *sh, size_t argc, char **argv)
|
||||
static int cmd_valve_set_open_time(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
const int label_width = 15;
|
||||
if (argc != 2) {
|
||||
shell_error(sh, "Usage: set_open_time <seconds>");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
shell_print(sh, "Modbus Settings:");
|
||||
shell_print(sh, "%*s %u", label_width, "Baudrate:", modbus_get_baudrate());
|
||||
shell_print(sh, "%*s %u", label_width, "Slave ID:", modbus_get_unit_id());
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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(setb, NULL, "Set Modbus baudrate", cmd_modbus_setb),
|
||||
SHELL_CMD(setid, NULL, "Set Modbus slave ID", cmd_modbus_setid),
|
||||
SHELL_CMD(show, NULL, "Show Modbus configuration", cmd_modbus_show),
|
||||
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_CMD_REGISTER(modbus, &sub_modbus_cmds, "Modbus commands", NULL);
|
||||
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_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);
|
||||
@@ -1,5 +1,5 @@
|
||||
config SHELL_SYSTEM
|
||||
bool "Enable Shell System"
|
||||
default n
|
||||
default y
|
||||
help
|
||||
Enable the system commands.
|
||||
@@ -1,25 +1,6 @@
|
||||
/**
|
||||
* @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...");
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
zephyr_library_sources(shell_valve.c)
|
||||
@@ -1,7 +0,0 @@
|
||||
config SHELL_VALVE
|
||||
bool "Shell Valve commands"
|
||||
default n
|
||||
depends on SHELL
|
||||
depends on LIB_VALVE
|
||||
help
|
||||
Enable the valve shell commands.
|
||||
@@ -1,136 +0,0 @@
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/shell/shell.h>
|
||||
#include <lib/valve.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int cmd_valve_set_open_t(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
shell_print(sh, "Usage: valve set_open_t <seconds>");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
uint16_t seconds = (uint16_t)atoi(argv[1]);
|
||||
valve_set_max_open_time(seconds);
|
||||
shell_print(sh, "Max open time set to %u seconds.", seconds);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_valve_set_close_t(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
shell_print(sh, "Usage: valve set_close_t <seconds>");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
uint16_t seconds = (uint16_t)atoi(argv[1]);
|
||||
valve_set_max_close_time(seconds);
|
||||
shell_print(sh, "Max close time set to %u seconds.", seconds);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_valve_set_end_curr_open(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
shell_print(sh, "Usage: valve set_end_curr_open <milliamps>");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
uint16_t current_ma = (uint16_t)atoi(argv[1]);
|
||||
valve_set_end_current_threshold_open(current_ma);
|
||||
shell_print(sh, "End current threshold (open) set to %u mA.", current_ma);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_valve_set_end_curr_close(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
shell_print(sh, "Usage: valve set_end_curr_close <milliamps>");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
uint16_t current_ma = (uint16_t)atoi(argv[1]);
|
||||
valve_set_end_current_threshold_close(current_ma);
|
||||
shell_print(sh, "End current threshold (close) set to %u mA.", current_ma);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_valve_set_obstacle_open(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
shell_print(sh, "Usage: valve set_obstacle_open <milliamps>");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
uint16_t current_ma = (uint16_t)atoi(argv[1]);
|
||||
valve_set_obstacle_threshold_open(current_ma);
|
||||
shell_print(sh, "Obstacle threshold (open) set to %u mA.", current_ma);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_valve_set_obstacle_close(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
shell_print(sh, "Usage: valve set_obstacle_close <milliamps>");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
uint16_t current_ma = (uint16_t)atoi(argv[1]);
|
||||
valve_set_obstacle_threshold_close(current_ma);
|
||||
shell_print(sh, "Obstacle threshold (close) set to %u mA.", current_ma);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_valve_show(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
const int label_width = 30;
|
||||
|
||||
shell_print(sh, "Valve Settings:");
|
||||
shell_print(sh, "%*s %u s", label_width, "Max Open Time:", valve_get_max_open_time());
|
||||
shell_print(sh, "%*s %u s", label_width, "Max Close Time:", valve_get_max_close_time());
|
||||
shell_print(sh,
|
||||
"%*s %u mA",
|
||||
label_width,
|
||||
"End Current Threshold (Open):",
|
||||
valve_get_end_current_threshold_open());
|
||||
shell_print(sh,
|
||||
"%*s %u mA",
|
||||
label_width,
|
||||
"End Current Threshold (Close):",
|
||||
valve_get_end_current_threshold_close());
|
||||
shell_print(sh,
|
||||
"%*s %u mA",
|
||||
label_width,
|
||||
"Obstacle Threshold (Open):",
|
||||
valve_get_obstacle_threshold_open());
|
||||
shell_print(sh,
|
||||
"%*s %u mA",
|
||||
label_width,
|
||||
"Obstacle Threshold (Close):",
|
||||
valve_get_obstacle_threshold_close());
|
||||
return 0;
|
||||
}
|
||||
|
||||
SHELL_STATIC_SUBCMD_SET_CREATE(sub_valve_settings,
|
||||
SHELL_CMD(set_open_t, NULL, "Set max open time (seconds)", cmd_valve_set_open_t),
|
||||
SHELL_CMD(set_close_t, NULL, "Set max close time (seconds)", cmd_valve_set_close_t),
|
||||
SHELL_CMD(set_end_curr_open,
|
||||
NULL,
|
||||
"Set end current threshold for opening (mA)",
|
||||
cmd_valve_set_end_curr_open),
|
||||
SHELL_CMD(set_end_curr_close,
|
||||
NULL,
|
||||
"Set end current threshold for closing (mA)",
|
||||
cmd_valve_set_end_curr_close),
|
||||
SHELL_CMD(set_obstacle_open,
|
||||
NULL,
|
||||
"Set obstacle threshold for opening (mA)",
|
||||
cmd_valve_set_obstacle_open),
|
||||
SHELL_CMD(set_obstacle_close,
|
||||
NULL,
|
||||
"Set obstacle threshold for closing (mA)",
|
||||
cmd_valve_set_obstacle_close),
|
||||
SHELL_CMD(show, NULL, "Show valve configuration", cmd_valve_show),
|
||||
SHELL_SUBCMD_SET_END);
|
||||
|
||||
SHELL_CMD_REGISTER(valve, &sub_valve_settings, "Valve commands", NULL);
|
||||
@@ -3,43 +3,3 @@ config LIB_VALVE
|
||||
default y
|
||||
help
|
||||
Enable the Valve Library.
|
||||
|
||||
if LIB_VALVE
|
||||
config LOG_VALVE_LEVEL
|
||||
int "Valve Log Level"
|
||||
default 3
|
||||
help
|
||||
Set the log level for the Valve Library.
|
||||
0 = None, 1 = Error, 2 = Warning, 3 = Info, 4 = Debug
|
||||
|
||||
config VALVE_INTERVALL_CURRENT_CHECK_MS
|
||||
int "Interval Current Check (ms)"
|
||||
default 100
|
||||
help
|
||||
Set the interval in milliseconds for checking the motor current
|
||||
during valve operation. This is used to detect obstacles.
|
||||
|
||||
config VALVE_INITIAL_INTERVALL_CURRENT_CHECK_MS
|
||||
int "Initial Current Check (ms)"
|
||||
default 200
|
||||
help
|
||||
Set the initial delay in milliseconds before the first current check
|
||||
after starting the valve operation. This allows the motor to stabilize.
|
||||
|
||||
config VALVE_OBSTACLE_THRESHOLD_OPEN_MA
|
||||
int "Obstacle Threshold Open (mA)"
|
||||
default 200
|
||||
help
|
||||
Set the current threshold in milliamps for obstacle detection
|
||||
during valve opening. If the motor current exceeds this value,
|
||||
an obstacle is detected and the valve stops.
|
||||
|
||||
config VALVE_OBSTACLE_THRESHOLD_CLOSE_MA
|
||||
int "Obstacle Threshold Close (mA)"
|
||||
default 200
|
||||
help
|
||||
Set the current threshold in milliamps for obstacle detection
|
||||
during vaslve closing. If the motor current exceeds this value,
|
||||
an obstacle is detected and the valve stops.
|
||||
|
||||
endif # LIB_VALVE
|
||||
|
||||
@@ -1,293 +1,216 @@
|
||||
/**
|
||||
* @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 <zephyr/kernel.h>
|
||||
#include <zephyr/settings/settings.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/drivers/misc/vnd7050aj/vnd7050aj.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/settings/settings.h>
|
||||
#include <zephyr/drivers/adc.h>
|
||||
#include <lib/valve.h>
|
||||
|
||||
#define VND_NODE DT_ALIAS(vnd7050aj)
|
||||
#if !DT_NODE_HAS_STATUS(VND_NODE, okay)
|
||||
#error VND7050AJ node is not defined or enabled
|
||||
#endif
|
||||
const struct device *vnd7050aj_dev = DEVICE_DT_GET(VND_NODE);
|
||||
LOG_MODULE_REGISTER(valve, LOG_LEVEL_DBG);
|
||||
|
||||
LOG_MODULE_REGISTER(valve, CONFIG_LOG_VALVE_LEVEL);
|
||||
// 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 enum valve_state current_state = VALVE_STATE_OPEN;
|
||||
static const struct valve_gpios valve_gpios = {
|
||||
.in0 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), in0_gpios),
|
||||
.in1 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), in1_gpios),
|
||||
.rst = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), rst_gpios),
|
||||
.sen = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), sen_gpios),
|
||||
.s0 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), s0_gpios),
|
||||
.s1 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), s1_gpios),
|
||||
};
|
||||
|
||||
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 = 10;
|
||||
static uint16_t max_closing_time_s = 10;
|
||||
static uint16_t end_current_threshold_open_ma = 10;
|
||||
static uint16_t end_current_threshold_close_ma = 10;
|
||||
static uint16_t obstacle_threshold_open_ma = CONFIG_VALVE_OBSTACLE_THRESHOLD_OPEN_MA;
|
||||
static uint16_t obstacle_threshold_close_ma = CONFIG_VALVE_OBSTACLE_THRESHOLD_CLOSE_MA;
|
||||
static uint16_t max_opening_time_s = 60;
|
||||
static uint16_t max_closing_time_s = 60;
|
||||
static struct k_work_delayable valve_work;
|
||||
static struct k_timer movement_timer;
|
||||
|
||||
/**
|
||||
* @brief Work handler for end position checks of the valve.
|
||||
*
|
||||
* This function is called periodically to check if the valve has reached its
|
||||
* end position. It reads the current load on the motor and determines if the
|
||||
* valve has reached its target position.
|
||||
*
|
||||
* @param work Pointer to the k_work item.
|
||||
*/
|
||||
static void valve_work_handler(struct k_work *work)
|
||||
{
|
||||
int current_ma = 0;
|
||||
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) {
|
||||
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_OPEN, ¤t_ma);
|
||||
LOG_DBG("Current load during opening: %d mA", current_ma);
|
||||
if (current_ma > obstacle_threshold_open_ma) {
|
||||
LOG_ERR(
|
||||
"Obstacle detected during opening (current: %d mA), stopping motor.",
|
||||
current_ma);
|
||||
current_movement = VALVE_MOVEMENT_ERROR;
|
||||
valve_stop();
|
||||
return;
|
||||
} else if (current_ma > end_current_threshold_open_ma) {
|
||||
k_work_schedule(&valve_work, VALVE_CURRENT_CHECK_INTERVAL);
|
||||
return;
|
||||
}
|
||||
LOG_DBG("Valve finished opening");
|
||||
} else if (current_movement == VALVE_MOVEMENT_CLOSING) {
|
||||
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, ¤t_ma);
|
||||
LOG_DBG("Current load during closing: %d mA", current_ma);
|
||||
if (current_ma > obstacle_threshold_close_ma) {
|
||||
LOG_ERR(
|
||||
"Obstacle detected during closing (current: %d mA), stopping motor.",
|
||||
current_ma);
|
||||
current_movement = VALVE_MOVEMENT_ERROR;
|
||||
valve_stop();
|
||||
return;
|
||||
} else if (current_ma > end_current_threshold_close_ma) {
|
||||
k_work_schedule(&valve_work, VALVE_CURRENT_CHECK_INTERVAL);
|
||||
return;
|
||||
}
|
||||
current_state = VALVE_STATE_CLOSED;
|
||||
LOG_DBG("Valve finished closing");
|
||||
}
|
||||
current_movement = VALVE_MOVEMENT_IDLE;
|
||||
|
||||
valve_stop();
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Timer handler for valve movement timeouts.
|
||||
*
|
||||
* This function is called when the maximum allowed time for valve movement
|
||||
* (opening or closing) has been reached. It stops the valve motor, cancels
|
||||
* any pending end-position checks, and sets the movement status to error.
|
||||
*
|
||||
* @param timer Pointer to the k_timer instance that expired.
|
||||
*/
|
||||
void movement_timeout_handler(struct k_timer *timer)
|
||||
void valve_init(void)
|
||||
{
|
||||
// Stop the end position check if the timer expires
|
||||
k_work_cancel_delayable(&valve_work);
|
||||
if (current_movement == VALVE_MOVEMENT_OPENING) {
|
||||
LOG_WRN("Valve opening timeout reached, stopping motor.");
|
||||
current_movement = VALVE_MOVEMENT_ERROR;
|
||||
} else if (current_movement == VALVE_MOVEMENT_CLOSING) {
|
||||
LOG_WRN("Valve closing timeout reached, stopping motor.");
|
||||
current_movement = VALVE_MOVEMENT_ERROR;
|
||||
}
|
||||
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, false);
|
||||
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, false);
|
||||
current_state = VALVE_STATE_CLOSED;
|
||||
}
|
||||
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));
|
||||
|
||||
int valve_init(void)
|
||||
{
|
||||
if (!device_is_ready(vnd7050aj_dev)) {
|
||||
LOG_ERR("VND7050AJ device is not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
// Initialize ADC for MULTISENSE
|
||||
if (!device_is_ready(adc_dev)) {
|
||||
LOG_ERR("ADC device not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
k_work_init_delayable(&valve_work, valve_work_handler);
|
||||
k_timer_init(&movement_timer, movement_timeout_handler, NULL);
|
||||
int ret = adc_channel_setup(adc_dev, &adc_channel_cfg);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Could not setup ADC channel (%d)", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
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));
|
||||
settings_load_one("valve/end_current_threshold_open",
|
||||
&end_current_threshold_open_ma,
|
||||
sizeof(end_current_threshold_open_ma));
|
||||
settings_load_one("valve/end_current_threshold_close",
|
||||
&end_current_threshold_close_ma,
|
||||
sizeof(end_current_threshold_close_ma));
|
||||
settings_load_one("valve/obstacle_threshold_open",
|
||||
&obstacle_threshold_open_ma,
|
||||
sizeof(obstacle_threshold_open_ma));
|
||||
settings_load_one("valve/obstacle_threshold_close",
|
||||
&obstacle_threshold_close_ma,
|
||||
sizeof(obstacle_threshold_close_ma));
|
||||
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, end_curr_open=%umA, "
|
||||
"end_curr_close=%umA, obs_open=%umA, obs_close=%umA",
|
||||
max_opening_time_s,
|
||||
max_closing_time_s,
|
||||
end_current_threshold_open_ma,
|
||||
end_current_threshold_close_ma,
|
||||
obstacle_threshold_open_ma,
|
||||
obstacle_threshold_close_ma);
|
||||
valve_close();
|
||||
return 0;
|
||||
LOG_INF("Valve initialized: max_open=%us, max_close=%us", max_opening_time_s, max_closing_time_s);
|
||||
}
|
||||
|
||||
void valve_open(void)
|
||||
{
|
||||
LOG_DBG("Opening valve");
|
||||
vnd7050aj_reset_fault(vnd7050aj_dev);
|
||||
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, false);
|
||||
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, true);
|
||||
current_state =
|
||||
VALVE_STATE_OPEN; /* Security: assume valve open as soon as it starts opening */
|
||||
current_movement = VALVE_MOVEMENT_OPENING;
|
||||
if (max_opening_time_s > 0) {
|
||||
k_timer_start(&movement_timer, K_SECONDS(max_opening_time_s), K_NO_WAIT);
|
||||
}
|
||||
k_work_schedule(&valve_work, VALVE_INITIAL_CURRENT_CHECK_INTERVAL);
|
||||
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)
|
||||
{
|
||||
LOG_DBG("Closing valve");
|
||||
vnd7050aj_reset_fault(vnd7050aj_dev);
|
||||
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, false);
|
||||
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, true);
|
||||
if (max_closing_time_s > 0) {
|
||||
k_timer_start(&movement_timer, K_SECONDS(max_closing_time_s), K_NO_WAIT);
|
||||
}
|
||||
current_movement = VALVE_MOVEMENT_CLOSING;
|
||||
k_work_schedule(&valve_work, VALVE_INITIAL_CURRENT_CHECK_INTERVAL);
|
||||
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);
|
||||
k_timer_stop(&movement_timer);
|
||||
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, false);
|
||||
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, false);
|
||||
current_movement = VALVE_MOVEMENT_IDLE;
|
||||
k_work_cancel_delayable(&valve_work);
|
||||
current_movement = VALVE_MOVEMENT_IDLE;
|
||||
}
|
||||
|
||||
enum valve_state valve_get_state(void)
|
||||
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_supply_voltage(void)
|
||||
{
|
||||
return current_state;
|
||||
}
|
||||
enum valve_movement valve_get_movement(void)
|
||||
{
|
||||
return current_movement;
|
||||
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_end_current_threshold_open(uint16_t current_ma)
|
||||
{
|
||||
end_current_threshold_open_ma = current_ma;
|
||||
settings_save_one("valve/end_current_threshold_open",
|
||||
&end_current_threshold_open_ma,
|
||||
sizeof(end_current_threshold_open_ma));
|
||||
}
|
||||
|
||||
void valve_set_end_current_threshold_close(uint16_t current_ma)
|
||||
{
|
||||
end_current_threshold_close_ma = current_ma;
|
||||
settings_save_one("valve/end_current_threshold_close",
|
||||
&end_current_threshold_close_ma,
|
||||
sizeof(end_current_threshold_close_ma));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
uint16_t valve_get_end_current_threshold_open(void)
|
||||
{
|
||||
return end_current_threshold_open_ma;
|
||||
}
|
||||
|
||||
uint16_t valve_get_end_current_threshold_close(void)
|
||||
{
|
||||
return end_current_threshold_close_ma;
|
||||
}
|
||||
|
||||
int32_t valve_get_opening_current(void)
|
||||
{
|
||||
int32_t current;
|
||||
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_OPEN, ¤t);
|
||||
return current;
|
||||
}
|
||||
|
||||
int32_t valve_get_closing_current(void)
|
||||
{
|
||||
int32_t current;
|
||||
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, ¤t);
|
||||
return current;
|
||||
}
|
||||
|
||||
int32_t valve_get_vnd_temp(void)
|
||||
{
|
||||
int32_t temp_c;
|
||||
vnd7050aj_read_chip_temp(vnd7050aj_dev, &temp_c);
|
||||
return temp_c;
|
||||
}
|
||||
|
||||
int32_t valve_get_vnd_voltage(void)
|
||||
{
|
||||
int32_t voltage_mv;
|
||||
vnd7050aj_read_supply_voltage(vnd7050aj_dev, &voltage_mv);
|
||||
return voltage_mv;
|
||||
}
|
||||
|
||||
void valve_set_obstacle_threshold_open(uint16_t current_ma)
|
||||
{
|
||||
obstacle_threshold_open_ma = current_ma;
|
||||
settings_save_one("valve/obstacle_threshold_open",
|
||||
&obstacle_threshold_open_ma,
|
||||
sizeof(obstacle_threshold_open_ma));
|
||||
}
|
||||
|
||||
void valve_set_obstacle_threshold_close(uint16_t current_ma)
|
||||
{
|
||||
obstacle_threshold_close_ma = current_ma;
|
||||
settings_save_one("valve/obstacle_threshold_close",
|
||||
&obstacle_threshold_close_ma,
|
||||
sizeof(obstacle_threshold_close_ma));
|
||||
}
|
||||
|
||||
uint16_t valve_get_obstacle_threshold_open(void)
|
||||
{
|
||||
return obstacle_threshold_open_ma;
|
||||
}
|
||||
|
||||
uint16_t valve_get_obstacle_threshold_close(void)
|
||||
{
|
||||
return obstacle_threshold_close_ma;
|
||||
}
|
||||
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; }
|
||||
|
||||
43
software/serial_monitor.py
Normal file
43
software/serial_monitor.py
Normal 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)
|
||||
55
software/serial_reset_monitor.py
Normal file
55
software/serial_reset_monitor.py
Normal 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()
|
||||
@@ -9,9 +9,9 @@ from pymodbus.client import ModbusSerialClient
|
||||
from pymodbus.exceptions import ModbusException
|
||||
|
||||
# --- Register Definitions ---
|
||||
# (omitted for brevity, no changes here)
|
||||
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000
|
||||
REG_INPUT_MOTOR_OPEN_CURRENT_MA = 0x0001
|
||||
REG_INPUT_MOTOR_CLOSE_CURRENT_MA = 0x0002
|
||||
REG_INPUT_MOTOR_CURRENT_MA = 0x0001
|
||||
REG_INPUT_DIGITAL_INPUTS_STATE = 0x0020
|
||||
REG_INPUT_BUTTON_EVENTS = 0x0021
|
||||
REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR = 0x00F0
|
||||
@@ -24,10 +24,6 @@ REG_INPUT_FWU_LAST_CHUNK_CRC = 0x0100
|
||||
REG_HOLDING_VALVE_COMMAND = 0x0000
|
||||
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001
|
||||
REG_HOLDING_MAX_CLOSING_TIME_S = 0x0002
|
||||
REG_HOLDING_END_CURRENT_THRESHOLD_OPEN_MA = 0x0003
|
||||
REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA = 0x0004
|
||||
REG_HOLDING_OBSTACLE_THRESHOLD_OPEN_MA = 0x0005
|
||||
REG_HOLDING_OBSTACLE_THRESHOLD_CLOSE_MA = 0x0006
|
||||
REG_HOLDING_DIGITAL_OUTPUTS_STATE = 0x0010
|
||||
REG_HOLDING_WATCHDOG_TIMEOUT_S = 0x00F0
|
||||
REG_HOLDING_DEVICE_RESET = 0x00F1
|
||||
@@ -80,24 +76,21 @@ def poll_status(slave_id, interval):
|
||||
# Attempt to connect
|
||||
if client.connect():
|
||||
reconnect_attempts = 0
|
||||
with status_lock:
|
||||
status_data["error"] = None # Clear error in status_data immediately
|
||||
time.sleep(0.1) # Allow UI to refresh with cleared error
|
||||
new_data["error"] = None # Clear error on successful reconnect
|
||||
else:
|
||||
new_data["error"] = f"Connection lost. Attempting to reconnect ({reconnect_attempts}/{max_reconnect_attempts})..."
|
||||
time.sleep(reconnect_delay)
|
||||
continue
|
||||
|
||||
# If connected, try to read data
|
||||
ir_valve = client.read_input_registers(REG_INPUT_VALVE_STATE_MOVEMENT, count=1, slave=slave_id)
|
||||
ir_current = client.read_input_registers(REG_INPUT_MOTOR_OPEN_CURRENT_MA, count=2, slave=slave_id)
|
||||
ir_valve = client.read_input_registers(REG_INPUT_VALVE_STATE_MOVEMENT, count=2, slave=slave_id)
|
||||
ir_dig = client.read_input_registers(REG_INPUT_DIGITAL_INPUTS_STATE, count=2, slave=slave_id)
|
||||
ir_sys = client.read_input_registers(REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR, count=6, slave=slave_id)
|
||||
hr_valve = client.read_holding_registers(REG_HOLDING_MAX_OPENING_TIME_S, count=6, slave=slave_id)
|
||||
hr_valve = client.read_holding_registers(REG_HOLDING_MAX_OPENING_TIME_S, count=2, slave=slave_id)
|
||||
hr_dig = client.read_holding_registers(REG_HOLDING_DIGITAL_OUTPUTS_STATE, count=1, slave=slave_id)
|
||||
hr_sys = client.read_holding_registers(REG_HOLDING_WATCHDOG_TIMEOUT_S, count=1, slave=slave_id)
|
||||
|
||||
for res in [ir_valve, ir_current, ir_dig, ir_sys, hr_valve, hr_dig, hr_sys]:
|
||||
for res in [ir_valve, ir_dig, ir_sys, hr_valve, hr_dig, hr_sys]:
|
||||
if res.isError():
|
||||
raise ModbusException(str(res))
|
||||
|
||||
@@ -106,14 +99,9 @@ def poll_status(slave_id, interval):
|
||||
state_map = {0: "Closed", 1: "Open"}
|
||||
new_data["movement"] = movement_map.get(valve_state_raw >> 8, 'Unknown')
|
||||
new_data["state"] = state_map.get(valve_state_raw & 0xFF, 'Unknown')
|
||||
new_data["motor_current_open"] = f"{ir_current.registers[0]} mA"
|
||||
new_data["motor_current_close"] = f"{ir_current.registers[1]} mA"
|
||||
new_data["motor_current"] = f"{ir_valve.registers[1]} mA"
|
||||
new_data["open_time"] = f"{hr_valve.registers[0]}s"
|
||||
new_data["close_time"] = f"{hr_valve.registers[1]}s"
|
||||
new_data["end_curr_open"] = f"{hr_valve.registers[2]}mA"
|
||||
new_data["end_curr_close"] = f"{hr_valve.registers[3]}mA"
|
||||
new_data["obstacle_open"] = f"{hr_valve.registers[4]}mA"
|
||||
new_data["obstacle_close"] = f"{hr_valve.registers[5]}mA"
|
||||
new_data["digital_inputs"] = f"0x{ir_dig.registers[0]:04X}"
|
||||
new_data["button_events"] = f"0x{ir_dig.registers[1]:04X}"
|
||||
new_data["digital_outputs"] = f"0x{hr_dig.registers[0]:04X}"
|
||||
@@ -194,7 +182,7 @@ def file_browser(stdscr):
|
||||
selected_index = 0
|
||||
|
||||
while True:
|
||||
stdscr.erase()
|
||||
stdscr.clear()
|
||||
h, w = stdscr.getmaxyx()
|
||||
stdscr.addstr(0, 0, f"Select Firmware File: {path}".ljust(w-1), curses.color_pair(2))
|
||||
|
||||
@@ -238,9 +226,7 @@ def main_menu(stdscr, slave_id):
|
||||
curses.start_color(); curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE); curses.init_pair(2, curses.COLOR_BLUE, curses.COLOR_WHITE); curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLUE)
|
||||
stdscr.bkgd(' ', curses.color_pair(1))
|
||||
|
||||
menu = ["Open Valve", "Close Valve", "Stop Valve", "Settings", "Reset Node", "Firmware Update", "Exit"]
|
||||
settings_menu = ["Set Max Open Time", "Set Max Close Time", "Set End Current Open", "Set End Current Close", "Set Obstacle Current Open", "Set Obstacle Current Close", "Set Watchdog", "Back"]
|
||||
current_menu = menu
|
||||
menu = ["Open Valve", "Close Valve", "Stop Valve", "Toggle Output 1", "Toggle Output 2", "Set Watchdog", "Reset Node", "Firmware Update", "Exit"]
|
||||
current_row_idx = 0
|
||||
message, message_time = "", 0
|
||||
input_mode, input_prompt, input_str, input_target_reg = False, "", "", 0
|
||||
@@ -264,31 +250,24 @@ def main_menu(stdscr, slave_id):
|
||||
elif key == curses.KEY_BACKSPACE or key == 127: input_str = input_str[:-1]
|
||||
elif key != -1 and chr(key).isprintable(): input_str += chr(key)
|
||||
else:
|
||||
if key == curses.KEY_UP: current_row_idx = (current_row_idx - 1) % len(current_menu)
|
||||
elif key == curses.KEY_DOWN: current_row_idx = (current_row_idx + 1) % len(current_menu)
|
||||
if key == curses.KEY_UP: current_row_idx = (current_row_idx - 1) % len(menu)
|
||||
elif key == curses.KEY_DOWN: current_row_idx = (current_row_idx + 1) % len(menu)
|
||||
elif key == curses.KEY_ENTER or key in [10, 13]:
|
||||
selected_option = current_menu[current_row_idx]
|
||||
selected_option = menu[current_row_idx]
|
||||
message_time = time.time()
|
||||
if selected_option == "Exit": stop_event.set(); continue
|
||||
elif selected_option == "Back": current_menu = menu; current_row_idx = 0; continue
|
||||
elif selected_option == "Settings": current_menu = settings_menu; current_row_idx = 0; continue
|
||||
elif selected_option == "Open Valve": client.write_register(REG_HOLDING_VALVE_COMMAND, 1, slave=slave_id); message = "-> Sent OPEN command"
|
||||
elif selected_option == "Close Valve": client.write_register(REG_HOLDING_VALVE_COMMAND, 2, slave=slave_id); message = "-> Sent CLOSE command"
|
||||
elif selected_option == "Stop Valve": client.write_register(REG_HOLDING_VALVE_COMMAND, 0, slave=slave_id); message = "-> Sent STOP command"
|
||||
elif selected_option == "Set Max Open Time":
|
||||
input_mode, input_prompt, input_target_reg = True, "Enter Max Open Time (s): ", REG_HOLDING_MAX_OPENING_TIME_S
|
||||
elif selected_option == "Set Max Close Time":
|
||||
input_mode, input_prompt, input_target_reg = True, "Enter Max Close Time (s): ", REG_HOLDING_MAX_CLOSING_TIME_S
|
||||
elif selected_option == "Set End Current Open":
|
||||
input_mode, input_prompt, input_target_reg = True, "Enter End Current Threshold Open (mA): ", REG_HOLDING_END_CURRENT_THRESHOLD_OPEN_MA
|
||||
elif selected_option == "Set End Current Close":
|
||||
input_mode, input_prompt, input_target_reg = True, "Enter End Current Threshold Close (mA): ", REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA
|
||||
elif "Toggle Output" in selected_option:
|
||||
bit = 0 if "1" in selected_option else 1
|
||||
try:
|
||||
current_val = client.read_holding_registers(REG_HOLDING_DIGITAL_OUTPUTS_STATE, count=1, slave=slave_id).registers[0]
|
||||
client.write_register(REG_HOLDING_DIGITAL_OUTPUTS_STATE, current_val ^ (1 << bit), slave=slave_id)
|
||||
message = f"-> Toggled Output {bit+1}"
|
||||
except Exception as e: message = f"-> Error: {e}"
|
||||
elif selected_option == "Set Watchdog":
|
||||
input_mode, input_prompt, input_target_reg = True, "Enter Watchdog Timeout (s): ", REG_HOLDING_WATCHDOG_TIMEOUT_S
|
||||
elif selected_option == "Set Obstacle Current Open":
|
||||
input_mode, input_prompt, input_target_reg = True, "Enter Obstacle Threshold Open (mA): ", REG_HOLDING_OBSTACLE_THRESHOLD_OPEN_MA
|
||||
elif selected_option == "Set Obstacle Current Close":
|
||||
input_mode, input_prompt, input_target_reg = True, "Enter Obstacle Threshold Close (mA): ", REG_HOLDING_OBSTACLE_THRESHOLD_CLOSE_MA
|
||||
elif selected_option == "Reset Node":
|
||||
try:
|
||||
client.write_register(REG_HOLDING_DEVICE_RESET, 1, slave=slave_id)
|
||||
@@ -302,7 +281,7 @@ def main_menu(stdscr, slave_id):
|
||||
else:
|
||||
message = "-> Firmware update cancelled."
|
||||
|
||||
stdscr.erase()
|
||||
stdscr.clear()
|
||||
if is_updating:
|
||||
with update_lock: prog, msg = update_status["progress"], update_status["message"]
|
||||
stdscr.addstr(h // 2 - 1, w // 2 - 25, "FIRMWARE UPDATE IN PROGRESS", curses.A_BOLD | curses.color_pair(2))
|
||||
@@ -316,30 +295,25 @@ def main_menu(stdscr, slave_id):
|
||||
col1, col2, col3, col4 = 2, 30, 58, 88
|
||||
stdscr.addstr(1, col1, "State:", bold); stdscr.addstr(1, col1 + 18, str(current_data.get('state', 'N/A')), normal)
|
||||
stdscr.addstr(2, col1, "Movement:", bold); stdscr.addstr(2, col1 + 18, str(current_data.get('movement', 'N/A')), normal)
|
||||
stdscr.addstr(3, col1, "Open Current:", bold); stdscr.addstr(3, col1 + 18, str(current_data.get('motor_current_open', 'N/A')), normal)
|
||||
stdscr.addstr(4, col1, "Close Current:", bold); stdscr.addstr(4, col1 + 18, str(current_data.get('motor_current_close', 'N/A')), normal)
|
||||
stdscr.addstr(3, col1, "Motor Current:", bold); stdscr.addstr(3, col1 + 18, str(current_data.get('motor_current', 'N/A')), normal)
|
||||
stdscr.addstr(1, col2, "Digital Inputs:", bold); stdscr.addstr(1, col2 + 18, str(current_data.get('digital_inputs', 'N/A')), normal)
|
||||
stdscr.addstr(2, col2, "Digital Outputs:", bold); stdscr.addstr(2, col2 + 18, str(current_data.get('digital_outputs', 'N/A')), normal)
|
||||
stdscr.addstr(3, col2, "Button Events:", bold); stdscr.addstr(3, col2 + 18, str(current_data.get('button_events', 'N/A')), normal)
|
||||
stdscr.addstr(1, col3, "Max Open Time:", bold); stdscr.addstr(1, col3 + 16, str(current_data.get('open_time', 'N/A')), normal)
|
||||
stdscr.addstr(2, col3, "Max Close Time:", bold); stdscr.addstr(2, col3 + 16, str(current_data.get('close_time', 'N/A')), normal)
|
||||
stdscr.addstr(3, col3, "Watchdog:", bold); stdscr.addstr(3, col3 + 16, str(current_data.get('watchdog', 'N/A')), normal)
|
||||
stdscr.addstr(4, col3, "End Curr Open:", bold); stdscr.addstr(4, col3 + 16, str(current_data.get('end_curr_open', 'N/A')), normal)
|
||||
stdscr.addstr(5, col3, "End Curr Close:", bold); stdscr.addstr(5, col3 + 16, str(current_data.get('end_curr_close', 'N/A')), normal)
|
||||
stdscr.addstr(6, col3, "Obstacle Open:", bold); stdscr.addstr(6, col3 + 16, str(current_data.get('obstacle_open', 'N/A')), normal)
|
||||
stdscr.addstr(7, col3, "Obstacle Close:", bold); stdscr.addstr(7, col3 + 16, str(current_data.get('obstacle_close', 'N/A')), normal)
|
||||
stdscr.addstr(1, col4, "Firmware:", bold); stdscr.addstr(1, col4 + 14, str(current_data.get('firmware', 'N/A')), normal)
|
||||
stdscr.addstr(2, col4, "Uptime:", bold); stdscr.addstr(2, col4 + 14, str(current_data.get('uptime', 'N/A')), normal)
|
||||
stdscr.addstr(3, col4, "Dev. Status:", bold); stdscr.addstr(3, col4 + 14, str(current_data.get('device_status', 'N/A')), normal)
|
||||
stdscr.addstr(4, col4, "Supply V:", bold); stdscr.addstr(4, col4 + 14, str(current_data.get('supply_voltage', 'N/A')), normal)
|
||||
stdscr.addstr(8, 0, "─" * (w - 1), normal)
|
||||
for idx, row in enumerate(current_menu):
|
||||
draw_button(stdscr, 9 + (idx * 2), w // 2 - len(row) // 2, row, idx == current_row_idx)
|
||||
stdscr.addstr(5, 0, "─" * (w - 1), normal)
|
||||
for idx, row in enumerate(menu):
|
||||
draw_button(stdscr, h // 2 - len(menu) + (idx * 2), w // 2 - len(row) // 2, row, idx == current_row_idx)
|
||||
if time.time() - message_time < 2.0: stdscr.addstr(h - 2, 0, message.ljust(w - 1), curses.color_pair(1) | curses.A_BOLD)
|
||||
if input_mode:
|
||||
curses.curs_set(1); stdscr.addstr(h - 2, 0, (input_prompt + input_str).ljust(w-1), curses.color_pair(2)); stdscr.move(h - 2, len(input_prompt) + len(input_str))
|
||||
else: curses.curs_set(0)
|
||||
curses.doupdate()
|
||||
stdscr.refresh()
|
||||
|
||||
def main():
|
||||
global client
|
||||
|
||||
Reference in New Issue
Block a user