examples: freemodbus port slave basic implementation
This example adds functionality to support basic communication in RS485 networks using Modbus protocol. This example uses FreeModbus stack and regular UART driver API to communicate in RS485 half duplex mode. Added initial support of modbus controller pure C api to access device parameters over Modbus transport. Move freemodbus stack and port files into components folder Move the modbus_controller interface into components idf folder Source files updated after review. Add modbus interface documentation docs/en/api-reference/protocols/modbus.rst porttimer.c: fix bug with timer1 selected in the Kconfig Add support of cmake system (added cmake files) Closes https://github.com/espressif/esp-idf/issues/858
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# The following five lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(modbus_slave)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := modbus_slave
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# Modbus Slave Example
|
||||
|
||||
This example demonstrates using of FreeModbus stack port implementation for ESP32. The external Modbus host is able to read/write device parameters using Modbus protocol transport. The parameters accessible thorough Modbus are located in deviceparams.h/c files and can be updated by user.
|
||||
These are represented in structures holding_reg_params, input_reg_params, coil_reg_params, discrete_reg_params for holding registers, input parameters, coils and discrete inputs accordingly. The app_main application demonstrates how to setup Modbus stack and use notifications about parameters change from host system.
|
||||
The FreeModbus stack located in components\freemodbus\ folder and contain \port folder inside which contains FreeModbus stack port for ESP32. There are some parameters that can be configured in KConfig file to start stack correctly (See description below for more information).
|
||||
|
||||
## Hardware required :
|
||||
PC + USB Serial adapter connected to USB port + RS485 line drivers + ESP32 WROVER-KIT board.
|
||||
The MAX485 line driver is used as an example below but other similar chips can be used as well.
|
||||
|
||||
RS485 example circuit schematic:
|
||||
```
|
||||
VCC ---------------+ +--------------- VCC
|
||||
| |
|
||||
+-------x-------+ +-------x-------+
|
||||
RXD <------| RO | DIFFERENTIAL | RO|-----> RXD
|
||||
| B|---------------|B |
|
||||
TXD ------>| DI MAX485 | \ / | MAX485 DI|<----- TXD
|
||||
ESP32 WROVER KIT 1 | | RS-485 side | | Modbus master
|
||||
RTS --+--->| DE | / \ | DE|---+
|
||||
| | A|---------------|A | |
|
||||
+----| /RE | PAIR | /RE|---+-- RTS
|
||||
+-------x--------+ +-------x-------+
|
||||
| |
|
||||
--- ---
|
||||
```
|
||||
|
||||
## How to setup and use an example:
|
||||
|
||||
### Configure the application
|
||||
Configure the UART pins used for modbus communication using command and table below.
|
||||
```
|
||||
make menuconfig
|
||||
```
|
||||
|
||||
```
|
||||
-----------------------------------------------------------------------------------
|
||||
| ESP32 Interface | #define | Default ESP32 Pin | External RS485 |
|
||||
| ----------------------|--------------------|-------------------| Driver Pin |
|
||||
| Transmit Data (TxD) | CONFIG_MB_UART_TXD | GPIO23 | DI |
|
||||
| Receive Data (RxD) | CONFIG_MB_UART_RXD | GPIO22 | RO |
|
||||
| Request To Send (RTS) | CONFIG_MB_UART_RTS | GPIO18 | ~RE/DE |
|
||||
| Ground | n/a | GND | GND |
|
||||
-----------------------------------------------------------------------------------
|
||||
```
|
||||
The communication parameters below allow to configure freemodbus stack appropriately but usually it is enough to use default settings.
|
||||
See the help string of parameters for more information.
|
||||
|
||||
### Setup external Modbus master software
|
||||
Configure the external Modbus master software according to port configuration parameters used in application.
|
||||
As an example the Modbus Poll application can be used with this example.
|
||||
|
||||
### Build and flash software
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
```
|
||||
make -j4 flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
Example output of the application:
|
||||
```
|
||||
INPUT READ: time_stamp(us):565240387, mb_addr:1, type:8, st_address:0x3ffb385c, size:8
|
||||
|
||||
HOLDING READ/WRITE: time_stamp(us):12104081, mb_addr:1, type:2, st_address:0x3ffb386c, size:8
|
||||
```
|
||||
The output lines describe type of operation, its timestamp, modbus address, access type, storage address in parameter structure and number of registers accordingly.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
set(COMPONENT_SRCS "freemodbus.c"
|
||||
"deviceparams.c")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||
|
||||
register_component()
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,17 @@
|
||||
/*=====================================================================================
|
||||
* Description:
|
||||
* C file to define parameter storage instances
|
||||
*====================================================================================*/
|
||||
#include <stdint.h>
|
||||
#include "deviceparams.h"
|
||||
|
||||
// Here are the user defined instances for device parameters packed by 1 byte
|
||||
// These are keep the values that can be accessed from Modbus master
|
||||
holding_reg_params_t holding_reg_params = { 0 };
|
||||
|
||||
input_reg_params_t input_reg_params = { 0 };
|
||||
|
||||
coil_reg_params_t coil_reg_params = { 0 };
|
||||
|
||||
discrete_reg_params_t discrete_reg_params = { 0 };
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*=====================================================================================
|
||||
* Description:
|
||||
* The Modbus parameter structures used to define Modbus instances that
|
||||
* can be addressed by Modbus protocol. Define these structures per your needs in
|
||||
* your application. Below is just an example of possible parameters.
|
||||
*====================================================================================*/
|
||||
#ifndef _DEVICE_PARAMS
|
||||
#define _DEVICE_PARAMS
|
||||
|
||||
#define A24_ARR_SIZE 24
|
||||
|
||||
// This file defines structure of modbus parameters which reflect correspond modbus address space
|
||||
// for each modbus register type (coils, discreet inputs, holding registers, input registers)
|
||||
#pragma pack(push, 1)
|
||||
typedef struct
|
||||
{
|
||||
// Parameter: discrete_input0
|
||||
uint8_t discrete_input0:1;
|
||||
// Parameter: discrete_input1
|
||||
uint8_t discrete_input1:1;
|
||||
// Parameter: discrete_input2
|
||||
uint8_t discrete_input2:1;
|
||||
// Parameter: discrete_input3
|
||||
uint8_t discrete_input3:1;
|
||||
// Parameter: discrete_input4
|
||||
uint8_t discrete_input4:1;
|
||||
// Parameter: discrete_input5
|
||||
uint8_t discrete_input5:1;
|
||||
// Parameter: discrete_input6
|
||||
uint8_t discrete_input6:1;
|
||||
// Parameter: discrete_input7
|
||||
uint8_t discrete_input7:1;
|
||||
uint8_t discrete_input_port1:8;
|
||||
} discrete_reg_params_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct
|
||||
{
|
||||
// Parameter: Coil 0 : Coil0
|
||||
uint8_t coil0:1;
|
||||
// Parameter: Coil 1 : Coil1
|
||||
uint8_t coil1:1;
|
||||
// Parameter: Coil 2 : Coil2
|
||||
uint8_t coil2:1;
|
||||
// Parameter: Coil 3 : Coil3
|
||||
uint8_t coil3:1;
|
||||
// Parameter: Coil 4 : Coil4
|
||||
uint8_t coil4:1;
|
||||
// Parameter: Coil 5 : Coil5
|
||||
uint8_t coil5:1;
|
||||
// Parameter: Coil 6 : Coil6
|
||||
uint8_t coil6:1;
|
||||
// Parameter: Coil 7 : Coil7
|
||||
uint8_t coil7:1;
|
||||
// Coils port 1
|
||||
uint8_t coil_port1:8;
|
||||
} coil_reg_params_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct
|
||||
{
|
||||
// Parameter: Data channel 0 : data_chan0 : NV Address: 0
|
||||
float data_chan0;
|
||||
// Parameter: Data channel 1 : data_chan1 : NV Address: 0
|
||||
float data_chan1;
|
||||
// Parameter: Data channel 2 : data_chan2 : NV Address: 0
|
||||
float data_chan2;
|
||||
// Parameter: Data channel 3 : data_chan3 : NV Address: 0
|
||||
float data_chan3;
|
||||
} input_reg_params_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
//See register map for more information.
|
||||
#pragma pack(push, 1)
|
||||
typedef struct
|
||||
{
|
||||
// Parameter: Data channel 0 : DataChan0
|
||||
float data_chan0;
|
||||
// Parameter: Data channel 1 : DataChan1
|
||||
float data_chan1;
|
||||
// Parameter: Data channel 2 : DataChan2
|
||||
float data_chan2;
|
||||
// Parameter: Data channel 3 : DataChan3
|
||||
float data_chan3;
|
||||
// Parameter: Protocol version : protocol_version
|
||||
uint16_t protocol_version;
|
||||
// Parameter: Hardware version : hardware_version
|
||||
uint16_t hardware_version;
|
||||
// Parameter: Software Version : software_version
|
||||
uint16_t software_version;
|
||||
// Parameter: Software Revision : software_revision
|
||||
uint16_t software_revision;
|
||||
// Parameter: Device Type : deviceType :
|
||||
uint16_t deviceType;
|
||||
// Parameter: Modbus Network Address : modbus_address
|
||||
uint16_t modbus_address;
|
||||
// Parameter: Modbus Baudrate : modbus_baud
|
||||
uint16_t modbus_baud;
|
||||
// Parameter: Modbus parity : modbus_parity
|
||||
uint16_t modbus_parity;
|
||||
// Parameter: Modbus stopbit : modbus_stop_bits
|
||||
uint16_t modbus_stop_bits;
|
||||
// Parameter: Brace control : modbus_brace_ctrl
|
||||
uint16_t modbus_brace_ctrl;
|
||||
// Parameter: Serial number : serial_number
|
||||
uint32_t serial_number;
|
||||
// Parameter: Up time : up_time
|
||||
uint32_t up_time;
|
||||
// Parameter: Device state : device_state
|
||||
uint16_t device_state;
|
||||
// Parameter: Test Float0 : test_float0
|
||||
float test_float0;
|
||||
// Parameter: Test Float1 : test_float1
|
||||
float test_float1;
|
||||
// Parameter: Test Float2 : test_float2
|
||||
float test_float2;
|
||||
// Parameter: Test Float3 : test_float3
|
||||
float test_float3;
|
||||
// Parameter: Test String : string_test
|
||||
uint8_t string_test[A24_ARR_SIZE];
|
||||
} holding_reg_params_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
extern holding_reg_params_t holding_reg_params;
|
||||
extern input_reg_params_t input_reg_params;
|
||||
extern coil_reg_params_t coil_reg_params;
|
||||
extern discrete_reg_params_t discrete_reg_params;
|
||||
|
||||
#endif // !defined(_DEVICE_PARAMS)
|
||||
@@ -0,0 +1,168 @@
|
||||
/* FreeModbus Slave Example ESP32
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "esp_err.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "mbcontroller.h" // for mbcontroller defines and api
|
||||
#include "deviceparams.h" // for device parameters structures
|
||||
#include "esp_log.h" // for log_write
|
||||
|
||||
#define MB_PORT_NUM (2) // Number of UART port used for Modbus connection
|
||||
#define MB_DEV_ADDR (1) // The address of device in Modbus network
|
||||
#define MB_DEV_SPEED (115200) // The communication speed of the UART
|
||||
|
||||
// Defines below are used to define register start address for each type of Modbus registers
|
||||
#define MB_REG_DISCRETE_INPUT_START (0x0000)
|
||||
#define MB_REG_INPUT_START (0x0000)
|
||||
#define MB_REG_HOLDING_START (0x0000)
|
||||
#define MB_REG_COILS_START (0x0000)
|
||||
|
||||
#define MB_PAR_INFO_GET_TOUT (10) // Timeout for get parameter info
|
||||
#define MB_CHAN_DATA_MAX_VAL (10)
|
||||
#define MB_CHAN_DATA_OFFSET (0.01f)
|
||||
|
||||
static const char *TAG = "MODBUS_SLAVE_APP";
|
||||
|
||||
// Set register values into known state
|
||||
static void setup_reg_data()
|
||||
{
|
||||
// Define initial state of parameters
|
||||
discrete_reg_params.discrete_input1 = 1;
|
||||
discrete_reg_params.discrete_input3 = 1;
|
||||
discrete_reg_params.discrete_input5 = 1;
|
||||
discrete_reg_params.discrete_input7 = 1;
|
||||
|
||||
holding_reg_params.data_chan0 = 1.34;
|
||||
holding_reg_params.data_chan1 = 2.56;
|
||||
holding_reg_params.data_chan2 = 3.78;
|
||||
holding_reg_params.data_chan3 = 4.90;
|
||||
|
||||
coil_reg_params.coil0 = 1;
|
||||
coil_reg_params.coil2 = 1;
|
||||
coil_reg_params.coil4 = 1;
|
||||
coil_reg_params.coil6 = 1;
|
||||
coil_reg_params.coil7 = 1;
|
||||
|
||||
input_reg_params.data_chan0 = 1.34;
|
||||
input_reg_params.data_chan1 = 2.56;
|
||||
input_reg_params.data_chan2 = 3.78;
|
||||
input_reg_params.data_chan3 = 4.90;
|
||||
}
|
||||
|
||||
// An example application of Modbus slave. It is based on freemodbus stack.
|
||||
// See deviceparams.h file for more information about assigned Modbus parameters.
|
||||
// These parameters can be accessed from main application and also can be changed
|
||||
// by external Modbus master host.
|
||||
void app_main()
|
||||
{
|
||||
mb_param_info_t reg_info; // keeps the Modbus registers access information
|
||||
mb_communication_info_t comm_info; // Modbus communication parameters
|
||||
mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure
|
||||
|
||||
// Set UART log level
|
||||
esp_log_level_set(TAG, ESP_LOG_INFO);
|
||||
|
||||
mbcontroller_init(); // Initialization of Modbus controller
|
||||
|
||||
// Setup communication parameters and start stack
|
||||
comm_info.mode = MB_MODE_RTU;
|
||||
comm_info.slave_addr = MB_DEV_ADDR;
|
||||
comm_info.port = MB_PORT_NUM;
|
||||
comm_info.baudrate = MB_DEV_SPEED;
|
||||
comm_info.parity = MB_PARITY_NONE;
|
||||
ESP_ERROR_CHECK(mbcontroller_setup(comm_info));
|
||||
|
||||
// The code below initializes Modbus register area descriptors
|
||||
// for Modbus Holding Registers, Input Registers, Coils and Discrete Inputs
|
||||
// Initialization should be done for each supported Modbus register area according to register map.
|
||||
// When external master trying to access the register in the area that is not initialized
|
||||
// by mbcontroller_set_descriptor() API call then Modbus stack
|
||||
// will send exception response for this register area.
|
||||
reg_area.type = MB_PARAM_HOLDING; // Set type of register area
|
||||
reg_area.start_offset = MB_REG_HOLDING_START; // Offset of register area in Modbus protocol
|
||||
reg_area.address = (void*)&holding_reg_params; // Set pointer to storage instance
|
||||
reg_area.size = sizeof(holding_reg_params); // Set the size of register storage instance
|
||||
ESP_ERROR_CHECK(mbcontroller_set_descriptor(reg_area));
|
||||
|
||||
// Initialization of Input Registers area
|
||||
reg_area.type = MB_PARAM_INPUT;
|
||||
reg_area.start_offset = MB_REG_INPUT_START;
|
||||
reg_area.address = (void*)&input_reg_params;
|
||||
reg_area.size = sizeof(input_reg_params);
|
||||
ESP_ERROR_CHECK(mbcontroller_set_descriptor(reg_area));
|
||||
|
||||
// Initialization of Coils register area
|
||||
reg_area.type = MB_PARAM_COIL;
|
||||
reg_area.start_offset = MB_REG_COILS_START;
|
||||
reg_area.address = (void*)&coil_reg_params;
|
||||
reg_area.size = sizeof(coil_reg_params);
|
||||
ESP_ERROR_CHECK(mbcontroller_set_descriptor(reg_area));
|
||||
|
||||
// Initialization of Discrete Inputs register area
|
||||
reg_area.type = MB_PARAM_DISCRETE;
|
||||
reg_area.start_offset = MB_REG_DISCRETE_INPUT_START;
|
||||
reg_area.address = (void*)&discrete_reg_params;
|
||||
reg_area.size = sizeof(discrete_reg_params);
|
||||
ESP_ERROR_CHECK(mbcontroller_set_descriptor(reg_area));
|
||||
|
||||
setup_reg_data(); // Set values into known state
|
||||
|
||||
// Starts of modbus controller and stack
|
||||
ESP_ERROR_CHECK(mbcontroller_start());
|
||||
|
||||
// The cycle below will be terminated when parameter holdingRegParams.dataChan0
|
||||
// incremented each access cycle reaches the CHAN_DATA_MAX_VAL value.
|
||||
for(;holding_reg_params.data_chan0 < MB_CHAN_DATA_MAX_VAL;){
|
||||
// Check for read/write events of Modbus master for certain events
|
||||
mb_event_group_t event = mbcontroller_check_event((MB_EVENT_HOLDING_REG_WR
|
||||
| MB_EVENT_INPUT_REG_RD
|
||||
| MB_EVENT_HOLDING_REG_RD
|
||||
| MB_EVENT_DISCRETE_RD));
|
||||
// Filter events and process them accordingly
|
||||
if((event & MB_EVENT_HOLDING_REG_WR) || (event & MB_EVENT_HOLDING_REG_RD)) {
|
||||
// Get parameter information from parameter queue
|
||||
ESP_ERROR_CHECK(mbcontroller_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
|
||||
printf("HOLDING READ/WRITE: time_stamp(us):%u, mb_addr:%u, type:%u, st_address:0x%.4x, size:%u\r\n",
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
if (reg_info.address == (uint8_t*)&holding_reg_params.data_chan0)
|
||||
{
|
||||
holding_reg_params.data_chan0 += MB_CHAN_DATA_OFFSET;
|
||||
}
|
||||
} else if (event & MB_EVENT_INPUT_REG_RD) {
|
||||
ESP_ERROR_CHECK(mbcontroller_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
|
||||
printf("INPUT READ: time_stamp(us):%u, mb_addr:%u, type:%u, st_address:0x%.4x, size:%u\r\n",
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
} else if (event & MB_EVENT_DISCRETE_RD) {
|
||||
ESP_ERROR_CHECK(mbcontroller_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
|
||||
printf("DISCRETE READ: time_stamp(us):%u, mb_addr:%u, type:%u, st_address:0x%.4x, size:%u\r\n",
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
} else if (event & MB_EVENT_COILS_RD) {
|
||||
ESP_ERROR_CHECK(mbcontroller_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
|
||||
printf("COILS READ: time_stamp(us):%u, mb_addr:%u, type:%u, st_address:0x%.4x, size:%u\r\n",
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
}
|
||||
}
|
||||
// Destroy of Modbus controller once get maximum value of data_chan0
|
||||
printf("Modbus controller destroyed.");
|
||||
ESP_ERROR_CHECK(mbcontroller_destroy());
|
||||
}
|
||||
Reference in New Issue
Block a user