Added modbus lib and test

This commit is contained in:
Eduard Iten 2025-06-12 17:04:34 +02:00
parent 8d5139c621
commit 57f7060c0e
11 changed files with 353 additions and 10 deletions

12
software/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,12 @@
{
"nrf-connect.applications": [
"${workspaceFolder}\\modbus_test"
],
"files.associations": {
"log.h": "c",
"modbus.h": "c",
"array": "c",
"string": "c",
"string_view": "c"
}
}

View File

@ -10,12 +10,12 @@
#include <zephyr/dt-bindings/input/input-event-codes.h>
/ {
model = "STMicroelectronics STM32F103RB-NUCLEO board";
compatible = "st,stm32f103rb-nucleo";
model = "Iten engineering Valve Node";
compatible = "iten,valve-node", "st,stm32f103rb";
chosen {
zephyr,console = &usart2;
zephyr,shell-uart = &usart2;
zephyr,console = &usart1;
zephyr,shell-uart = &usart1;
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,canbus = &can1;
@ -25,7 +25,7 @@
compatible = "gpio-leds";
green_led_2: led_2 {
gpios = <&gpioa 5 GPIO_ACTIVE_HIGH>;
gpios = <&gpiob 2 GPIO_ACTIVE_HIGH>;
label = "User LD2";
};
};
@ -81,8 +81,7 @@
};
&clk_hse {
hse-bypass;
clock-frequency = <DT_FREQ_M(8)>; /* STLink 8MHz clock */
clock-frequency = <DT_FREQ_M(8)>;
status = "okay";
};
@ -104,15 +103,26 @@
&usart1 {
pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>;
pinctrl-names = "default";
current-speed = <115200>;
status = "okay";
current-speed = <115200>;
};
&usart2 {
pinctrl-0 = <&usart2_tx_pa2 &usart2_rx_pa3>;
current-speed = <9600>;
pinctrl-names = "default";
current-speed = <115200>;
status = "okay";
modbus0 {
compatible = "zephyr,modbus-serial";
status = "okay";
//de-gpios = <&gpioa 15 GPIO_ACTIVE_LOW>;
};
};
&usart3 {
pinctrl-0 = <&usart3_tx_pb10 &usart3_rx_pb11>;
current-speed = <115200>;
pinctrl-names = "default";
};
&i2c1 {

View File

@ -4,7 +4,6 @@
CONFIG_SERIAL=y
# enable console
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
# enable GPIO
CONFIG_GPIO=y

164
software/lib/modbus.c Normal file
View File

@ -0,0 +1,164 @@
#include "modbus.h"
#include <zephyr/kernel.h>
#include <zephyr/sys/util.h>
#include <zephyr/modbus/modbus.h>
#include <zephyr/logging/log.h>
// LOG_MODULE_REGISTER(mbc, CONFIG_LOG_DEFAULT_LEVEL);
LOG_MODULE_REGISTER(mbc, 4); // Set log level to 4 (Debug)
#define MODBUS_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(zephyr_modbus_serial)
static int client_iface;
static struct
{
int level; // Water level value
int minimum; // Minimum value
int maximum; // Maximum value
int factor; // Factor for unit conversion
} measurement;
int mb_init_client(void)
{
const char iface_name[] = {DEVICE_DT_NAME(MODBUS_NODE)};
client_iface = modbus_iface_get_by_name(iface_name);
LOG_DBG("Modbus client interface: %d", client_iface);
if (client_iface < 0)
{
LOG_ERR("Failed to get Modbus interface by name: %s", iface_name);
return client_iface;
}
return modbus_init_client(client_iface, client_param);
}
int mb_read_holding_registers(int node, uint16_t reg_addr, uint16_t *data, size_t len)
{
return modbus_read_holding_regs(client_iface, node, reg_addr, data, len);
}
int mb_read()
{
int rc;
int16_t data[5] = {0};
rc = mb_read_holding_registers(1, 0x0002, data, sizeof(data) / sizeof(data[0]));
if (rc < 0)
{
LOG_ERR("Failed to read holding registers: %d", rc);
return rc;
}
LOG_HEXDUMP_DBG(data, sizeof(data), "Holding Registers Data");
int unit, decimals;
unit = data[0];
decimals = data[1];
int factor;
switch (unit)
{
case 1: // cm
factor = 10;
break;
case 2: // mm
factor = 1;
break;
default:
LOG_ERR("Unknown unit: %d", unit);
return -EINVAL;
}
switch(decimals)
{
case 0: // no decimals
factor /= 1;
break;
case 1: // one decimal
factor /= 10;
break;
case 2: // two decimals
factor /= 100;
break;
default:
LOG_ERR("Unknown decimals: %d", decimals);
return -EINVAL;
}
measurement.factor = factor;
measurement.level = data[2] * factor;
measurement.minimum = data[3] * factor;
measurement.maximum = data[4] * factor;
LOG_DBG("Water level: %dmm, Minimum: %dmm, Maximum: %dmm",
measurement.level, measurement.minimum, measurement.maximum);
return 0;
}
int mb_read_water_level(double *mb_read_water_level)
{
int rc = mb_read();
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
return rc;
}
*mb_read_water_level = (double)measurement.level / 1000.0; // Convert to meters
return 0;
}
int mb_read_water_level_mm(int *mb_read_water_level)
{
int rc = mb_read();
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
return rc;
}
*mb_read_water_level = measurement.level;
return 0;
}
int mb_read_minimum_mm(int *mb_read_minimum)
{
int rc = mb_read();
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
return rc;
}
*mb_read_minimum = measurement.minimum;
return 0;
}
int mb_read_maximum_mm(int *mb_read_maximum)
{
int rc = mb_read();
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
return rc;
}
*mb_read_maximum = measurement.maximum;
return 0;
}
int mb_write_minimum_mm(int minimum)
{
int rc = mb_read();
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
return rc;
}
modbus_write_holding_reg(client_iface, 1, 0x0005, minimum / measurement.factor);
return 0;
}
int mb_write_maximum_mm(int maximum)
{
int rc = mb_read();
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
return rc;
}
modbus_write_holding_reg(client_iface, 1, 0x0006, maximum / measurement.factor);
return 0;
}

