Compare commits

6 Commits

Author SHA1 Message Date
bb25134b6c feat(modbus): Implement persistent and improved reconfiguration for Modbus server
This commit enhances the Modbus server's configuration handling by:

- Loading saved baudrate and unit ID settings during initialization, ensuring persistence across reboots.
- Providing improved feedback during `modbus_reconfigure`, including logging for successful changes and informing the user when a device restart is required for changes to take effect.
- Saving new configuration settings even if immediate reinitialization fails, allowing them to be applied on the next boot.
2025-07-03 18:59:01 +02:00
9f96384aa5 fix(cdc-acm): Correct CDC ACM overlay configuration
This commit fixes an issue in the `cdc-acm.overlay` file.
2025-07-03 18:57:06 +02:00
b543579393 feat(modbus): Add supply voltage register and display in tool
This commit introduces a new Modbus input register for the system's supply voltage.

- The `modbus-registers.de.md` documentation is updated to include the `SUPPLY_VOLTAGE_MV` register at address `0x00F5` within the system block.
- The `modbus_server.h` header defines the new register.
- The `modbus_server.c` implementation provides a fixed value (12300 mV) for this register.
- The `modbus_tool.py` script is updated to read and display this new supply voltage value in the UI.

This lays the groundwork for integrating actual voltage measurements in the future.
2025-07-03 18:47:48 +02:00
69cf7e9511 feat(valve): Implement GPIO control for VND7050AJ
This commit implements the real valve control using the GPIOs connected to the VND7050AJ driver.

- The `weact_stm32g431_core.overlay` is updated with a specific compatible string and a device tree label for the valve controller.
- `valve.h` is extended to include GPIO device specifications.
- `valve.c` now initializes and controls the GPIOs for opening and closing the valve, including the reset logic. The IN0 and IN1 pins are interlocked to prevent simultaneous activation. The RST pin is activated before each movement and deactivated afterward.

This replaces the previous virtual/simulated valve logic with actual hardware control.
2025-07-03 18:17:31 +02:00
8df7aef51b Removed unused lib dir 2025-07-03 17:47:48 +02:00
f6ee0a5122 feat(weact_stm32g431_core): Configure VND7050AJ driver pins in overlay
Updated the weact_stm32g431_core.overlay to define the GPIO and ADC
pin assignments for the VND7050AJ driver. This includes:
- Digital I/O pins (IN0, IN1, RST, S0, S1, SEN) configured as GPIOs.
- Analog input pin (MULTISENSE/PA0) configured for ADC1.
2025-07-03 17:39:04 +02:00
11 changed files with 154 additions and 48 deletions

View File

