docs: Update German documentation and project plan

- Updated Doxygen comments in header files (valve.h, fwu.h, modbus_server.h) to be consistent and in English.
- Translated German register names in docs/modbus-registers.de.md to English.
- Updated docs/concept.de.md to reflect new details on current measurement and sensors.
- Updated docs/planning.de.md to reflect completed tasks in Phase 1.
This commit is contained in:
Eduard Iten 2025-07-10 21:11:20 +02:00
parent c1622bb01c
commit 8ab2f4c1ce
21 changed files with 219 additions and 957 deletions

View File

@ -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).
* **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 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.

View File

@ -29,10 +29,11 @@ Alle Register sind in einer einzigen, durchgehenden Liste pro Register-Typ (`Inp
| Adresse (hex) | Name | Zugehörigkeit | Beschreibung |
| :------------ | :----------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------------- |
| **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. |
| **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. |
| **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. |
@ -45,10 +46,10 @@ Alle Register sind in einer einzigen, durchgehenden Liste pro Register-Typ (`Inp
| Adresse (hex) | Name | Zugehörigkeit | Beschreibung |
| :------------ | :---------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------------- |
| **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. |
| **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. |
| **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. |
| **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. |
@ -80,10 +81,10 @@ Diese Register gehören zum externen Füllstandsensor und können auf dem Bus eb
| Adresse (hex) | Name | R/W | Beschreibung |
| :------------ | :------------------------- | :-- | :---------------------------------------------------------------------------------------------------------------------------------------- |
| **0x0000** | `NODE_ADRESSE` | R/W | Geräteadresse des Sensors (1-255). |
| **0x0000** | `NODE_ADDRESS` | 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** | `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. |
| **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. |

View File

@ -9,11 +9,13 @@
| ✅ | **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 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. |
| ✅ | 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. |
| ☐ | **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. |

View File

@ -1,91 +1,41 @@
#include <zephyr/dt-bindings/gpio/gpio.h>
/ {
vnd7050aj: vnd7050aj {
compatible = "vnd7050aj-valve-controller";
status = "okay";
aliases {
vnd7050aj = &vnd7050aj;
};
// 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
};
vnd7050aj: vnd7050aj {
compatible = "st,vnd7050aj";
status = "okay";
adc_sensors {
compatible = "adc-sensors";
supply_voltage: supply-voltage {
compatible = "custom,supply-voltage";
io-channels = <&adc1 1>; /* ADC1 channel 1 (PA0) */
io-channel-names = "voltage";
reference-mv = <3300>;
voltage-divider-ratio = <4>; /* Adjust based on your voltage divider */
/* GPIO control pins using VND7050AJ pins */
sen-gpios = <&gpiob 4 GPIO_ACTIVE_HIGH>; /* SEN (PB4) - enable sensor */
s0-gpios = <&gpiob 6 GPIO_ACTIVE_HIGH>; /* S0 (PB6) - mux select bit 0 */
s1-gpios = <&gpiob 5 GPIO_ACTIVE_HIGH>; /* S1 (PB5) - mux select bit 1 */
measurement-delay-ms = <5>; /* 5ms delay after GPIO setup */
};
motor_current_open: motor-current-open {
compatible = "custom,motor-current";
io-channels = <&adc1 1>; /* Same ADC channel, different mux setting */
io-channel-names = "current";
reference-mv = <3300>;
current-sense-resistor-mohm = <1500000>; /* 1.5kΩ sense resistor in mΩ */
k-factor = <10>; /* Current sense amplification factor */
/* GPIO control pins using VND7050AJ pins */
sen-gpios = <&gpiob 4 GPIO_ACTIVE_HIGH>; /* SEN (PB4) - enable sensor */
s0-gpios = <&gpiob 6 GPIO_ACTIVE_HIGH>; /* S0 (PB6) - mux select bit 0 */
s1-gpios = <&gpiob 5 GPIO_ACTIVE_HIGH>; /* S1 (PB5) - mux select bit 1 */
measurement-delay-ms = <10>; /* 10ms delay for current settling */
};
motor_current_close: motor-current-close {
compatible = "custom,motor-current";
io-channels = <&adc1 1>; /* Same ADC channel, different mux setting */
io-channel-names = "current";
reference-mv = <3300>;
current-sense-resistor-mohm = <1500000>; /* 1.5kΩ sense resistor in mΩ */
k-factor = <10>; /* Current sense amplification factor */
/* GPIO control pins using VND7050AJ pins */
sen-gpios = <&gpiob 4 GPIO_ACTIVE_HIGH>; /* SEN (PB4) - enable sensor */
s0-gpios = <&gpiob 6 GPIO_ACTIVE_HIGH>; /* S0 (PB6) - mux select bit 0 */
s1-gpios = <&gpiob 5 GPIO_ACTIVE_HIGH>; /* S1 (PB5) - mux select bit 1 */
measurement-delay-ms = <10>; /* 10ms delay for current settling */
};
};
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 = <4139>;
};
};
// Clock configuration: Uncomment the following section to use calibrated HSI instead of HSE
//&clk_hse {
// status = "disabled"; // Disable external crystal oscillator
//};
//
//&clk_hsi {
// status = "okay"; // Enable internal high-speed oscillator (16 MHz, calibrated)
//};
//
//&pll {
// // Change PLL source from HSE to HSI
// clocks = <&clk_hsi>;
// // Adjust multipliers to maintain 144 MHz system clock with 16 MHz HSI input
// // HSI = 16 MHz, div-m = 4, mul-n = 72, div-r = 2
// // PLL_VCO = (16 MHz / 4) * 72 = 288 MHz
// // SYSCLK = 288 MHz / 2 = 144 MHz
// div-m = <4>; // Divide HSI by 4 (16 MHz / 4 = 4 MHz)
// mul-n = <72>; // Multiply by 72 (4 MHz * 72 = 288 MHz)
// div-r = <2>; // Divide by 2 for system clock (288 MHz / 2 = 144 MHz)
//};
&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>;
};
};
&usart1 {
modbus0 {
@ -96,20 +46,3 @@
pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>; // PA9=TX, PA10=RX for Modbus communication
pinctrl-names = "default";
};
&adc1 { // ADC1 wird für PA0 verwendet
status = "okay"; // ADC1 aktivieren
pinctrl-0 = <&adc1_in1_pa0>; // Pinmux für PA0 als ADC1_IN1
pinctrl-names = "default";
st,adc-clock-source = "SYNC";
st,adc-prescaler = <4>;
#address-cells = <1>;
#size-cells = <0>;
};
&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
};
};

View File

@ -1,78 +1,5 @@
#include <zephyr/dt-bindings/gpio/gpio.h>
/ {
vnd7050aj: vnd7050aj {
compatible = "vnd7050aj-valve-controller";
status = "okay";
// 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
};
adc_sensors {
compatible = "adc-sensors";
supply_voltage: supply-voltage {
compatible = "custom,supply-voltage";
io-channels = <&adc1 1>; /* ADC1 channel 1 (PA0) */
io-channel-names = "voltage";
reference-mv = <3300>;
voltage-divider-ratio = <4>; /* Adjust based on your voltage divider */
sensor-mux = <&vnd7050aj>; /* Reference to VND7050AJ mux */
mux-channel = <3>; /* VCC sense channel */
measurement-delay-ms = <5>; /* 5ms delay after GPIO setup */
};
motor_current_open: motor-current-open {
compatible = "custom,motor-current";
io-channels = <&adc1 1>; /* Same ADC channel, different mux setting */
io-channel-names = "current";
reference-mv = <3300>;
current-sense-resistor-mohm = <1500000>; /* 1.5kΩ sense resistor in mΩ */
k-factor = <10>; /* Current sense amplification factor */
sen-gpios = <&gpiob 4 GPIO_ACTIVE_HIGH>; /* SEN (PB4) - enable sensor */
s0-gpios = <&gpiob 6 GPIO_ACTIVE_HIGH>; /* S0 (PB6) - mux select bit 0 */
s1-gpios = <&gpiob 5 GPIO_ACTIVE_HIGH>; /* S1 (PB5) - mux select bit 1 */
measurement-delay-ms = <10>; /* 10ms delay for current settling */
};
motor_current_close: motor-current-close {
compatible = "custom,motor-current";
io-channels = <&adc1 1>; /* Same ADC channel, different mux setting */
io-channel-names = "current";
reference-mv = <3300>;
current-sense-resistor-mohm = <1500000>; /* 1.5kΩ sense resistor in mΩ */
k-factor = <10>; /* Current sense amplification factor */
sen-gpios = <&gpiob 4 GPIO_ACTIVE_HIGH>; /* SEN (PB4) - enable sensor */
s0-gpios = <&gpiob 6 GPIO_ACTIVE_HIGH>; /* S0 (PB6) - mux select bit 0 */
s1-gpios = <&gpiob 5 GPIO_ACTIVE_HIGH>; /* S1 (PB5) - mux select bit 1 */
measurement-delay-ms = <10>; /* 10ms delay for current settling */
};
};
};
&adc1 { // ADC1 wird für PA0 verwendet
status = "okay"; // ADC1 aktivieren
pinctrl-0 = <&adc1_in1_pa0>; // Pinmux für PA0 als ADC1_IN1
pinctrl-names = "default";
st,adc-clock-source = "SYNC";
st,adc-prescaler = <4>;
#address-cells = <1>;
#size-cells = <0>;
};
&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
};
};
&zephyr_udc0 {
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";

View File

@ -1,51 +0,0 @@
# Custom motor current sensor binding
description: Motor current sensor using VND7050AJ multiplexer
compatible: "custom,motor-current"
properties:
io-channels:
type: phandle-array
required: true
description: ADC channel phandle and specifier
io-channel-names:
type: string-array
required: true
description: Names for the ADC channels
reference-mv:
type: int
required: true
description: ADC reference voltage in millivolts
current-sense-resistor-mohm:
type: int
required: true
description: Current sense resistor value in milliohms
k-factor:
type: int
required: true
description: Current sense amplification factor for VND7050AJ
sen-gpios:
type: phandle-array
required: true
description: GPIO for sensor enable (SEN pin)
s0-gpios:
type: phandle-array
required: true
description: GPIO for multiplexer select bit 0 (S0 pin)
s1-gpios:
type: phandle-array
required: true
description: GPIO for multiplexer select bit 1 (S1 pin)
measurement-delay-ms:
type: int
required: false
default: 10
description: Delay in milliseconds after setting GPIO pins before reading ADC

View File

@ -1,41 +0,0 @@
# Custom supply voltage sensor binding
description: Supply voltage sensor using VND7050AJ multiplexer
compatible: "custom,supply-voltage"
properties:
io-channels:
type: phandle-array
required: true
description: ADC channel phandle and specifier
io-channel-names:
type: string-array
required: true
description: Names for the ADC channels
reference-mv:
type: int
required: true
description: ADC reference voltage in millivolts
sensor-mux:
type: phandle
required: true
description: Reference to the VND7050AJ sensor multiplexer node
voltage-divider-ratio:
type: int
required: true
description: Voltage divider ratio for scaling measurements
mux-channel:
type: int
required: true
description: Multiplexer channel number (0-3) for this sensor
measurement-delay-ms:
type: int
required: false
default: 5
description: Delay in milliseconds after GPIO setup before measurement

View File

@ -1,60 +0,0 @@
# VND7050AJ Sensor Multiplexer binding
description: VND7050AJ sensor multiplexer for ADC channel selection
compatible: "vnd7050aj,sensor-mux"
properties:
io-channels:
type: phandle-array
required: true
description: ADC channel phandle and specifier for sensor input
io-channel-names:
type: string-array
required: true
description: Names for the ADC channels
reference-mv:
type: int
required: true
description: ADC reference voltage in millivolts
sen-gpios:
type: phandle-array
required: true
description: GPIO for sensor enable (SEN pin)
s0-gpios:
type: phandle-array
required: true
description: GPIO for multiplexer select bit 0 (S0 pin)
s1-gpios:
type: phandle-array
required: true
description: GPIO for multiplexer select bit 1 (S1 pin)
in0-gpios:
type: phandle-array
required: false
description: GPIO for valve input 0 control (IN0 pin)
in1-gpios:
type: phandle-array
required: false
description: GPIO for valve input 1 control (IN1 pin)
rst-gpios:
type: phandle-array
required: false
description: GPIO for reset control (RST pin)
sense-resistor-ohm:
type: int
required: true
description: Current sense resistor value in ohms
k-factor:
type: int
required: true
description: Current sense ratio for VND7050AJ (typical ~1200:1)

View File

@ -1,35 +0,0 @@
# 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 status/select 0 pin
required: true
s1-gpios:
type: phandle-array
description: GPIO for status/select 1 pin
required: true

View File

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

View File

@ -1,4 +1,3 @@
#include <app_version.h>
#include <lib/fwu.h>
#include <lib/modbus_server.h>
#include <lib/valve.h>
@ -9,9 +8,7 @@
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
int main(void) {
int rc;
LOG_INF("Starting Irrigation System Slave Node version %s (Build version %s)",
APP_VERSION_STRING, STRINGIFY(APP_BUILD_VERSION));
LOG_INF("Starting Irrigation System Slave Node");
if (settings_subsys_init() || settings_load()) {
LOG_ERR("Settings initialization or loading failed");
@ -20,9 +17,8 @@ int main(void) {
valve_init();
fwu_init();
rc = modbus_server_init();
if (rc < 0) {
LOG_ERR("Modbus RTU server initialization failed: %d", rc);
if (modbus_server_init()) {
LOG_ERR("Modbus RTU server initialization failed");
return 0;
}

View File

@ -1,67 +0,0 @@
#ifndef ADC_SENSOR_H
#define ADC_SENSOR_H
#include <stdint.h>
/**
* @file adc_sensor.h
* @brief API for the ADC sensor library.
*
* This library provides functions to initialize and read from the ADC sensors,
* specifically for measuring supply voltage and motor current.
* It can operate in a real or simulated mode.
*/
/**
* @brief Initializes the ADC sensor system.
*
* This function sets up the necessary ADC channels and configurations.
* It should be called once before any other function in this library.
* In simulated mode, it logs the simulated values.
*
* @return 0 on success, or a negative error code on failure.
*/
int adc_sensor_init(void);
/**
* @brief Gets the current supply voltage reading.
*
* This function reads the value from the corresponding ADC channel and converts
* it to millivolts.
*
* @return The supply voltage in millivolts (mV).
*/
uint16_t adc_sensor_get_voltage_mv(void);
/**
* @brief Gets the current motor current reading (legacy function).
*
* This function reads the current from the opening channel (IN0).
* For backward compatibility only. Use adc_sensor_get_current_open_ma()
* instead.
*
* @return The motor current in milliamps (mA).
*/
uint16_t adc_sensor_get_current_ma(void);
/**
* @brief Gets the motor opening current reading (IN0 current sense).
*
* This function reads the current from the VND7050AJ IN0 current sense channel.
* Used for monitoring valve opening current.
*
* @return The motor opening current in milliamps (mA).
*/
uint16_t adc_sensor_get_current_open_ma(void);
/**
* @brief Gets the motor closing current reading (IN1 current sense).
*
* This function reads the current from the VND7050AJ IN1 current sense channel.
* Used for monitoring valve closing current.
*
* @return The motor closing current in milliamps (mA).
*/
uint16_t adc_sensor_get_current_close_ma(void);
#endif /* ADC_SENSOR_H */

View File

@ -17,56 +17,56 @@
*/
enum {
/**
* @brief Kombiniertes Status-Register für das Ventil.
* High-Byte: Bewegung (0=Idle, 1=Öffnet, 2=Schliesst, 3=Fehler).
* Low-Byte: Zustand (0=Geschlossen, 1=Geöffnet).
* @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).
*/
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000,
/**
* @brief Motorstrom beim Öffnen in Milliampere (mA) - IN0 current sense.
* @brief Motor current during opening in milliamperes (mA).
*/
REG_INPUT_MOTOR_OPEN_CURRENT_MA = 0x0001,
/**
* @brief Motorstrom beim Schließen in Milliampere (mA) - IN1 current sense.
* @brief Motor current during closing in milliamperes (mA).
*/
REG_INPUT_MOTOR_CLOSE_CURRENT_MA = 0x0002,
/**
* @brief Bitmaske der digitalen Eingänge. Bit 0: Eingang 1, Bit 1: Eingang 2.
* 1=Aktiv.
* @brief Bitmask of digital inputs. Bit 0: Input 1, Bit 1: Input 2.
* 1=Active.
*/
REG_INPUT_DIGITAL_INPUTS_STATE = 0x0020,
/**
* @brief Event-Flags für Taster (Clear-on-Read). Bit 0: Taster 1 gedrückt.
* Bit 1: Taster 2 gedrückt.
* @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, z.B. 0x0102 r v1.2.
* @brief Firmware version, e.g., 0x0102 for v1.2.
*/
REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR = 0x00F0,
/**
* @brief Firmware-Version Patch-Level, z.B. 3 r v1.2.3.
* @brief Firmware version patch level, e.g., 3 for v1.2.3.
*/
REG_INPUT_FIRMWARE_VERSION_PATCH = 0x00F1,
/**
* @brief Gerätestatus (0=OK, 1=Allgemeiner Fehler).
* @brief Device status (0=OK, 1=General Error).
*/
REG_INPUT_DEVICE_STATUS = 0x00F2,
/**
* @brief Untere 16 Bit der Uptime in Sekunden.
* @brief Lower 16 bits of uptime in seconds.
*/
REG_INPUT_UPTIME_SECONDS_LOW = 0x00F3,
/**
* @brief Obere 16 Bit der Uptime in Sekunden.
* @brief Upper 16 bits of uptime in seconds.
*/
REG_INPUT_UPTIME_SECONDS_HIGH = 0x00F4,
/**
* @brief Aktuelle Versorgungsspannung in Millivolt (mV).
* @brief Current supply voltage in millivolts (mV).
*/
REG_INPUT_SUPPLY_VOLTAGE_MV = 0x00F5,
/**
* @brief CRC16 des zuletzt im Puffer empfangenen Daten-Chunks für das
* Firmware-Update.
* @brief CRC16 of the last received data chunk in the buffer for firmware
* update.
*/
REG_INPUT_FWU_LAST_CHUNK_CRC = 0x0100
};
@ -77,52 +77,52 @@ enum {
*/
enum {
/**
* @brief Ventilsteuerungsbefehl (1=Öffnen, 2=Schliessen, 0=Bewegung stoppen).
* @brief Valve control command (1=Open, 2=Close, 0=Stop movement).
*/
REG_HOLDING_VALVE_COMMAND = 0x0000,
/**
* @brief Sicherheits-Timeout in Sekunden für den Öffnen-Vorgang.
* @brief Safety timeout in seconds for the opening process.
*/
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001,
/**
* @brief Sicherheits-Timeout in Sekunden für den Schliessen-Vorgang.
* @brief Safety timeout in seconds for the closing process.
*/
REG_HOLDING_MAX_CLOSING_TIME_S = 0x0002,
/**
* @brief Bitmaske zum Lesen und Schreiben der digitalen Ausgänge. Bit 0:
* Ausgang 1, Bit 1: Ausgang 2. 1=AN, 0=AUS.
* @brief Bitmask for reading and writing digital outputs. Bit 0: Output 1,
* Bit 1: Output 2. 1=ON, 0=OFF.
*/
REG_HOLDING_DIGITAL_OUTPUTS_STATE = 0x0010,
/**
* @brief Timeout des Fail-Safe-Watchdogs in Sekunden. 0=Deaktiviert.
* @brief Fail-safe watchdog timeout in seconds. 0=Disabled.
*/
REG_HOLDING_WATCHDOG_TIMEOUT_S = 0x00F0,
/**
* @brief Schreiben von 1 startet das Gerät neu.
* @brief Writing 1 restarts the device.
*/
REG_HOLDING_DEVICE_RESET = 0x00F1,
/**
* @brief Befehl für das Firmware-Update.
* 1: Verify Chunk - Slave schreibt den letzten Chunk ins Flash.
* 2: Finalize Update - Installation abschliessen und neu starten.
* @brief Command for firmware update.
* 1: Verify Chunk - Slave writes the last chunk to flash.
* 2: Finalize Update - Complete installation and restart.
*/
REG_HOLDING_FWU_COMMAND = 0x0100,
/**
* @brief Untere 16 Bit des 32-Bit-Offsets für den nächsten
* Firmware-Update-Chunk.
* @brief Lower 16 bits of the 32-bit offset for the next firmware update
* chunk.
*/
REG_HOLDING_FWU_CHUNK_OFFSET_LOW = 0x0101,
/**
* @brief Obere 16 Bit des 32-Bit-Offsets für den nächsten
* Firmware-Update-Chunk.
* @brief Upper 16 bits of the 32-bit offset for the next firmware update
* chunk.
*/
REG_HOLDING_FWU_CHUNK_OFFSET_HIGH = 0x0102,
/**
* @brief Grösse des nächsten Firmware-Update-Chunks in Bytes (max. 256).
* @brief Size of the next firmware update chunk in bytes (max. 256).
*/
REG_HOLDING_FWU_CHUNK_SIZE = 0x0103,
/**
* @brief Startadresse des 256-Byte-Puffers für Firmware-Update-Daten.
* @brief Start address of the 256-byte buffer for firmware update data.
*/
REG_HOLDING_FWU_DATA_BUFFER = 0x0180,
};

View File

@ -13,19 +13,9 @@
* configuring the maximum opening and closing times.
*/
/**
* @brief Defines the GPIO pins used for the valve controller.
*/
struct valve_gpios {
const struct gpio_dt_spec
in0; /**< Control input 0 for the VND7050AJ driver. */
const struct gpio_dt_spec
in1; /**< Control input 1 for the VND7050AJ driver. */
const struct gpio_dt_spec rst; /**< Reset pin for the VND7050AJ driver. */
const struct gpio_dt_spec sen; /**< Sense (current measurement) pin. */
const struct gpio_dt_spec s0; /**< S0 select pin. */
const struct gpio_dt_spec s1; /**< S1 select pin. */
};
#define VALVE_CHANNEL_OPEN 0
#define VALVE_CHANNEL_CLOSE 1
#define VALVE_ENDPOSITION_CHECK_INTERVAL K_MSEC(100)
/**
* @brief Represents the static state of the valve (open or closed).
@ -49,8 +39,11 @@ enum valve_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.
*/
void valve_init(void);
int valve_init(void);
/**
* @brief Starts opening the valve.
@ -120,4 +113,31 @@ uint16_t valve_get_max_open_time(void);
*/
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);
#endif // VALVE_H

View File

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

View File

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

View File

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

View File

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

View File

@ -1,382 +0,0 @@
/**
* @file adc_sensor.c
* @brief Implementation of the ADC sensor library.
*
* This file contains the implementation for initializing and reading from ADC
* sensors. It currently provides simulated values for voltage and current, with
* placeholders for real hardware ADC implementation including GPIO control.
*/
#include <lib/adc_sensor.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(adc_sensor, LOG_LEVEL_INF);
// Simulated values
#define SIMULATED_VOLTAGE_MV 12000
#define SIMULATED_CURRENT_MA 45
// Devicetree node checks
#define VOLTAGE_SENSOR_NODE DT_NODELABEL(supply_voltage)
#define CURRENT_OPEN_SENSOR_NODE DT_NODELABEL(motor_current_open)
#define CURRENT_CLOSE_SENSOR_NODE DT_NODELABEL(motor_current_close)
#define VND7050AJ_NODE DT_NODELABEL(vnd7050aj)
#ifndef CONFIG_ADC_SENSOR_SIMULATED
// ADC device reference from voltage sensor node (all sensors use same ADC)
#if DT_NODE_EXISTS(VOLTAGE_SENSOR_NODE)
#define ADC_NODE DT_PHANDLE(VOLTAGE_SENSOR_NODE, io_channels)
#define ADC_REFERENCE_MV DT_PROP(VOLTAGE_SENSOR_NODE, reference_mv)
#endif
#define ADC_CHANNEL 1 /* ADC1 channel 1 as defined in overlay */
// Sensor-specific properties
#if DT_NODE_EXISTS(VOLTAGE_SENSOR_NODE)
#define VOLTAGE_DIVIDER_RATIO \
DT_PROP(VOLTAGE_SENSOR_NODE, voltage_divider_ratio)
#define VOLTAGE_DELAY_MS DT_PROP(VOLTAGE_SENSOR_NODE, measurement_delay_ms)
#endif
#if DT_NODE_EXISTS(CURRENT_OPEN_SENSOR_NODE)
#define CURRENT_OPEN_SENSE_RESISTOR_MOHM \
DT_PROP(CURRENT_OPEN_SENSOR_NODE, current_sense_resistor_mohm)
#define CURRENT_OPEN_K_FACTOR DT_PROP(CURRENT_OPEN_SENSOR_NODE, k_factor)
#define CURRENT_OPEN_DELAY_MS \
DT_PROP(CURRENT_OPEN_SENSOR_NODE, measurement_delay_ms)
#endif
#if DT_NODE_EXISTS(CURRENT_CLOSE_SENSOR_NODE)
#define CURRENT_CLOSE_SENSE_RESISTOR_MOHM \
DT_PROP(CURRENT_CLOSE_SENSOR_NODE, current_sense_resistor_mohm)
#define CURRENT_CLOSE_K_FACTOR DT_PROP(CURRENT_CLOSE_SENSOR_NODE, k_factor)
#define CURRENT_CLOSE_DELAY_MS \
DT_PROP(CURRENT_CLOSE_SENSOR_NODE, measurement_delay_ms)
#endif
static const struct device *adc_dev;
static struct adc_channel_cfg adc_channel_cfg = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME_DEFAULT,
.channel_id = ADC_CHANNEL,
.differential = 0};
static struct adc_sequence adc_sequence = {
.channels = BIT(ADC_CHANNEL),
.buffer_size = sizeof(uint16_t),
.resolution = 12,
};
static uint16_t adc_buffer;
#endif
static bool initialized = false;
#ifndef CONFIG_ADC_SENSOR_SIMULATED
// GPIO specs from VND7050AJ node
#if DT_NODE_EXISTS(VND7050AJ_NODE)
static const struct gpio_dt_spec sen_gpio =
GPIO_DT_SPEC_GET(VND7050AJ_NODE, sen_gpios);
static const struct gpio_dt_spec s0_gpio =
GPIO_DT_SPEC_GET(VND7050AJ_NODE, s0_gpios);
static const struct gpio_dt_spec s1_gpio =
GPIO_DT_SPEC_GET(VND7050AJ_NODE, s1_gpios);
#endif
/**
* @brief Configure GPIO pins for ADC sensor multiplexer control
*/
static int configure_sensor_gpios(void) {
int ret = 0;
#if DT_NODE_EXISTS(VND7050AJ_NODE)
// Configure sensor multiplexer GPIOs
if (gpio_is_ready_dt(&sen_gpio)) {
ret = gpio_pin_configure_dt(&sen_gpio, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure SEN GPIO: %d", ret);
return ret;
}
}
if (gpio_is_ready_dt(&s0_gpio)) {
ret = gpio_pin_configure_dt(&s0_gpio, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure S0 GPIO: %d", ret);
return ret;
}
}
if (gpio_is_ready_dt(&s1_gpio)) {
ret = gpio_pin_configure_dt(&s1_gpio, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure S1 GPIO: %d", ret);
return ret;
}
}
#endif
return 0;
}
/**
* @brief Set multiplexer channel for sensor selection
* @param enable Enable/disable the sensor
* @param channel Multiplexer channel (0-3)
* @param delay_ms Delay after setting GPIOs
*/
static int set_mux_channel(bool enable, uint8_t channel, uint32_t delay_ms) {
#if DT_NODE_EXISTS(VND7050AJ_NODE)
if (gpio_is_ready_dt(&sen_gpio)) {
gpio_pin_set_dt(&sen_gpio, enable ? 1 : 0);
}
if (gpio_is_ready_dt(&s0_gpio)) {
gpio_pin_set_dt(&s0_gpio, (channel & 0x01) ? 1 : 0);
}
if (gpio_is_ready_dt(&s1_gpio)) {
gpio_pin_set_dt(&s1_gpio, (channel & 0x02) ? 1 : 0);
}
// Delay for GPIO settling
if (delay_ms > 0) {
k_msleep(delay_ms);
}
#endif
return 0;
}
#endif /* !CONFIG_ADC_SENSOR_SIMULATED */
#ifndef CONFIG_ADC_SENSOR_SIMULATED
/**
* @brief Read ADC value and convert to millivolts (for voltage sensor)
* @return ADC reading in millivolts, or 0 on error
*/
static uint16_t read_adc_voltage_mv(void) {
#if DT_NODE_EXISTS(VOLTAGE_SENSOR_NODE)
int ret = adc_read(adc_dev, &adc_sequence);
if (ret < 0) {
LOG_ERR("ADC read failed: %d", ret);
return 0;
}
// Convert ADC reading to millivolts
// ADC reading is 12-bit (0-4095) representing 0 to ADC_REFERENCE_MV
uint32_t adc_value = adc_buffer;
uint32_t voltage_mv = (adc_value * ADC_REFERENCE_MV) / 4095;
// Apply voltage divider scaling
voltage_mv *= VOLTAGE_DIVIDER_RATIO;
LOG_DBG("ADC raw: %u, voltage: %u mV", adc_value, (uint16_t)voltage_mv);
return (uint16_t)voltage_mv;
#else
return 0;
#endif
}
/**
* @brief Read ADC value and convert to milliamps for opening current
* @return ADC reading in milliamps, or 0 on error
*/
static uint16_t read_adc_current_open_ma(void) {
#if DT_NODE_EXISTS(CURRENT_OPEN_SENSOR_NODE)
int ret = adc_read(adc_dev, &adc_sequence);
if (ret < 0) {
LOG_ERR("ADC read failed: %d", ret);
return 0;
}
// Convert ADC reading to millivolts first
uint32_t adc_value = adc_buffer;
uint32_t voltage_mv = (adc_value * ADC_REFERENCE_MV) / 4095;
// VND7050AJ current calculation: I = V_sense * K / R_sense
// Where: V_sense in mV, K is the current sense factor, R_sense in mΩ
// Result is in milliamps
uint32_t current_ma = (voltage_mv * CURRENT_OPEN_K_FACTOR * 1000) /
CURRENT_OPEN_SENSE_RESISTOR_MOHM;
LOG_DBG("Open current - ADC raw: %u, voltage: %u mV, current: %u mA",
adc_value, voltage_mv, (uint16_t)current_ma);
return (uint16_t)current_ma;
#else
return 0;
#endif
}
/**
* @brief Read ADC value and convert to milliamps for closing current
* @return ADC reading in milliamps, or 0 on error
*/
static uint16_t read_adc_current_close_ma(void) {
#if DT_NODE_EXISTS(CURRENT_CLOSE_SENSOR_NODE)
int ret = adc_read(adc_dev, &adc_sequence);
if (ret < 0) {
LOG_ERR("ADC read failed: %d", ret);
return 0;
}
// Convert ADC reading to millivolts first
uint32_t adc_value = adc_buffer;
uint32_t voltage_mv = (adc_value * ADC_REFERENCE_MV) / 4095;
// VND7050AJ current calculation: I = V_sense * K / R_sense
// Where: V_sense in mV, K is the current sense factor, R_sense in mΩ
// Result is in milliamps
uint32_t current_ma = (voltage_mv * CURRENT_CLOSE_K_FACTOR * 1000) /
CURRENT_CLOSE_SENSE_RESISTOR_MOHM;
LOG_DBG("Close current - ADC raw: %u, voltage: %u mV, current: %u mA",
adc_value, voltage_mv, (uint16_t)current_ma);
return (uint16_t)current_ma;
#else
return 0;
#endif
}
#endif
int adc_sensor_init(void) {
if (initialized) {
return 0;
}
#ifdef CONFIG_ADC_SENSOR_SIMULATED
LOG_INF("ADC sensor initialized (simulated mode)");
LOG_INF("Simulated values: %dmV, %dmA", SIMULATED_VOLTAGE_MV,
SIMULATED_CURRENT_MA);
#else
// Initialize GPIO pins for sensor control
int ret = configure_sensor_gpios();
if (ret < 0) {
LOG_ERR("Failed to configure sensor GPIOs: %d", ret);
return ret;
}
// Initialize ADC hardware
#if DT_NODE_EXISTS(VOLTAGE_SENSOR_NODE)
adc_dev = DEVICE_DT_GET(ADC_NODE);
if (!device_is_ready(adc_dev)) {
LOG_ERR("ADC device not ready");
return -ENODEV;
}
adc_sequence.buffer = &adc_buffer;
ret = adc_channel_setup(adc_dev, &adc_channel_cfg);
if (ret < 0) {
LOG_ERR("Failed to setup ADC channel: %d", ret);
return ret;
}
LOG_INF("ADC device ready: %s", adc_dev->name);
#endif
LOG_INF("ADC sensor initialized (real ADC mode)");
#if DT_NODE_EXISTS(VOLTAGE_SENSOR_NODE)
LOG_INF("Voltage sensor: divider ratio %d", VOLTAGE_DIVIDER_RATIO);
#endif
#if DT_NODE_EXISTS(CURRENT_OPEN_SENSOR_NODE)
LOG_INF("Open current sensor: K-factor %d, sense resistor %d mΩ",
CURRENT_OPEN_K_FACTOR, CURRENT_OPEN_SENSE_RESISTOR_MOHM);
#endif
#if DT_NODE_EXISTS(CURRENT_CLOSE_SENSOR_NODE)
LOG_INF("Close current sensor: K-factor %d, sense resistor %d mΩ",
CURRENT_CLOSE_K_FACTOR, CURRENT_CLOSE_SENSE_RESISTOR_MOHM);
#endif
#endif
initialized = true;
return 0;
}
uint16_t adc_sensor_get_voltage_mv(void) {
if (!initialized) {
LOG_WRN("ADC sensor not initialized, calling adc_sensor_init()");
adc_sensor_init();
}
#ifdef CONFIG_ADC_SENSOR_SIMULATED
return SIMULATED_VOLTAGE_MV;
#else
// Set multiplexer to voltage channel (channel 3: VCC sense)
#if DT_NODE_EXISTS(VOLTAGE_SENSOR_NODE)
set_mux_channel(true, 3, VOLTAGE_DELAY_MS);
// Read real ADC value for voltage
uint16_t voltage = read_adc_voltage_mv();
// Disable sensor after measurement to save power
set_mux_channel(false, 0, 0);
return voltage;
#else
return 0;
#endif
#endif
}
uint16_t adc_sensor_get_current_ma(void) {
// Legacy function - redirect to opening current for backward compatibility
return adc_sensor_get_current_open_ma();
}
uint16_t adc_sensor_get_current_open_ma(void) {
if (!initialized) {
LOG_WRN("ADC sensor not initialized, calling adc_sensor_init()");
adc_sensor_init();
}
#ifdef CONFIG_ADC_SENSOR_SIMULATED
return SIMULATED_CURRENT_MA;
#else
// Set multiplexer to IN0 current sense channel (channel 0)
#if DT_NODE_EXISTS(CURRENT_OPEN_SENSOR_NODE)
set_mux_channel(true, 0, CURRENT_OPEN_DELAY_MS);
// Read real ADC value for current
uint16_t current = read_adc_current_open_ma();
// Disable sensor after measurement to save power
set_mux_channel(false, 0, 0);
return current;
#else
return 0;
#endif
#endif
}
uint16_t adc_sensor_get_current_close_ma(void) {
if (!initialized) {
LOG_WRN("ADC sensor not initialized, calling adc_sensor_init()");
adc_sensor_init();
}
#ifdef CONFIG_ADC_SENSOR_SIMULATED
return SIMULATED_CURRENT_MA;
#else
// Set multiplexer to IN1 current sense channel (channel 1)
#if DT_NODE_EXISTS(CURRENT_CLOSE_SENSOR_NODE)
set_mux_channel(true, 1, CURRENT_CLOSE_DELAY_MS);
// Read real ADC value for current
uint16_t current = read_adc_current_close_ma();
// Disable sensor after measurement to save power
set_mux_channel(false, 0, 0);
return current;
#else
return 0;
#endif
#endif
}

View File

@ -8,11 +8,11 @@
*/
#include <app_version.h>
#include <lib/adc_sensor.h>
#include <lib/fwu.h>
#include <lib/modbus_server.h>
#include <lib/valve.h>
#include <zephyr/device.h>
#include <zephyr/drivers/misc/vnd7050aj/vnd7050aj.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
@ -148,10 +148,10 @@ static int input_reg_rd(uint16_t addr, uint16_t *reg) {
*reg = (valve_get_movement() << 8) | (valve_get_state() & 0xFF);
break;
case REG_INPUT_MOTOR_OPEN_CURRENT_MA:
*reg = adc_sensor_get_current_open_ma();
*reg = (uint16_t)valve_get_opening_current();
break;
case REG_INPUT_MOTOR_CLOSE_CURRENT_MA:
*reg = adc_sensor_get_current_close_ma();
*reg = (uint16_t)valve_get_closing_current();
break;
case REG_INPUT_UPTIME_SECONDS_LOW:
*reg = (uint16_t)(uptime_s & 0xFFFF);
@ -160,7 +160,7 @@ 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 = adc_sensor_get_voltage_mv();
*reg = (uint16_t)valve_get_vnd_voltage();
break;
case REG_INPUT_FWU_LAST_CHUNK_CRC:
*reg = fwu_get_last_chunk_crc();
@ -190,13 +190,6 @@ static struct modbus_user_callbacks mbs_cbs = {
int modbus_server_init(void) {
k_timer_init(&watchdog_timer, watchdog_timer_handler, NULL);
// Initialize ADC sensor
int ret = adc_sensor_init();
if (ret < 0) {
LOG_ERR("Failed to initialize ADC sensor: %d", ret);
return ret;
}
// Load saved settings
uint32_t saved_baudrate = 19200;
uint8_t saved_unit_id = 1;

View File

@ -10,25 +10,24 @@
#include <lib/valve.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>
#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_INF);
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_state current_state = VALVE_STATE_OPEN;
static enum valve_movement current_movement = VALVE_MOVEMENT_IDLE;
static uint16_t max_opening_time_s = 60;
static uint16_t max_closing_time_s = 60;
static uint16_t max_opening_time_s = 10;
static uint16_t max_closing_time_s = 10;
static uint32_t movement_start_time = 0;
static struct k_work_delayable
valve_work; // Work item for scheduling valve movement timeouts
@ -41,63 +40,85 @@ static struct k_work_delayable
* @param work Pointer to the k_work item.
*/
static void valve_work_handler(struct k_work *work) {
gpio_pin_set_dt(&valve_gpios.in0, 0);
gpio_pin_set_dt(&valve_gpios.in1, 0);
gpio_pin_set_dt(&valve_gpios.rst, 0);
int current_ma = 0;
uint32_t now;
now = k_uptime_get_32();
if (current_movement == VALVE_MOVEMENT_OPENING) {
if (now - movement_start_time > max_opening_time_s * 1000) {
LOG_WRN("Valve opening timeout reached, stopping motor.");
current_movement = VALVE_MOVEMENT_ERROR;
goto work_handler_finish;
}
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_OPEN, &current_ma);
if (current_ma > 10) {
k_work_schedule(&valve_work, VALVE_ENDPOSITION_CHECK_INTERVAL);
return;
}
LOG_INF("Valve finished opening");
} else if (current_movement == VALVE_MOVEMENT_CLOSING) {
if (now - movement_start_time > max_closing_time_s * 1000) {
LOG_WRN("Valve closing timeout reached, stopping motor.");
current_movement = VALVE_MOVEMENT_ERROR;
goto work_handler_finish;
}
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_CLOSE,
&current_ma);
if (current_ma > 10) {
k_work_schedule(&valve_work, VALVE_ENDPOSITION_CHECK_INTERVAL);
return;
}
current_state = VALVE_STATE_CLOSED;
LOG_INF("Valve finished closing");
}
current_movement = VALVE_MOVEMENT_IDLE;
work_handler_finish:
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, false);
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, false);
LOG_INF("Valve work handler finished. Current state: %d, Movement: %d",
current_state, current_movement);
}
void valve_init(void) {
int valve_init(void) {
if (!device_is_ready(vnd7050aj_dev)) {
LOG_ERR("VND7050AJ device is not ready");
return -ENODEV;
}
k_work_init_delayable(&valve_work, valve_work_handler);
settings_load_one("valve/max_open_time", &max_opening_time_s,
sizeof(max_opening_time_s));
settings_load_one("valve/max_close_time", &max_closing_time_s,
sizeof(max_closing_time_s));
gpio_pin_configure_dt(&valve_gpios.in0, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&valve_gpios.in1, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&valve_gpios.rst,
GPIO_OUTPUT_ACTIVE); // Keep VND7050AJ out of reset
gpio_pin_configure_dt(&valve_gpios.sen, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&valve_gpios.s0,
GPIO_OUTPUT_INACTIVE); // S0 select pin - output
gpio_pin_configure_dt(&valve_gpios.s1,
GPIO_OUTPUT_INACTIVE); // S1 select pin - output
LOG_INF("Valve initialized: max_open=%us, max_close=%us", max_opening_time_s,
max_closing_time_s);
valve_close();
return 0;
}
void valve_open(void) {
if (current_state == VALVE_STATE_CLOSED) {
gpio_pin_set_dt(&valve_gpios.rst, 1);
gpio_pin_set_dt(&valve_gpios.in0, 1);
gpio_pin_set_dt(&valve_gpios.in1, 0);
current_state = VALVE_STATE_OPEN;
current_movement = VALVE_MOVEMENT_OPENING;
k_work_schedule(&valve_work, K_MSEC(max_opening_time_s * 1000 * 0.9));
}
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;
current_movement = VALVE_MOVEMENT_OPENING; /* Security: assume valve open as
soons as it starts opening */
movement_start_time = k_uptime_get_32();
k_work_schedule(&valve_work, K_MSEC(100));
}
void valve_close(void) {
if (current_state == VALVE_STATE_OPEN) {
gpio_pin_set_dt(&valve_gpios.rst, 1);
gpio_pin_set_dt(&valve_gpios.in0, 0);
gpio_pin_set_dt(&valve_gpios.in1, 1);
current_movement = VALVE_MOVEMENT_CLOSING;
k_work_schedule(&valve_work, K_MSEC(max_closing_time_s * 1000 * 0.9));
}
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);
movement_start_time = k_uptime_get_32();
current_movement = VALVE_MOVEMENT_CLOSING;
k_work_schedule(&valve_work, VALVE_ENDPOSITION_CHECK_INTERVAL);
}
void valve_stop(void) {
k_work_cancel_delayable(&valve_work);
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;
}
@ -119,3 +140,27 @@ void valve_set_max_close_time(uint16_t seconds) {
}
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; }
int32_t valve_get_opening_current(void) {
int32_t current;
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_OPEN, &current);
return current;
}
int32_t valve_get_closing_current(void) {
int32_t current;
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, &current);
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;
}