24
software/lib/modbus.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef __waterlevel_h
#define __waterlevel_h
#include <zephyr/modbus/modbus.h>
const static struct modbus_iface_param client_param = {
.mode = MODBUS_MODE_RTU,
.rx_timeout = 50000,
.serial = {
.baud = 9600,
.parity = UART_CFG_PARITY_NONE,
},
};
int mb_init_client(void);
int mb_read_holding_registers(int node, uint16_t reg_addr, uint16_t *data, size_t len);
int mb_read_water_level(double *mb_read_water_level);
int mb_read_water_level_mm(int *mb_read_water_level_mm);
int mb_read_minimum_mm(int *mb_read_minimum);
int mb_read_maximum_mm(int *mb_read_maximum);
int mb_write_minimum_mm(int minimum);
int mb_write_maximum_mm(int maximum);
#endif

1
software/modbus_test/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build

View File

@ -0,0 +1,16 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
]
}
],
"version": 4
}

View File

@ -0,0 +1,14 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
list(APPEND BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/..)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(hello_world)
target_sources(app PRIVATE src/main.c)
target_sources(app PRIVATE ../lib/modbus.c)
target_include_directories(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../lib
)

View File

@ -0,0 +1,9 @@
&usart1 {
status = "okay";
current-speed = <9600>;
modbus0 {
compatible = "zephyr,modbus-serial";
status = "okay";
// de-gpios = <&arduino_header 15 GPIO_ACTIVE_LOW>; /* D9 */
};
};

View File

@ -0,0 +1,13 @@
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_UART_CONSOLE=y # Console on USART1
#CONFIG_RTT_CONSOLE=y
#CONFIG_USE_SEGGER_RTT=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_UART_LINE_CTRL=n
CONFIG_MODBUS=y
CONFIG_MODBUS_ROLE_CLIENT=y

View File

@ -0,0 +1,81 @@
/* Testing MODBUS functionality
This code initializes a Modbus client, sets minimum and maximum water levels,
reads the current water level, and logs the results.
You can set the zero point and the 2m point for water level in lines 32 and 38.
*/
#include <zephyr/logging/log.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include "modbus.h"
LOG_MODULE_REGISTER(main, CONFIG_LOG_DEFAULT_LEVEL);
/* 1000 msec = 1 sec */
#define SLEEP_TIME_MS 1000
int main(void)
{
int rc;
rc = mb_init_client();
if (rc != 0)
{
LOG_ERR("Failed to initialize Modbus client: %d", rc);
return rc;
}
LOG_INF("Modbus client initialized successfully");
double water_level = 0.0;
int water_level_mm, water_level_min_mm, water_level_max_mm = 0;
rc = mb_write_minimum_mm(42); // Set the zero point for water level
if (rc < 0)
{
LOG_ERR("Failed to write minimum water level: %d", rc);
return rc;
}
rc = mb_write_maximum_mm(2000); // Set the 2m point for water level
if (rc < 0)
{
LOG_ERR("Failed to write maximum water level: %d", rc);
return rc;
}
rc = mb_read_minimum_mm(&water_level_min_mm);
if (rc < 0)
{
LOG_ERR("Failed to read minimum water level: %d", rc);
return rc;
}
rc = mb_read_maximum_mm(&water_level_max_mm);
if (rc < 0)
{
LOG_ERR("Failed to read maximum water level: %d", rc);
return rc;
}
LOG_INF("Water zero point is set to: %dmm", water_level_min_mm);
LOG_INF("Water 2m point is set to: %dmm", water_level_max_mm);
while (1)
{ /* Read water level */
rc = mb_read_water_level(&water_level);
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
return rc;
}
rc = mb_read_water_level_mm(&water_level_mm);
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
return rc;
}
LOG_INF("Water level: %.3fm", water_level);
LOG_INF("Water level: %dmm", water_level_mm);
LOG_INF("Modbus test completed successfully");
return 0;
k_sleep(K_SECONDS(1));
}
return 0;
}