1 /*
2 * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 // FreeModbus Slave Example ESP32
8
9 #include <stdio.h>
10 #include "esp_err.h"
11 #include "sdkconfig.h"
12 #include "esp_log.h"
13 #include "esp_system.h"
14 #include "esp_wifi.h"
15 #include "esp_event.h"
16 #include "esp_log.h"
17 #include "nvs_flash.h"
18
19 #include "mdns.h"
20 #include "esp_netif.h"
21 #include "protocol_examples_common.h"
22
23 #include "mbcontroller.h" // for mbcontroller defines and api
24 #include "modbus_params.h" // for modbus parameters structures
25
26 #define MB_TCP_PORT_NUMBER (CONFIG_FMB_TCP_PORT_DEFAULT)
27 #define MB_MDNS_PORT (502)
28
29 // Defines below are used to define register start address for each type of Modbus registers
30 #define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) >> 1))
31 #define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) >> 1))
32 #define MB_REG_DISCRETE_INPUT_START (0x0000)
33 #define MB_REG_COILS_START (0x0000)
34 #define MB_REG_INPUT_START_AREA0 (INPUT_OFFSET(input_data0)) // register offset input area 0
35 #define MB_REG_INPUT_START_AREA1 (INPUT_OFFSET(input_data4)) // register offset input area 1
36 #define MB_REG_HOLDING_START_AREA0 (HOLD_OFFSET(holding_data0))
37 #define MB_REG_HOLDING_START_AREA1 (HOLD_OFFSET(holding_data4))
38
39 #define MB_PAR_INFO_GET_TOUT (10) // Timeout for get parameter info
40 #define MB_CHAN_DATA_MAX_VAL (10)
41 #define MB_CHAN_DATA_OFFSET (1.1f)
42
43 #define MB_READ_MASK (MB_EVENT_INPUT_REG_RD \
44 | MB_EVENT_HOLDING_REG_RD \
45 | MB_EVENT_DISCRETE_RD \
46 | MB_EVENT_COILS_RD)
47 #define MB_WRITE_MASK (MB_EVENT_HOLDING_REG_WR \
48 | MB_EVENT_COILS_WR)
49 #define MB_READ_WRITE_MASK (MB_READ_MASK | MB_WRITE_MASK)
50
51 static const char *TAG = "SLAVE_TEST";
52
53 static portMUX_TYPE param_lock = portMUX_INITIALIZER_UNLOCKED;
54
55 #if CONFIG_MB_MDNS_IP_RESOLVER
56
57 #define MB_ID_BYTE0(id) ((uint8_t)(id))
58 #define MB_ID_BYTE1(id) ((uint8_t)(((uint16_t)(id) >> 8) & 0xFF))
59 #define MB_ID_BYTE2(id) ((uint8_t)(((uint32_t)(id) >> 16) & 0xFF))
60 #define MB_ID_BYTE3(id) ((uint8_t)(((uint32_t)(id) >> 24) & 0xFF))
61
62 #define MB_ID2STR(id) MB_ID_BYTE0(id), MB_ID_BYTE1(id), MB_ID_BYTE2(id), MB_ID_BYTE3(id)
63
64 #if CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT
65 #define MB_DEVICE_ID (uint32_t)CONFIG_FMB_CONTROLLER_SLAVE_ID
66 #endif
67
68 #define MB_SLAVE_ADDR (CONFIG_MB_SLAVE_ADDR)
69
70 #define MB_MDNS_INSTANCE(pref) pref"mb_slave_tcp"
71
72 // convert mac from binary format to string
gen_mac_str(const uint8_t * mac,char * pref,char * mac_str)73 static inline char* gen_mac_str(const uint8_t* mac, char* pref, char* mac_str)
74 {
75 sprintf(mac_str, "%s%02X%02X%02X%02X%02X%02X", pref, MAC2STR(mac));
76 return mac_str;
77 }
78
gen_id_str(char * service_name,char * slave_id_str)79 static inline char* gen_id_str(char* service_name, char* slave_id_str)
80 {
81 sprintf(slave_id_str, "%s%02X%02X%02X%02X", service_name, MB_ID2STR(MB_DEVICE_ID));
82 return slave_id_str;
83 }
84
gen_host_name_str(char * service_name,char * name)85 static inline char* gen_host_name_str(char* service_name, char* name)
86 {
87 sprintf(name, "%s_%02X", service_name, MB_SLAVE_ADDR);
88 return name;
89 }
90
start_mdns_service(void)91 static void start_mdns_service(void)
92 {
93 char temp_str[32] = {0};
94 uint8_t sta_mac[6] = {0};
95 ESP_ERROR_CHECK(esp_read_mac(sta_mac, ESP_MAC_WIFI_STA));
96 char* hostname = gen_host_name_str(MB_MDNS_INSTANCE(""), temp_str);
97 //initialize mDNS
98 ESP_ERROR_CHECK(mdns_init());
99 //set mDNS hostname (required if you want to advertise services)
100 ESP_ERROR_CHECK(mdns_hostname_set(hostname));
101 ESP_LOGI(TAG, "mdns hostname set to: [%s]", hostname);
102
103 //set default mDNS instance name
104 ESP_ERROR_CHECK(mdns_instance_name_set(MB_MDNS_INSTANCE("esp32_")));
105
106 //structure with TXT records
107 mdns_txt_item_t serviceTxtData[] = {
108 {"board","esp32"}
109 };
110
111 //initialize service
112 ESP_ERROR_CHECK(mdns_service_add(hostname, "_modbus", "_tcp", MB_MDNS_PORT, serviceTxtData, 1));
113 //add mac key string text item
114 ESP_ERROR_CHECK(mdns_service_txt_item_set("_modbus", "_tcp", "mac", gen_mac_str(sta_mac, "\0", temp_str)));
115 //add slave id key txt item
116 ESP_ERROR_CHECK( mdns_service_txt_item_set("_modbus", "_tcp", "mb_id", gen_id_str("\0", temp_str)));
117 }
118
stop_mdns_service(void)119 static void stop_mdns_service(void)
120 {
121 mdns_free();
122 }
123
124 #endif
125
126 // Set register values into known state
setup_reg_data(void)127 static void setup_reg_data(void)
128 {
129 // Define initial state of parameters
130 discrete_reg_params.discrete_input0 = 1;
131 discrete_reg_params.discrete_input1 = 0;
132 discrete_reg_params.discrete_input2 = 1;
133 discrete_reg_params.discrete_input3 = 0;
134 discrete_reg_params.discrete_input4 = 1;
135 discrete_reg_params.discrete_input5 = 0;
136 discrete_reg_params.discrete_input6 = 1;
137 discrete_reg_params.discrete_input7 = 0;
138
139 holding_reg_params.holding_data0 = 1.34;
140 holding_reg_params.holding_data1 = 2.56;
141 holding_reg_params.holding_data2 = 3.78;
142 holding_reg_params.holding_data3 = 4.90;
143
144 holding_reg_params.holding_data4 = 5.67;
145 holding_reg_params.holding_data5 = 6.78;
146 holding_reg_params.holding_data6 = 7.79;
147 holding_reg_params.holding_data7 = 8.80;
148 coil_reg_params.coils_port0 = 0x55;
149 coil_reg_params.coils_port1 = 0xAA;
150
151 input_reg_params.input_data0 = 1.12;
152 input_reg_params.input_data1 = 2.34;
153 input_reg_params.input_data2 = 3.56;
154 input_reg_params.input_data3 = 4.78;
155 input_reg_params.input_data4 = 1.12;
156 input_reg_params.input_data5 = 2.34;
157 input_reg_params.input_data6 = 3.56;
158 input_reg_params.input_data7 = 4.78;
159 }
160
slave_operation_func(void * arg)161 static void slave_operation_func(void *arg)
162 {
163 mb_param_info_t reg_info; // keeps the Modbus registers access information
164
165 ESP_LOGI(TAG, "Modbus slave stack initialized.");
166 ESP_LOGI(TAG, "Start modbus test...");
167 // The cycle below will be terminated when parameter holding_data0
168 // incremented each access cycle reaches the CHAN_DATA_MAX_VAL value.
169 for(;holding_reg_params.holding_data0 < MB_CHAN_DATA_MAX_VAL;) {
170 // Check for read/write events of Modbus master for certain events
171 mb_event_group_t event = mbc_slave_check_event(MB_READ_WRITE_MASK);
172 const char* rw_str = (event & MB_READ_MASK) ? "READ" : "WRITE";
173 // Filter events and process them accordingly
174 if(event & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
175 // Get parameter information from parameter queue
176 ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
177 ESP_LOGI(TAG, "HOLDING %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
178 rw_str,
179 (uint32_t)reg_info.time_stamp,
180 (uint32_t)reg_info.mb_offset,
181 (uint32_t)reg_info.type,
182 (uint32_t)reg_info.address,
183 (uint32_t)reg_info.size);
184 if (reg_info.address == (uint8_t*)&holding_reg_params.holding_data0)
185 {
186 portENTER_CRITICAL(¶m_lock);
187 holding_reg_params.holding_data0 += MB_CHAN_DATA_OFFSET;
188 if (holding_reg_params.holding_data0 >= (MB_CHAN_DATA_MAX_VAL - MB_CHAN_DATA_OFFSET)) {
189 coil_reg_params.coils_port1 = 0xFF;
190 }
191 portEXIT_CRITICAL(¶m_lock);
192 }
193 } else if (event & MB_EVENT_INPUT_REG_RD) {
194 ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
195 ESP_LOGI(TAG, "INPUT READ (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
196 (uint32_t)reg_info.time_stamp,
197 (uint32_t)reg_info.mb_offset,
198 (uint32_t)reg_info.type,
199 (uint32_t)reg_info.address,
200 (uint32_t)reg_info.size);
201 } else if (event & MB_EVENT_DISCRETE_RD) {
202 ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
203 ESP_LOGI(TAG, "DISCRETE READ (%u us): ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
204 (uint32_t)reg_info.time_stamp,
205 (uint32_t)reg_info.mb_offset,
206 (uint32_t)reg_info.type,
207 (uint32_t)reg_info.address,
208 (uint32_t)reg_info.size);
209 } else if (event & (MB_EVENT_COILS_RD | MB_EVENT_COILS_WR)) {
210 ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
211 ESP_LOGI(TAG, "COILS %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
212 rw_str,
213 (uint32_t)reg_info.time_stamp,
214 (uint32_t)reg_info.mb_offset,
215 (uint32_t)reg_info.type,
216 (uint32_t)reg_info.address,
217 (uint32_t)reg_info.size);
218 if (coil_reg_params.coils_port1 == 0xFF) break;
219 }
220 }
221 // Destroy of Modbus controller on alarm
222 ESP_LOGI(TAG,"Modbus controller destroyed.");
223 vTaskDelay(100);
224 }
225
init_services(void)226 static esp_err_t init_services(void)
227 {
228 esp_err_t result = nvs_flash_init();
229 if (result == ESP_ERR_NVS_NO_FREE_PAGES || result == ESP_ERR_NVS_NEW_VERSION_FOUND) {
230 ESP_ERROR_CHECK(nvs_flash_erase());
231 result = nvs_flash_init();
232 }
233 MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
234 TAG,
235 "nvs_flash_init fail, returns(0x%x).",
236 (uint32_t)result);
237 result = esp_netif_init();
238 MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
239 TAG,
240 "esp_netif_init fail, returns(0x%x).",
241 (uint32_t)result);
242 result = esp_event_loop_create_default();
243 MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
244 TAG,
245 "esp_event_loop_create_default fail, returns(0x%x).",
246 (uint32_t)result);
247 #if CONFIG_MB_MDNS_IP_RESOLVER
248 // Start mdns service and register device
249 start_mdns_service();
250 #endif
251 // This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
252 // Read "Establishing Wi-Fi or Ethernet Connection" section in
253 // examples/protocols/README.md for more information about this function.
254 result = example_connect();
255 MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
256 TAG,
257 "example_connect fail, returns(0x%x).",
258 (uint32_t)result);
259 #if CONFIG_EXAMPLE_CONNECT_WIFI
260 result = esp_wifi_set_ps(WIFI_PS_NONE);
261 MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
262 TAG,
263 "esp_wifi_set_ps fail, returns(0x%x).",
264 (uint32_t)result);
265 #endif
266 return ESP_OK;
267 }
268
destroy_services(void)269 static esp_err_t destroy_services(void)
270 {
271 esp_err_t err = ESP_OK;
272
273 err = example_disconnect();
274 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
275 TAG,
276 "example_disconnect fail, returns(0x%x).",
277 (uint32_t)err);
278 err = esp_event_loop_delete_default();
279 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
280 TAG,
281 "esp_event_loop_delete_default fail, returns(0x%x).",
282 (uint32_t)err);
283 err = esp_netif_deinit();
284 MB_RETURN_ON_FALSE((err == ESP_OK || err == ESP_ERR_NOT_SUPPORTED), ESP_ERR_INVALID_STATE,
285 TAG,
286 "esp_netif_deinit fail, returns(0x%x).",
287 (uint32_t)err);
288 err = nvs_flash_deinit();
289 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
290 TAG,
291 "nvs_flash_deinit fail, returns(0x%x).",
292 (uint32_t)err);
293 #if CONFIG_MB_MDNS_IP_RESOLVER
294 stop_mdns_service();
295 #endif
296 return err;
297 }
298
299 // Modbus slave initialization
slave_init(mb_communication_info_t * comm_info)300 static esp_err_t slave_init(mb_communication_info_t* comm_info)
301 {
302 mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure
303
304 void* slave_handler = NULL;
305
306 // Initialization of Modbus controller
307 esp_err_t err = mbc_slave_init_tcp(&slave_handler);
308 MB_RETURN_ON_FALSE((err == ESP_OK && slave_handler != NULL), ESP_ERR_INVALID_STATE,
309 TAG,
310 "mb controller initialization fail.");
311
312 comm_info->ip_addr = NULL; // Bind to any address
313 comm_info->ip_netif_ptr = (void*)get_example_netif();
314
315 // Setup communication parameters and start stack
316 err = mbc_slave_setup((void*)comm_info);
317 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
318 TAG,
319 "mbc_slave_setup fail, returns(0x%x).",
320 (uint32_t)err);
321
322 // The code below initializes Modbus register area descriptors
323 // for Modbus Holding Registers, Input Registers, Coils and Discrete Inputs
324 // Initialization should be done for each supported Modbus register area according to register map.
325 // When external master trying to access the register in the area that is not initialized
326 // by mbc_slave_set_descriptor() API call then Modbus stack
327 // will send exception response for this register area.
328 reg_area.type = MB_PARAM_HOLDING; // Set type of register area
329 reg_area.start_offset = MB_REG_HOLDING_START_AREA0; // Offset of register area in Modbus protocol
330 reg_area.address = (void*)&holding_reg_params.holding_data0; // Set pointer to storage instance
331 reg_area.size = sizeof(float) << 2; // Set the size of register storage instance
332 err = mbc_slave_set_descriptor(reg_area);
333 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
334 TAG,
335 "mbc_slave_set_descriptor fail, returns(0x%x).",
336 (uint32_t)err);
337
338 reg_area.type = MB_PARAM_HOLDING; // Set type of register area
339 reg_area.start_offset = MB_REG_HOLDING_START_AREA1; // Offset of register area in Modbus protocol
340 reg_area.address = (void*)&holding_reg_params.holding_data4; // Set pointer to storage instance
341 reg_area.size = sizeof(float) << 2; // Set the size of register storage instance
342 err = mbc_slave_set_descriptor(reg_area);
343 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
344 TAG,
345 "mbc_slave_set_descriptor fail, returns(0x%x).",
346 (uint32_t)err);
347
348 // Initialization of Input Registers area
349 reg_area.type = MB_PARAM_INPUT;
350 reg_area.start_offset = MB_REG_INPUT_START_AREA0;
351 reg_area.address = (void*)&input_reg_params.input_data0;
352 reg_area.size = sizeof(float) << 2;
353 err = mbc_slave_set_descriptor(reg_area);
354 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
355 TAG,
356 "mbc_slave_set_descriptor fail, returns(0x%x).",
357 (uint32_t)err);
358 reg_area.type = MB_PARAM_INPUT;
359 reg_area.start_offset = MB_REG_INPUT_START_AREA1;
360 reg_area.address = (void*)&input_reg_params.input_data4;
361 reg_area.size = sizeof(float) << 2;
362 err = mbc_slave_set_descriptor(reg_area);
363 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
364 TAG,
365 "mbc_slave_set_descriptor fail, returns(0x%x).",
366 (uint32_t)err);
367
368 // Initialization of Coils register area
369 reg_area.type = MB_PARAM_COIL;
370 reg_area.start_offset = MB_REG_COILS_START;
371 reg_area.address = (void*)&coil_reg_params;
372 reg_area.size = sizeof(coil_reg_params);
373 err = mbc_slave_set_descriptor(reg_area);
374 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
375 TAG,
376 "mbc_slave_set_descriptor fail, returns(0x%x).",
377 (uint32_t)err);
378
379 // Initialization of Discrete Inputs register area
380 reg_area.type = MB_PARAM_DISCRETE;
381 reg_area.start_offset = MB_REG_DISCRETE_INPUT_START;
382 reg_area.address = (void*)&discrete_reg_params;
383 reg_area.size = sizeof(discrete_reg_params);
384 err = mbc_slave_set_descriptor(reg_area);
385 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
386 TAG,
387 "mbc_slave_set_descriptor fail, returns(0x%x).",
388 (uint32_t)err);
389
390 // Set values into known state
391 setup_reg_data();
392
393 // Starts of modbus controller and stack
394 err = mbc_slave_start();
395 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
396 TAG,
397 "mbc_slave_start fail, returns(0x%x).",
398 (uint32_t)err);
399 vTaskDelay(5);
400 return err;
401 }
402
slave_destroy(void)403 static esp_err_t slave_destroy(void)
404 {
405 esp_err_t err = mbc_slave_destroy();
406 MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
407 TAG,
408 "mbc_slave_destroy fail, returns(0x%x).",
409 (uint32_t)err);
410 return err;
411 }
412
413 // An example application of Modbus slave. It is based on freemodbus stack.
414 // See deviceparams.h file for more information about assigned Modbus parameters.
415 // These parameters can be accessed from main application and also can be changed
416 // by external Modbus master host.
app_main(void)417 void app_main(void)
418 {
419 ESP_ERROR_CHECK(init_services());
420
421 // Set UART log level
422 esp_log_level_set(TAG, ESP_LOG_INFO);
423
424 mb_communication_info_t comm_info = { 0 };
425
426 #if !CONFIG_EXAMPLE_CONNECT_IPV6
427 comm_info.ip_addr_type = MB_IPV4;
428 #else
429 comm_info.ip_addr_type = MB_IPV6;
430 #endif
431 comm_info.ip_mode = MB_MODE_TCP;
432
433 comm_info.ip_port = MB_TCP_PORT_NUMBER;
434 ESP_ERROR_CHECK(slave_init(&comm_info));
435
436 // The Modbus slave logic is located in this function (user handling of Modbus)
437 slave_operation_func(NULL);
438
439 ESP_ERROR_CHECK(slave_destroy());
440 ESP_ERROR_CHECK(destroy_services());
441 }
442