master.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. /*
  2. * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD
  3. *
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. #include "string.h"
  7. #include "esp_log.h"
  8. #include "modbus_params.h" // for modbus parameters structures
  9. #include "mbcontroller.h"
  10. #include "sdkconfig.h"
  11. #define MB_PORT_NUM (CONFIG_MB_UART_PORT_NUM) // Number of UART port used for Modbus connection
  12. #define MB_DEV_SPEED (CONFIG_MB_UART_BAUD_RATE) // The communication speed of the UART
  13. // Note: Some pins on target chip cannot be assigned for UART communication.
  14. // See UART documentation for selected board and target to configure pins using Kconfig.
  15. // The number of parameters that intended to be used in the particular control process
  16. #define MASTER_MAX_CIDS num_device_parameters
  17. // Number of reading of parameters from slave
  18. #define MASTER_MAX_RETRY 30
  19. // Timeout to update cid over Modbus
  20. #define UPDATE_CIDS_TIMEOUT_MS (500)
  21. #define UPDATE_CIDS_TIMEOUT_TICS (UPDATE_CIDS_TIMEOUT_MS / portTICK_PERIOD_MS)
  22. // Timeout between polls
  23. #define POLL_TIMEOUT_MS (1)
  24. #define POLL_TIMEOUT_TICS (POLL_TIMEOUT_MS / portTICK_PERIOD_MS)
  25. // The macro to get offset for parameter in the appropriate structure
  26. #define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) + 1))
  27. #define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) + 1))
  28. #define COIL_OFFSET(field) ((uint16_t)(offsetof(coil_reg_params_t, field) + 1))
  29. // Discrete offset macro
  30. #define DISCR_OFFSET(field) ((uint16_t)(offsetof(discrete_reg_params_t, field) + 1))
  31. #define STR(fieldname) ((const char*)( fieldname ))
  32. // Options can be used as bit masks or parameter limits
  33. #define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val }
  34. static const char *TAG = "MASTER_TEST";
  35. // Enumeration of modbus device addresses accessed by master device
  36. enum {
  37. MB_DEVICE_ADDR1 = 1 // Only one slave device used for the test (add other slave addresses here)
  38. };
  39. // Enumeration of all supported CIDs for device (used in parameter definition table)
  40. enum {
  41. CID_INP_DATA_0 = 0,
  42. CID_HOLD_DATA_0,
  43. CID_INP_DATA_1,
  44. CID_HOLD_DATA_1,
  45. CID_INP_DATA_2,
  46. CID_HOLD_DATA_2,
  47. CID_HOLD_TEST_REG,
  48. CID_RELAY_P1,
  49. CID_RELAY_P2,
  50. CID_COUNT
  51. };
  52. // Example Data (Object) Dictionary for Modbus parameters:
  53. // The CID field in the table must be unique.
  54. // Modbus Slave Addr field defines slave address of the device with correspond parameter.
  55. // Modbus Reg Type - Type of Modbus register area (Holding register, Input Register and such).
  56. // Reg Start field defines the start Modbus register number and Reg Size defines the number of registers for the characteristic accordingly.
  57. // The Instance Offset defines offset in the appropriate parameter structure that will be used as instance to save parameter value.
  58. // Data Type, Data Size specify type of the characteristic and its data size.
  59. // Parameter Options field specifies the options that can be used to process parameter value (limits or masks).
  60. // Access Mode - can be used to implement custom options for processing of characteristic (Read/Write restrictions, factory mode values and etc).
  61. const mb_parameter_descriptor_t device_parameters[] = {
  62. // { CID, Param Name, Units, Modbus Slave Addr, Modbus Reg Type, Reg Start, Reg Size, Instance Offset, Data Type, Data Size, Parameter Options, Access Mode}
  63. { CID_INP_DATA_0, STR("Data_channel_0"), STR("Volts"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 2,
  64. INPUT_OFFSET(input_data0), PARAM_TYPE_FLOAT, 4, OPTS( -10, 10, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
  65. { CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 2,
  66. HOLD_OFFSET(holding_data0), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
  67. { CID_INP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 2, 2,
  68. INPUT_OFFSET(input_data1), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
  69. { CID_HOLD_DATA_1, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 2, 2,
  70. HOLD_OFFSET(holding_data1), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
  71. { CID_INP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 4, 2,
  72. INPUT_OFFSET(input_data2), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
  73. { CID_HOLD_DATA_2, STR("Humidity_3"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 4, 2,
  74. HOLD_OFFSET(holding_data2), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
  75. { CID_HOLD_TEST_REG, STR("Test_regs"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 10, 58,
  76. HOLD_OFFSET(test_regs), PARAM_TYPE_ASCII, 116, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
  77. { CID_RELAY_P1, STR("RelayP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 0, 8,
  78. COIL_OFFSET(coils_port0), PARAM_TYPE_U16, 2, OPTS( BIT1, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
  79. { CID_RELAY_P2, STR("RelayP2"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 8, 8,
  80. COIL_OFFSET(coils_port1), PARAM_TYPE_U16, 2, OPTS( BIT0, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER }
  81. };
  82. // Calculate number of parameters in the table
  83. const uint16_t num_device_parameters = (sizeof(device_parameters)/sizeof(device_parameters[0]));
  84. // The function to get pointer to parameter storage (instance) according to parameter description table
  85. static void* master_get_param_data(const mb_parameter_descriptor_t* param_descriptor)
  86. {
  87. assert(param_descriptor != NULL);
  88. void* instance_ptr = NULL;
  89. if (param_descriptor->param_offset != 0) {
  90. switch(param_descriptor->mb_param_type)
  91. {
  92. case MB_PARAM_HOLDING:
  93. instance_ptr = ((void*)&holding_reg_params + param_descriptor->param_offset - 1);
  94. break;
  95. case MB_PARAM_INPUT:
  96. instance_ptr = ((void*)&input_reg_params + param_descriptor->param_offset - 1);
  97. break;
  98. case MB_PARAM_COIL:
  99. instance_ptr = ((void*)&coil_reg_params + param_descriptor->param_offset - 1);
  100. break;
  101. case MB_PARAM_DISCRETE:
  102. instance_ptr = ((void*)&discrete_reg_params + param_descriptor->param_offset - 1);
  103. break;
  104. default:
  105. instance_ptr = NULL;
  106. break;
  107. }
  108. } else {
  109. ESP_LOGE(TAG, "Wrong parameter offset for CID #%d", param_descriptor->cid);
  110. assert(instance_ptr != NULL);
  111. }
  112. return instance_ptr;
  113. }
  114. // User operation function to read slave values and check alarm
  115. static void master_operation_func(void *arg)
  116. {
  117. esp_err_t err = ESP_OK;
  118. float value = 0;
  119. bool alarm_state = false;
  120. const mb_parameter_descriptor_t* param_descriptor = NULL;
  121. ESP_LOGI(TAG, "Start modbus test...");
  122. for(uint16_t retry = 0; retry <= MASTER_MAX_RETRY && (!alarm_state); retry++) {
  123. // Read all found characteristics from slave(s)
  124. for (uint16_t cid = 0; (err != ESP_ERR_NOT_FOUND) && cid < MASTER_MAX_CIDS; cid++)
  125. {
  126. // Get data from parameters description table
  127. // and use this information to fill the characteristics description table
  128. // and having all required fields in just one table
  129. err = mbc_master_get_cid_info(cid, &param_descriptor);
  130. if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) {
  131. void* temp_data_ptr = master_get_param_data(param_descriptor);
  132. assert(temp_data_ptr);
  133. uint8_t type = 0;
  134. if ((param_descriptor->param_type == PARAM_TYPE_ASCII) &&
  135. (param_descriptor->cid == CID_HOLD_TEST_REG)) {
  136. // Check for long array of registers of type PARAM_TYPE_ASCII
  137. err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key,
  138. (uint8_t*)temp_data_ptr, &type);
  139. if (err == ESP_OK) {
  140. ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%08x) read successful.",
  141. param_descriptor->cid,
  142. (char*)param_descriptor->param_key,
  143. (char*)param_descriptor->param_units,
  144. *(uint32_t*)temp_data_ptr);
  145. // Initialize data of test array and write to slave
  146. if (*(uint32_t*)temp_data_ptr != 0xAAAAAAAA) {
  147. memset((void*)temp_data_ptr, 0xAA, param_descriptor->param_size);
  148. *(uint32_t*)temp_data_ptr = 0xAAAAAAAA;
  149. err = mbc_master_set_parameter(cid, (char*)param_descriptor->param_key,
  150. (uint8_t*)temp_data_ptr, &type);
  151. if (err == ESP_OK) {
  152. ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%08x), write successful.",
  153. param_descriptor->cid,
  154. (char*)param_descriptor->param_key,
  155. (char*)param_descriptor->param_units,
  156. *(uint32_t*)temp_data_ptr);
  157. } else {
  158. ESP_LOGE(TAG, "Characteristic #%d (%s) write fail, err = 0x%x (%s).",
  159. param_descriptor->cid,
  160. (char*)param_descriptor->param_key,
  161. (int)err,
  162. (char*)esp_err_to_name(err));
  163. }
  164. }
  165. } else {
  166. ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).",
  167. param_descriptor->cid,
  168. (char*)param_descriptor->param_key,
  169. (int)err,
  170. (char*)esp_err_to_name(err));
  171. }
  172. } else {
  173. err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key,
  174. (uint8_t*)&value, &type);
  175. if (err == ESP_OK) {
  176. *(float*)temp_data_ptr = value;
  177. if ((param_descriptor->mb_param_type == MB_PARAM_HOLDING) ||
  178. (param_descriptor->mb_param_type == MB_PARAM_INPUT)) {
  179. ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %f (0x%x) read successful.",
  180. param_descriptor->cid,
  181. (char*)param_descriptor->param_key,
  182. (char*)param_descriptor->param_units,
  183. value,
  184. *(uint32_t*)temp_data_ptr);
  185. if (((value > param_descriptor->param_opts.max) ||
  186. (value < param_descriptor->param_opts.min))) {
  187. alarm_state = true;
  188. break;
  189. }
  190. } else {
  191. uint16_t state = *(uint16_t*)temp_data_ptr;
  192. const char* rw_str = (state & param_descriptor->param_opts.opt1) ? "ON" : "OFF";
  193. ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %s (0x%x) read successful.",
  194. param_descriptor->cid,
  195. (char*)param_descriptor->param_key,
  196. (char*)param_descriptor->param_units,
  197. (const char*)rw_str,
  198. *(uint16_t*)temp_data_ptr);
  199. if (state & param_descriptor->param_opts.opt1) {
  200. alarm_state = true;
  201. break;
  202. }
  203. }
  204. } else {
  205. ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).",
  206. param_descriptor->cid,
  207. (char*)param_descriptor->param_key,
  208. (int)err,
  209. (char*)esp_err_to_name(err));
  210. }
  211. }
  212. vTaskDelay(POLL_TIMEOUT_TICS); // timeout between polls
  213. }
  214. }
  215. vTaskDelay(UPDATE_CIDS_TIMEOUT_TICS); //
  216. }
  217. if (alarm_state) {
  218. ESP_LOGI(TAG, "Alarm triggered by cid #%d.",
  219. param_descriptor->cid);
  220. } else {
  221. ESP_LOGE(TAG, "Alarm is not triggered after %d retries.",
  222. MASTER_MAX_RETRY);
  223. }
  224. ESP_LOGI(TAG, "Destroy master...");
  225. ESP_ERROR_CHECK(mbc_master_destroy());
  226. }
  227. // Modbus master initialization
  228. static esp_err_t master_init(void)
  229. {
  230. // Initialize and start Modbus controller
  231. mb_communication_info_t comm = {
  232. .port = MB_PORT_NUM,
  233. #if CONFIG_MB_COMM_MODE_ASCII
  234. .mode = MB_MODE_ASCII,
  235. #elif CONFIG_MB_COMM_MODE_RTU
  236. .mode = MB_MODE_RTU,
  237. #endif
  238. .baudrate = MB_DEV_SPEED,
  239. .parity = MB_PARITY_NONE
  240. };
  241. void* master_handler = NULL;
  242. esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &master_handler);
  243. MB_RETURN_ON_FALSE((master_handler != NULL), ESP_ERR_INVALID_STATE, TAG,
  244. "mb controller initialization fail.");
  245. MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
  246. "mb controller initialization fail, returns(0x%x).",
  247. (uint32_t)err);
  248. err = mbc_master_setup((void*)&comm);
  249. MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
  250. "mb controller setup fail, returns(0x%x).",
  251. (uint32_t)err);
  252. // Set UART pin numbers
  253. err = uart_set_pin(MB_PORT_NUM, CONFIG_MB_UART_TXD, CONFIG_MB_UART_RXD,
  254. CONFIG_MB_UART_RTS, UART_PIN_NO_CHANGE);
  255. MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
  256. "mb serial set pin failure, uart_set_pin() returned (0x%x).", (uint32_t)err);
  257. err = mbc_master_start();
  258. MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
  259. "mb controller start fail, returns(0x%x).",
  260. (uint32_t)err);
  261. // Set driver mode to Half Duplex
  262. err = uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX);
  263. MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
  264. "mb serial set mode failure, uart_set_mode() returned (0x%x).", (uint32_t)err);
  265. vTaskDelay(5);
  266. err = mbc_master_set_descriptor(&device_parameters[0], num_device_parameters);
  267. MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
  268. "mb controller set descriptor fail, returns(0x%x).",
  269. (uint32_t)err);
  270. ESP_LOGI(TAG, "Modbus master stack initialized...");
  271. return err;
  272. }
  273. void app_main(void)
  274. {
  275. // Initialization of device peripheral and objects
  276. ESP_ERROR_CHECK(master_init());
  277. vTaskDelay(10);
  278. master_operation_func(NULL);
  279. }