@@ -38,6 +38,7 @@ Alle Register sind in einer einzigen, durchgehenden Liste pro Register-Typ (`Inp
| **0x00F2** | `DEVICE_STATUS` | System | `0`=OK, `1`=Allgemeiner Fehler. |
| **0x00F3** | `UPTIME_SECONDS_LOW` | System | Untere 16 Bit der Uptime in Sekunden. |
| **0x00F4** | `UPTIME_SECONDS_HIGH` | System | Obere 16 Bit der Uptime. |
| **0x00F5** | `SUPPLY_VOLTAGE_MV` | System | Aktuelle Versorgungsspannung in Millivolt (mV). |
| **0x0100** | `FWU_LAST_CHUNK_CRC` | Firmware-Update | Enthält den CRC16 des zuletzt im Puffer empfangenen Daten-Chunks. |
## 3. Holding Registers (4xxxx, Read/Write)

View File

@@ -1,6 +0,0 @@
cmake_minimum_required(VERSION 3.20)
project(fwu)
target_sources(fwu PRIVATE src/fwu.c)
target_include_directories(fwu PUBLIC include)

View File

@@ -1,6 +0,0 @@
cmake_minimum_required(VERSION 3.20)
project(modbus_server)
target_sources(modbus_server PRIVATE src/modbus_server.c)
target_include_directories(modbus_server PUBLIC include)

View File

@@ -1,6 +0,0 @@
cmake_minimum_required(VERSION 3.20)
project(valve)
target_sources(valve PRIVATE src/valve.c)
target_include_directories(valve PUBLIC include)

View File

@@ -1,9 +1,50 @@
/ {
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
};
};
&usart1 {
modbus0 {
compatible = "zephyr,modbus-serial";
status = "okay";
};
status = "okay";
pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>;
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>;
// Definition des ADC-Kanals für MULTISENSE (PA0)
channel@1 { // ADC1_IN1 ist Kanal 1
reg = <1>; // Kanalnummer
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};
&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

@@ -8,3 +8,7 @@
};
};
};
&usart1 {
/delete-node/ modbus0;
};

View File

@@ -6,42 +6,44 @@
/**
* @brief Modbus Input Register Addresses.
*/
enum {
enum
{
/* Valve Control & Status */
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000,
REG_INPUT_MOTOR_CURRENT_MA = 0x0001,
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000,
REG_INPUT_MOTOR_CURRENT_MA = 0x0001,
/* Digital Inputs */
REG_INPUT_DIGITAL_INPUTS_STATE = 0x0020,
REG_INPUT_BUTTON_EVENTS = 0x0021,
REG_INPUT_DIGITAL_INPUTS_STATE = 0x0020,
REG_INPUT_BUTTON_EVENTS = 0x0021,
/* System Config & Status */
REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR = 0x00F0,
REG_INPUT_FIRMWARE_VERSION_PATCH = 0x00F1,
REG_INPUT_DEVICE_STATUS = 0x00F2,
REG_INPUT_UPTIME_SECONDS_LOW = 0x00F3,
REG_INPUT_UPTIME_SECONDS_HIGH = 0x00F4,
/* Firmware Update */
REG_INPUT_FWU_LAST_CHUNK_CRC = 0x0100,
REG_INPUT_FIRMWARE_VERSION_PATCH = 0x00F1,
REG_INPUT_DEVICE_STATUS = 0x00F2,
REG_INPUT_UPTIME_SECONDS_LOW = 0x00F3,
REG_INPUT_UPTIME_SECONDS_HIGH = 0x00F4,
REG_INPUT_SUPPLY_VOLTAGE_MV = 0x00F5,
REG_INPUT_FWU_LAST_CHUNK_CRC = 0x0100
};
/**
* @brief Modbus Holding Register Addresses.
*/
enum {
enum
{
/* Valve Control */
REG_HOLDING_VALVE_COMMAND = 0x0000,
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001,
REG_HOLDING_MAX_CLOSING_TIME_S = 0x0002,
REG_HOLDING_VALVE_COMMAND = 0x0000,
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001,
REG_HOLDING_MAX_CLOSING_TIME_S = 0x0002,
/* Digital Outputs */
REG_HOLDING_DIGITAL_OUTPUTS_STATE = 0x0010,
REG_HOLDING_DIGITAL_OUTPUTS_STATE = 0x0010,
/* System Config */
REG_HOLDING_WATCHDOG_TIMEOUT_S = 0x00F0,
REG_HOLDING_DEVICE_RESET = 0x00F1,
REG_HOLDING_WATCHDOG_TIMEOUT_S = 0x00F0,
REG_HOLDING_DEVICE_RESET = 0x00F1,
/* Firmware Update */
REG_HOLDING_FWU_COMMAND = 0x0100,
REG_HOLDING_FWU_CHUNK_OFFSET_LOW = 0x0101,
REG_HOLDING_FWU_CHUNK_OFFSET_HIGH = 0x0102,
REG_HOLDING_FWU_CHUNK_SIZE = 0x0103,
REG_HOLDING_FWU_DATA_BUFFER = 0x0180,
REG_HOLDING_FWU_COMMAND = 0x0100,
REG_HOLDING_FWU_CHUNK_OFFSET_LOW = 0x0101,
REG_HOLDING_FWU_CHUNK_OFFSET_HIGH = 0x0102,
REG_HOLDING_FWU_CHUNK_SIZE = 0x0103,
REG_HOLDING_FWU_DATA_BUFFER = 0x0180,
};
int modbus_server_init(void);

View File

@@ -2,8 +2,21 @@
#define VALVE_H
#include <stdint.h>
#include <zephyr/drivers/gpio.h>
enum valve_state { VALVE_STATE_CLOSED, VALVE_STATE_OPEN };
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;
};
enum valve_state {
VALVE_STATE_CLOSED,
VALVE_STATE_OPEN,
};
enum valve_movement { VALVE_MOVEMENT_IDLE, VALVE_MOVEMENT_OPENING, VALVE_MOVEMENT_CLOSING, VALVE_MOVEMENT_ERROR };
void valve_init(void);

View File

@@ -128,6 +128,9 @@ static int input_reg_rd(uint16_t addr, uint16_t *reg)
case REG_INPUT_UPTIME_SECONDS_HIGH:
*reg = (uint16_t)(uptime_s >> 16);
break;
case REG_INPUT_SUPPLY_VOLTAGE_MV:
*reg = 12300;
break;
case REG_INPUT_FWU_LAST_CHUNK_CRC:
*reg = fwu_get_last_chunk_crc();
break;
@@ -155,6 +158,17 @@ static struct modbus_user_callbacks mbs_cbs = {
int modbus_server_init(void)
{
k_timer_init(&watchdog_timer, watchdog_timer_handler, NULL);
// Load saved settings
uint32_t saved_baudrate = 19200;
uint8_t saved_unit_id = 1;
settings_load_one("modbus/baudrate", &saved_baudrate, sizeof(saved_baudrate));
settings_load_one("modbus/unit_id", &saved_unit_id, sizeof(saved_unit_id));
// Apply loaded settings
server_param.serial.baud = saved_baudrate;
server_param.server.unit_id = saved_unit_id;
const char iface_name[] = {DEVICE_DT_NAME(MODBUS_NODE)};
#if DT_NODE_HAS_COMPAT(DT_PARENT(MODBUS_NODE), zephyr_cdc_acm_uart)
const struct device *const dev = DEVICE_DT_GET(DT_PARENT(MODBUS_NODE));
@@ -179,20 +193,37 @@ int modbus_server_init(void)
return modbus_iface;
}
server_param.server.user_cb = &mbs_cbs;
LOG_INF("Starting Modbus server: baudrate=%u, unit_id=%u", saved_baudrate, saved_unit_id);
return modbus_init_server(modbus_iface, server_param);
}
int modbus_reconfigure(uint32_t baudrate, uint8_t unit_id)
{
// Update parameters
server_param.serial.baud = baudrate;
server_param.server.unit_id = unit_id;
// Try to reinitialize - this should work for most cases
int ret = modbus_init_server(modbus_iface, server_param);
if (ret == 0)
{
settings_save_one("modbus/baudrate", &baudrate, sizeof(baudrate));
settings_save_one("modbus/unit_id", &unit_id, sizeof(unit_id));
LOG_INF("Modbus reconfigured: baudrate=%u, unit_id=%u", baudrate, unit_id);
}
else
{
LOG_ERR("Failed to reconfigure Modbus: %d", ret);
LOG_INF("Modbus reconfiguration requires restart to take effect");
// Save settings for next boot
settings_save_one("modbus/baudrate", &baudrate, sizeof(baudrate));
settings_save_one("modbus/unit_id", &unit_id, sizeof(unit_id));
LOG_INF("Settings saved. Type 'reset' to restart the device and apply the change.");
return 0; // Return success since settings are saved
}
return ret;

View File

@@ -1,10 +1,21 @@
#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <zephyr/logging/log.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <lib/valve.h>
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_movement current_movement = VALVE_MOVEMENT_IDLE;
static uint16_t max_opening_time_s = 60;
@@ -13,11 +24,15 @@ static struct k_work_delayable valve_work;
static void valve_work_handler(struct k_work *work)
{
gpio_pin_set_dt(&valve_gpios.in0, 0);
gpio_pin_set_dt(&valve_gpios.in1, 0);
gpio_pin_set_dt(&valve_gpios.rst, 0);
if (current_movement == VALVE_MOVEMENT_OPENING) {
LOG_INF("Virtual valve finished opening");
LOG_INF("Valve finished opening");
} else if (current_movement == VALVE_MOVEMENT_CLOSING) {
current_state = VALVE_STATE_CLOSED;
LOG_INF("Virtual valve finished closing");
LOG_INF("Valve finished closing");
}
current_movement = VALVE_MOVEMENT_IDLE;
}
@@ -27,22 +42,35 @@ void valve_init(void)
k_work_init_delayable(&valve_work, valve_work_handler);
settings_load_one("valve/max_open_time", &max_opening_time_s, sizeof(max_opening_time_s));
settings_load_one("valve/max_close_time", &max_closing_time_s, sizeof(max_closing_time_s));
gpio_pin_configure_dt(&valve_gpios.in0, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&valve_gpios.in1, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&valve_gpios.rst, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&valve_gpios.sen, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&valve_gpios.s0, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&valve_gpios.s1, GPIO_OUTPUT_INACTIVE);
}
void valve_open(void)
{
if (current_state == VALVE_STATE_CLOSED) {
gpio_pin_set_dt(&valve_gpios.rst, 1);
gpio_pin_set_dt(&valve_gpios.in1, 0);
gpio_pin_set_dt(&valve_gpios.in0, 1);
current_state = VALVE_STATE_OPEN;
current_movement = VALVE_MOVEMENT_OPENING;
k_work_schedule(&valve_work, K_SECONDS(max_opening_time_s));
k_work_schedule(&valve_work, K_MSEC(max_opening_time_s * 1000 * 0.9));
}
}
void valve_close(void)
{
if (current_state == VALVE_STATE_OPEN) {
gpio_pin_set_dt(&valve_gpios.rst, 1);
gpio_pin_set_dt(&valve_gpios.in0, 0);
gpio_pin_set_dt(&valve_gpios.in1, 1);
current_movement = VALVE_MOVEMENT_CLOSING;
k_work_schedule(&valve_work, K_SECONDS(max_closing_time_s));
k_work_schedule(&valve_work, K_MSEC(max_closing_time_s * 1000 * 0.9));
}
}

View File

@@ -19,6 +19,7 @@ REG_INPUT_FIRMWARE_VERSION_PATCH = 0x00F1
REG_INPUT_DEVICE_STATUS = 0x00F2
REG_INPUT_UPTIME_SECONDS_LOW = 0x00F3
REG_INPUT_UPTIME_SECONDS_HIGH = 0x00F4
REG_INPUT_SUPPLY_VOLTAGE_MV = 0x00F5
REG_INPUT_FWU_LAST_CHUNK_CRC = 0x0100
REG_HOLDING_VALVE_COMMAND = 0x0000
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001
@@ -84,7 +85,7 @@ def poll_status(slave_id, interval):
# If connected, try to read data
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=5, 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=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)
@@ -109,9 +110,11 @@ def poll_status(slave_id, interval):
fw_minor = ir_sys.registers[0] & 0xFF
fw_patch = ir_sys.registers[1]
uptime_seconds = (ir_sys.registers[4] << 16) | ir_sys.registers[3]
supply_voltage_mv = ir_sys.registers[5]
new_data["firmware"] = f"v{fw_major}.{fw_minor}.{fw_patch}"
new_data["device_status"] = "OK" if ir_sys.registers[2] == 0 else "ERROR"
new_data["uptime"] = format_uptime(uptime_seconds)
new_data["supply_voltage"] = f"{supply_voltage_mv / 1000.0:.2f} V"
new_data["watchdog"] = f"{hr_sys.registers[0]}s"
new_data["error"] = None # Clear any previous error on successful read
reconnect_attempts = 0 # Reset attempts on successful communication
@@ -302,6 +305,7 @@ def main_menu(stdscr, slave_id):
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(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)