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..2179a1da --- /dev/null +++ b/src/esp-idf/README.md @@ -0,0 +1,70 @@ +# 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. + +- 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/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..b059da94 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,92 @@ 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); + 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; +} + #else static speed_t _get_termios_speed(int baud, int debug)