From a4eb887b6cb1926efde97ff6c8db913429c11986 Mon Sep 17 00:00:00 2001 From: Federico Pellegrin Date: Mon, 20 May 2024 07:24:29 +0200 Subject: [PATCH 1/2] Add support for ESP-IDF platform complete with documentation --- src/esp-idf/CMakeLists.txt | 25 +++ src/esp-idf/README.md | 66 ++++++++ src/esp-idf/component.mk | 18 +++ src/esp-idf/config.h | 296 ++++++++++++++++++++++++++++++++++ src/esp-idf/idf_component.yml | 13 ++ src/modbus-rtu.c | 91 +++++++++++ 6 files changed, 509 insertions(+) create mode 100644 src/esp-idf/CMakeLists.txt create mode 100644 src/esp-idf/README.md create mode 100644 src/esp-idf/component.mk create mode 100644 src/esp-idf/config.h create mode 100644 src/esp-idf/idf_component.yml diff --git a/src/esp-idf/CMakeLists.txt b/src/esp-idf/CMakeLists.txt new file mode 100644 index 00000000..6f1102bc --- /dev/null +++ b/src/esp-idf/CMakeLists.txt @@ -0,0 +1,25 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +set(srcs + "src/modbus-data.c" + "src/modbus-rtu.c" + "src/modbus-tcp.c" + "src/modbus.c") + +set(include_dirs src) + +set(priv_include_dirs ../) + +list(APPEND priv_include_dirs) + +add_prefix(srcs "${CMAKE_CURRENT_LIST_DIR}/libmodbus/" ${srcs}) +add_prefix(include_dirs "${CMAKE_CURRENT_LIST_DIR}/libmodbus/" ${include_dirs}) +add_prefix(priv_include_dirs "${CMAKE_CURRENT_LIST_DIR}/libmodbus/" ${priv_include_dirs}) + +message(STATUS "DEBUG: Using libmodbus component folder: ${CMAKE_CURRENT_LIST_DIR}.") + +idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS "${include_dirs}" + PRIV_INCLUDE_DIRS "${priv_include_dirs}" + PRIV_REQUIRES driver vfs) + diff --git a/src/esp-idf/README.md b/src/esp-idf/README.md new file mode 100644 index 00000000..4f7f0d1d --- /dev/null +++ b/src/esp-idf/README.md @@ -0,0 +1,66 @@ +# Instructions to use with Espressif IoT Development Framework (ESP-IDF) + +## Adding libmodbus as a component + +- Create a subdirectory at the top level of your ESP-IDF project where you will +place this and create a component, ie. `libmodbus`. This directory will be +referred as *component directory* further down in this text. + +- Download the latest version of libmodbus with the method of your choice and +unpack it under component directory in a subdirectory `libmodbus` + +- Copy the files supplied in this documentation directory to the component directory, +namely: + - `CMakeLists.txt`: the CMake script that enumerates files and directories used + in the build as well as defines needed dependencies + - `component.mk`: the component build definition + - `config.h`: the library configuration, especially tailored for ESP-IDF. This is + usually generated with the autoconf tool, but this is not present in ESP-IDF and + is therefore manually prepared and customized + - `idf_component.yml`: the component description file + +- Add a reference from your main project in the project top level `CMakeLists.txt` to + the newly added module with something like: `set(EXTRA_COMPONENT_DIRS libmodbus/)`. + If you already have other components you may just add the reference to the newly + added component. + +- As the ESP-IDF does not provide a `nanosleep` function in its SDK, you should add + this in your project so you will be able to find it at linking time, for example: + +``` +int nanosleep(const struct timespec *req, struct timespec *_Nullable rem) { + return usleep(req->tv_sec*1000 + req->tv_nsec / 1000); +} +``` + +Now you are almost ready to use libmodbus in your project! + +If you desire to use the library for serial communication, you will need to do a few +more hardware configuration steps before using the `modbus_new_rtu` call, namely: + +- Configure, if needed, any pins for the used uart via `uart_set_pin` + +- Install the uart driver via the `uart_driver_install` + +- Configure, if needed, the uart mode (ie. set it to half duplex) via `uart_set_mode` + +These configurations are not included in libmodbus as they are highly hardware specific +and would require a heavy change in the library interface. + +## Other details using libmodbus with ESP-IDF + +- The serial driver is implemented using the `vfs` virtual filesystem component. This + makes the changes needed for the library minimal, but may not be the most performant + solution. Be aware that if you are not using the UART0 as console (ie. you are + disabling console or you are using the USB Serial/JTAG Controller) you may need to + explicitly initialize UART VFS in your main program as well just by calling + `uart_vfs_dev_register()` (from `driver/uart_vfs.h`). + +- The serial name (first parameter to `modbus_new_rtu`) should be a string containing + only the serial index (ie. `"1"` or `"2"`). + +- When using the TCP version be aware of the maximum number of sockets that can be + open on the platform: this is by default 10 and can be possibly raised to 16 in + a standard configuration. Please check the `LWIP_MAX_SOCKETS` configuration + variable. + diff --git a/src/esp-idf/component.mk b/src/esp-idf/component.mk new file mode 100644 index 00000000..a5c6f709 --- /dev/null +++ b/src/esp-idf/component.mk @@ -0,0 +1,18 @@ +INCLUDEDIRS := src +PRIV_INCLUDEDIRS := ../ +SRCDIRS := src + +COMPONENT_PRIV_INCLUDEDIRS = $(addprefix libmodbus/, \ + $(PRIV_INCLUDEDIRS) \ + ) + +COMPONENT_SRCDIRS = $(addprefix libmodbus/, \ + $(SRCDIRS) \ + ) + +COMPONENT_ADD_INCLUDEDIRS = $(addprefix libmodbus/, \ + $(INCLUDEDIRS) \ + ) + + + diff --git a/src/esp-idf/config.h b/src/esp-idf/config.h new file mode 100644 index 00000000..00abb7fb --- /dev/null +++ b/src/esp-idf/config.h @@ -0,0 +1,296 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +#define ESP_PLATFORM 1 + +#define O_NDELAY O_NONBLOCK + +/* Define to 1 if you have the `accept4' function. */ +// #define HAVE_ACCEPT4 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_ARPA_INET_H 1 + +/* Define to 1 if you have the header file. */ +// #define HAVE_BYTESWAP_H 1 + +/* Define to 1 if you have the declaration of `TIOCM_RTS', and to 0 if you + don't. */ +// #define HAVE_DECL_TIOCM_RTS 1 + +/* Define to 1 if you have the declaration of `TIOCSRS485', and to 0 if you + don't. */ +// #define HAVE_DECL_TIOCSRS485 1 + +/* Define to 1 if you have the declaration of `__CYGWIN__', and to 0 if you + don't. */ +#define HAVE_DECL___CYGWIN__ 0 + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_ERRNO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define to 1 if you have the `getaddrinfo' function. */ +#define HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `gettimeofday' function. */ +#define HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the `inet_ntop' function. */ +#define HAVE_INET_NTOP 1 + +/* Define to 1 if you have the `inet_pton' function. */ +#define HAVE_INET_PTON 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LIMITS_H 1 + +/* Define to 1 if you have the header file. */ +// #define HAVE_LINUX_SERIAL_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MINIX_CONFIG_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_NETDB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETINET_IN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETINET_TCP_H 1 + +/* Define to 1 if you have the `select' function. */ +#define HAVE_SELECT 1 + +/* Define to 1 if you have the `socket' function. */ +#define HAVE_SOCKET 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDIO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the `strerror' function. */ +#define HAVE_STRERROR 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcpy' function. */ +/* #undef HAVE_STRLCPY */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_PARAMS_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_TERMIOS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_WCHAR_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_WINSOCK2_H */ + +/* Define to the sub-directory where libtool stores uninstalled libraries. */ +#define LT_OBJDIR ".libs/" + +/* Name of package */ +#define PACKAGE "libmodbus" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "https://github.com/stephane/libmodbus/issues" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "libmodbus" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "libmodbus 3.1.10" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "libmodbus" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "http://libmodbus.org/" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "3.1.10" + +/* Define to 1 if all of the C90 standard headers exist (not just the ones + required in a freestanding environment). This macro is provided for + backward compatibility; new code need not use it. */ +#define STDC_HEADERS 1 + +/* Enable extensions on AIX 3, Interix. */ +#ifndef _ALL_SOURCE +# define _ALL_SOURCE 1 +#endif +/* Enable general extensions on macOS. */ +#ifndef _DARWIN_C_SOURCE +# define _DARWIN_C_SOURCE 1 +#endif +/* Enable general extensions on Solaris. */ +#ifndef __EXTENSIONS__ +# define __EXTENSIONS__ 1 +#endif +/* Enable GNU extensions on systems that have them. */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE 1 +#endif +/* Enable X/Open compliant socket functions that do not require linking + with -lxnet on HP-UX 11.11. */ +#ifndef _HPUX_ALT_XOPEN_SOCKET_API +# define _HPUX_ALT_XOPEN_SOCKET_API 1 +#endif +/* Identify the host operating system as Minix. + This macro does not affect the system headers' behavior. + A future release of Autoconf may stop defining this macro. */ +#ifndef _MINIX +/* # undef _MINIX */ +#endif +/* Enable general extensions on NetBSD. + Enable NetBSD compatibility extensions on Minix. */ +#ifndef _NETBSD_SOURCE +# define _NETBSD_SOURCE 1 +#endif +/* Enable OpenBSD compatibility extensions on NetBSD. + Oddly enough, this does nothing on OpenBSD. */ +#ifndef _OPENBSD_SOURCE +# define _OPENBSD_SOURCE 1 +#endif +/* Define to 1 if needed for POSIX-compatible behavior. */ +#ifndef _POSIX_SOURCE +/* # undef _POSIX_SOURCE */ +#endif +/* Define to 2 if needed for POSIX-compatible behavior. */ +#ifndef _POSIX_1_SOURCE +/* # undef _POSIX_1_SOURCE */ +#endif +/* Enable POSIX-compatible threading on Solaris. */ +#ifndef _POSIX_PTHREAD_SEMANTICS +# define _POSIX_PTHREAD_SEMANTICS 1 +#endif +/* Enable extensions specified by ISO/IEC TS 18661-5:2014. */ +#ifndef __STDC_WANT_IEC_60559_ATTRIBS_EXT__ +# define __STDC_WANT_IEC_60559_ATTRIBS_EXT__ 1 +#endif +/* Enable extensions specified by ISO/IEC TS 18661-1:2014. */ +#ifndef __STDC_WANT_IEC_60559_BFP_EXT__ +# define __STDC_WANT_IEC_60559_BFP_EXT__ 1 +#endif +/* Enable extensions specified by ISO/IEC TS 18661-2:2015. */ +#ifndef __STDC_WANT_IEC_60559_DFP_EXT__ +# define __STDC_WANT_IEC_60559_DFP_EXT__ 1 +#endif +/* Enable extensions specified by ISO/IEC TS 18661-4:2015. */ +#ifndef __STDC_WANT_IEC_60559_FUNCS_EXT__ +# define __STDC_WANT_IEC_60559_FUNCS_EXT__ 1 +#endif +/* Enable extensions specified by ISO/IEC TS 18661-3:2015. */ +#ifndef __STDC_WANT_IEC_60559_TYPES_EXT__ +# define __STDC_WANT_IEC_60559_TYPES_EXT__ 1 +#endif +/* Enable extensions specified by ISO/IEC TR 24731-2:2010. */ +#ifndef __STDC_WANT_LIB_EXT2__ +# define __STDC_WANT_LIB_EXT2__ 1 +#endif +/* Enable extensions specified by ISO/IEC 24747:2009. */ +#ifndef __STDC_WANT_MATH_SPEC_FUNCS__ +# define __STDC_WANT_MATH_SPEC_FUNCS__ 1 +#endif +/* Enable extensions on HP NonStop. */ +#ifndef _TANDEM_SOURCE +# define _TANDEM_SOURCE 1 +#endif +/* Enable X/Open extensions. Define to 500 only if necessary + to make mbstate_t available. */ +#ifndef _XOPEN_SOURCE +/* # undef _XOPEN_SOURCE */ +#endif + + +/* Version number of package */ +#define VERSION "3.1.10" + +/* _ */ +#define WINVER 0x0501 + +/* Number of bits in a file offset, on hosts where this is settable. */ +/* #undef _FILE_OFFSET_BITS */ + +/* Define for large files, on AIX-style hosts. */ +/* #undef _LARGE_FILES */ + +/* Define for Solaris 2.5.1 so the uint32_t typedef from , + , or is not used. If the typedef were allowed, the + #define below would cause a syntax error. */ +/* #undef _UINT32_T */ + +/* Define for Solaris 2.5.1 so the uint8_t typedef from , + , or is not used. If the typedef were allowed, the + #define below would cause a syntax error. */ +/* #undef _UINT8_T */ + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +/* #undef inline */ +#endif + +/* Define to the type of a signed integer type of width exactly 64 bits if + such a type exists and the standard includes do not define it. */ +/* #undef int64_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ + +/* Define to `int' if does not define. */ +/* #undef ssize_t */ + +/* Define to the type of an unsigned integer type of width exactly 16 bits if + such a type exists and the standard includes do not define it. */ +/* #undef uint16_t */ + +/* Define to the type of an unsigned integer type of width exactly 32 bits if + such a type exists and the standard includes do not define it. */ +/* #undef uint32_t */ + +/* Define to the type of an unsigned integer type of width exactly 8 bits if + such a type exists and the standard includes do not define it. */ +/* #undef uint8_t */ diff --git a/src/esp-idf/idf_component.yml b/src/esp-idf/idf_component.yml new file mode 100644 index 00000000..181228b2 --- /dev/null +++ b/src/esp-idf/idf_component.yml @@ -0,0 +1,13 @@ +dependencies: + idf: + version: '>=5.0' +description: A Modbus library for Linux, Mac OS, FreeBSD and Windows +files: + exclude: + - docs/**/* + - docs + - tests/**/* + - tests +url: https://github.com/stephane/libmodbus +version: 3.1.10 + diff --git a/src/modbus-rtu.c b/src/modbus-rtu.c index ebef9347..650220e5 100644 --- a/src/modbus-rtu.c +++ b/src/modbus-rtu.c @@ -26,6 +26,13 @@ #include #endif + +#if ESP_PLATFORM +#include "driver/uart.h" +#include "driver/uart_vfs.h" +#include "esp_log.h" +#endif + /* Table of CRC values for high-order byte */ static const uint8_t table_crc_hi[] = { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, @@ -501,6 +508,90 @@ static int _modbus_rtu_connect(modbus_t *ctx) return 0; } +#elif ESP_PLATFORM +// EspressIf SDK +static int _modbus_rtu_connect(modbus_t *ctx) +{ + modbus_rtu_t *ctx_rtu = ctx->backend_data; + char serial_name[16]; + uart_word_length_t data_bits; + uart_parity_t parity; + uart_stop_bits_t stop_bits; + + const uart_port_t uart_num = atoi(ctx_rtu->device); + + switch (ctx_rtu->data_bit) { + case 5: + data_bits = UART_DATA_5_BITS; + break; + case 6: + data_bits = UART_DATA_6_BITS; + break; + case 7: + data_bits = UART_DATA_7_BITS; + break; + case 8: + data_bits = UART_DATA_8_BITS; + break; + default: + ESP_LOGI("libmodbus", "Invalid data bits value"); + return -1; + } + + switch (ctx_rtu->parity) { + case 'N': + parity = UART_PARITY_DISABLE; + break; + case 'E': + parity = UART_PARITY_EVEN; + break; + case 'O': + parity = UART_PARITY_ODD; + break; + default: + ESP_LOGI("libmodbus", "Invalid parity value"); + return -1; + } + + switch (ctx_rtu->stop_bit) { + case 1: + stop_bits = UART_STOP_BITS_1; + break; + case 2: + stop_bits = UART_STOP_BITS_2; + break; + default: + ESP_LOGI("libmodbus", "Invalid stop-bits value"); + return -1; + } + + + uart_config_t uart_config = { + .baud_rate = ctx_rtu->baud, + .data_bits = data_bits, + .parity = parity, + .stop_bits = stop_bits, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, // UART_HW_FLOWCTRL_CTS_RTS, + .rx_flow_ctrl_thresh = 2, + .source_clk = UART_SCLK_DEFAULT, + }; + + // Configure UART parameters + ESP_ERROR_CHECK(uart_param_config(uart_num, &uart_config)); + + // Handle to access serial + snprintf(serial_name, 16, "/dev/uart/%s", ctx_rtu->device); + + if ((ctx->s = open(serial_name, O_RDWR)) == -1) { + ctx->s = -1; + return -1; + } + + uart_vfs_dev_use_driver(uart_num); + + return 0; +} + #else static speed_t _get_termios_speed(int baud, int debug) From ef8e7f828728355a1166af4bfb2b8a6bf262153f Mon Sep 17 00:00:00 2001 From: Federico Pellegrin Date: Tue, 19 Nov 2024 07:28:32 +0100 Subject: [PATCH 2/2] modbus-rtu: esp-idf port, make sure line endings are ignored on UART By default some line ending conversion may be done, so make sure to disable this on the UART used with libmodbus. --- src/esp-idf/README.md | 4 ++++ src/modbus-rtu.c | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/esp-idf/README.md b/src/esp-idf/README.md index 4f7f0d1d..2179a1da 100644 --- a/src/esp-idf/README.md +++ b/src/esp-idf/README.md @@ -64,3 +64,7 @@ and would require a heavy change in the library interface. a standard configuration. Please check the `LWIP_MAX_SOCKETS` configuration variable. +- Older versions (<=5.3.1) of ESP-IDF contain a buglet in the serial read code that + may cause some packet loss under certain circumstances (when line ending + chars are present), see also: https://github.com/espressif/esp-idf/issues/14155 + Please use a newer version of ESP-IDF if you see this behavior. diff --git a/src/modbus-rtu.c b/src/modbus-rtu.c index 650220e5..b059da94 100644 --- a/src/modbus-rtu.c +++ b/src/modbus-rtu.c @@ -588,6 +588,8 @@ static int _modbus_rtu_connect(modbus_t *ctx) } uart_vfs_dev_use_driver(uart_num); + uart_vfs_dev_port_set_rx_line_endings(uart_num, ESP_LINE_ENDINGS_LF); + uart_vfs_dev_port_set_tx_line_endings(uart_num, ESP_LINE_ENDINGS_LF); return 0; }