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:
Alex Lisitsyn
2018-06-28 17:45:41 +02:00
parent 5b7b64b946
commit f0eb9985b9
46 changed files with 5866 additions and 4 deletions
@@ -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)
+9
View File
@@ -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
+73
View File
@@ -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(&reg_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(&reg_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(&reg_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(&reg_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());
}