master.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. /*
  2. * SPDX-FileCopyrightText: 2016-2023 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_DISCR_P1,
  51. CID_COUNT
  52. };
  53. // Example Data (Object) Dictionary for Modbus parameters:
  54. // The CID field in the table must be unique.
  55. // Modbus Slave Addr field defines slave address of the device with correspond parameter.
  56. // Modbus Reg Type - Type of Modbus register area (Holding register, Input Register and such).
  57. // Reg Start field defines the start Modbus register number and Reg Size defines the number of registers for the characteristic accordingly.
  58. // The Instance Offset defines offset in the appropriate parameter structure that will be used as instance to save parameter value.
  59. // Data Type, Data Size specify type of the characteristic and its data size.
  60. // Parameter Options field specifies the options that can be used to process parameter value (limits or masks).
  61. // Access Mode - can be used to implement custom options for processing of characteristic (Read/Write restrictions, factory mode values and etc).
  62. const mb_parameter_descriptor_t device_parameters[] = {
  63. // { CID, Param Name, Units, Modbus Slave Addr, Modbus Reg Type, Reg Start, Reg Size, Instance Offset, Data Type, Data Size, Parameter Options, Access Mode}
  64. { CID_INP_DATA_0, STR("Data_channel_0"), STR("Volts"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 2,
  65. INPUT_OFFSET(input_data0), PARAM_TYPE_FLOAT, 4, OPTS( -10, 10, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
  66. { CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 2,
  67. HOLD_OFFSET(holding_data0), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
  68. { CID_INP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 2, 2,
  69. INPUT_OFFSET(input_data1), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
  70. { CID_HOLD_DATA_1, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 2, 2,
  71. HOLD_OFFSET(holding_data1), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
  72. { CID_INP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 4, 2,
  73. INPUT_OFFSET(input_data2), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
  74. { CID_HOLD_DATA_2, STR("Humidity_3"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 4, 2,
  75. HOLD_OFFSET(holding_data2), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
  76. { CID_HOLD_TEST_REG, STR("Test_regs"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 10, 58,
  77. HOLD_OFFSET(test_regs), PARAM_TYPE_ASCII, 116, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
  78. { CID_RELAY_P1, STR("RelayP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 2, 6,
  79. COIL_OFFSET(coils_port0), PARAM_TYPE_U8, 1, OPTS( 0xAA, 0x15, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
  80. { CID_RELAY_P2, STR("RelayP2"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 10, 6,
  81. COIL_OFFSET(coils_port1), PARAM_TYPE_U8, 1, OPTS( 0x55, 0x2A, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
  82. { CID_DISCR_P1, STR("DiscreteInpP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_DISCRETE, 2, 7,
  83. DISCR_OFFSET(discrete_input_port1), PARAM_TYPE_U8, 1, OPTS( 0xAA, 0x15, 0 ), PAR_PERMS_READ_WRITE_TRIGGER }
  84. };
  85. // Calculate number of parameters in the table
  86. const uint16_t num_device_parameters = (sizeof(device_parameters)/sizeof(device_parameters[0]));
  87. // The function to get pointer to parameter storage (instance) according to parameter description table
  88. static void* master_get_param_data(const mb_parameter_descriptor_t* param_descriptor)
  89. {
  90. assert(param_descriptor != NULL);
  91. void* instance_ptr = NULL;
  92. if (param_descriptor->param_offset != 0) {
  93. switch(param_descriptor->mb_param_type)
  94. {
  95. case MB_PARAM_HOLDING:
  96. instance_ptr = ((void*)&holding_reg_params + param_descriptor->param_offset - 1);
  97. break;
  98. case MB_PARAM_INPUT:
  99. instance_ptr = ((void*)&input_reg_params + param_descriptor->param_offset - 1);
  100. break;
  101. case MB_PARAM_COIL:
  102. instance_ptr = ((void*)&coil_reg_params + param_descriptor->param_offset - 1);
  103. break;
  104. case MB_PARAM_DISCRETE:
  105. instance_ptr = ((void*)&discrete_reg_params + param_descriptor->param_offset - 1);
  106. break;
  107. default:
  108. instance_ptr = NULL;
  109. break;
  110. }
  111. } else {
  112. ESP_LOGE(TAG, "Wrong parameter offset for CID #%u", (unsigned)param_descriptor->cid);
  113. assert(instance_ptr != NULL);
  114. }
  115. return instance_ptr;
  116. }
  117. // User operation function to read slave values and check alarm
  118. static void master_operation_func(void *arg)
  119. {
  120. esp_err_t err = ESP_OK;
  121. float value = 0;
  122. bool alarm_state = false;
  123. const mb_parameter_descriptor_t* param_descriptor = NULL;
  124. ESP_LOGI(TAG, "Start modbus test...");
  125. for(uint16_t retry = 0; retry <= MASTER_MAX_RETRY && (!alarm_state); retry++) {
  126. // Read all found characteristics from slave(s)
  127. for (uint16_t cid = 0; (err != ESP_ERR_NOT_FOUND) && cid < MASTER_MAX_CIDS; cid++)
  128. {
  129. // Get data from parameters description table
  130. // and use this information to fill the characteristics description table
  131. // and having all required fields in just one table
  132. err = mbc_master_get_cid_info(cid, &param_descriptor);
  133. if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) {
  134. void* temp_data_ptr = master_get_param_data(param_descriptor);
  135. assert(temp_data_ptr);
  136. uint8_t type = 0;
  137. if ((param_descriptor->param_type == PARAM_TYPE_ASCII) &&
  138. (param_descriptor->cid == CID_HOLD_TEST_REG)) {
  139. // Check for long array of registers of type PARAM_TYPE_ASCII
  140. err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key,
  141. (uint8_t*)temp_data_ptr, &type);
  142. if (err == ESP_OK) {
  143. ESP_LOGI(TAG, "Characteristic #%u %s (%s) value = (0x%" PRIx32 ") read successful.",
  144. param_descriptor->cid,
  145. param_descriptor->param_key,
  146. param_descriptor->param_units,
  147. *(uint32_t*)temp_data_ptr);
  148. // Initialize data of test array and write to slave
  149. if (*(uint32_t*)temp_data_ptr != 0xAAAAAAAA) {
  150. memset((void*)temp_data_ptr, 0xAA, param_descriptor->param_size);
  151. *(uint32_t*)temp_data_ptr = 0xAAAAAAAA;
  152. err = mbc_master_set_parameter(cid, (char*)param_descriptor->param_key,
  153. (uint8_t*)temp_data_ptr, &type);
  154. if (err == ESP_OK) {
  155. ESP_LOGI(TAG, "Characteristic #%u %s (%s) value = (0x%" PRIx32 "), write successful.",
  156. param_descriptor->cid,
  157. param_descriptor->param_key,
  158. param_descriptor->param_units,
  159. *(uint32_t*)temp_data_ptr);
  160. } else {
  161. ESP_LOGE(TAG, "Characteristic #%u (%s) write fail, err = 0x%x (%s).",
  162. param_descriptor->cid,
  163. param_descriptor->param_key,
  164. (int)err,
  165. (char*)esp_err_to_name(err));
  166. }
  167. }
  168. } else {
  169. ESP_LOGE(TAG, "Characteristic #%u (%s) read fail, err = 0x%x (%s).",
  170. param_descriptor->cid,
  171. param_descriptor->param_key,
  172. (int)err,
  173. (char*)esp_err_to_name(err));
  174. }
  175. } else {
  176. err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key,
  177. (uint8_t*)temp_data_ptr, &type);
  178. if (err == ESP_OK) {
  179. if ((param_descriptor->mb_param_type == MB_PARAM_HOLDING) ||
  180. (param_descriptor->mb_param_type == MB_PARAM_INPUT)) {
  181. value = *(float*)temp_data_ptr;
  182. ESP_LOGI(TAG, "Characteristic #%u %s (%s) value = %f (0x%" PRIx32 ") read successful.",
  183. param_descriptor->cid,
  184. param_descriptor->param_key,
  185. param_descriptor->param_units,
  186. value,
  187. *(uint32_t*)temp_data_ptr);
  188. if (((value > param_descriptor->param_opts.max) ||
  189. (value < param_descriptor->param_opts.min))) {
  190. alarm_state = true;
  191. break;
  192. }
  193. } else {
  194. uint8_t state = *(uint8_t*)temp_data_ptr;
  195. const char* rw_str = (state & param_descriptor->param_opts.opt1) ? "ON" : "OFF";
  196. if ((state & param_descriptor->param_opts.opt2) == param_descriptor->param_opts.opt2) {
  197. ESP_LOGI(TAG, "Characteristic #%u %s (%s) value = %s (0x%" PRIx8 ") read successful.",
  198. param_descriptor->cid,
  199. param_descriptor->param_key,
  200. param_descriptor->param_units,
  201. (const char*)rw_str,
  202. *(uint8_t*)temp_data_ptr);
  203. } else {
  204. ESP_LOGE(TAG, "Characteristic #%u %s (%s) value = %s (0x%" PRIx8 "), unexpected value.",
  205. param_descriptor->cid,
  206. param_descriptor->param_key,
  207. param_descriptor->param_units,
  208. (const char*)rw_str,
  209. *(uint8_t*)temp_data_ptr);
  210. alarm_state = true;
  211. break;
  212. }
  213. if (state & param_descriptor->param_opts.opt1) {
  214. alarm_state = true;
  215. break;
  216. }
  217. }
  218. } else {
  219. ESP_LOGE(TAG, "Characteristic #%u (%s) read fail, err = 0x%x (%s).",
  220. param_descriptor->cid,
  221. param_descriptor->param_key,
  222. (int)err,
  223. (char*)esp_err_to_name(err));
  224. }
  225. }
  226. vTaskDelay(POLL_TIMEOUT_TICS); // timeout between polls
  227. }
  228. }
  229. vTaskDelay(UPDATE_CIDS_TIMEOUT_TICS);
  230. }
  231. if (alarm_state) {
  232. ESP_LOGI(TAG, "Alarm triggered by cid #%u.", param_descriptor->cid);
  233. } else {
  234. ESP_LOGE(TAG, "Alarm is not triggered after %u retries.", MASTER_MAX_RETRY);
  235. }
  236. ESP_LOGI(TAG, "Destroy master...");
  237. ESP_ERROR_CHECK(mbc_master_destroy());
  238. }
  239. // Modbus master initialization
  240. static esp_err_t master_init(void)
  241. {
  242. // Initialize and start Modbus controller
  243. mb_communication_info_t comm = {
  244. .port = MB_PORT_NUM,
  245. #if CONFIG_MB_COMM_MODE_ASCII
  246. .mode = MB_MODE_ASCII,
  247. #elif CONFIG_MB_COMM_MODE_RTU
  248. .mode = MB_MODE_RTU,
  249. #endif
  250. .baudrate = MB_DEV_SPEED,
  251. .parity = MB_PARITY_NONE
  252. };
  253. void* master_handler = NULL;
  254. esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &master_handler);
  255. MB_RETURN_ON_FALSE((master_handler != NULL), ESP_ERR_INVALID_STATE, TAG,
  256. "mb controller initialization fail.");
  257. MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
  258. "mb controller initialization fail, returns(0x%x).", (int)err);
  259. err = mbc_master_setup((void*)&comm);
  260. MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
  261. "mb controller setup fail, returns(0x%x).", (int)err);
  262. // Set UART pin numbers
  263. err = uart_set_pin(MB_PORT_NUM, CONFIG_MB_UART_TXD, CONFIG_MB_UART_RXD,
  264. CONFIG_MB_UART_RTS, UART_PIN_NO_CHANGE);
  265. MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
  266. "mb serial set pin failure, uart_set_pin() returned (0x%x).", (int)err);
  267. err = mbc_master_start();
  268. MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
  269. "mb controller start fail, returned (0x%x).", (int)err);
  270. // Set driver mode to Half Duplex
  271. err = uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX);
  272. MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
  273. "mb serial set mode failure, uart_set_mode() returned (0x%x).", (int)err);
  274. vTaskDelay(5);
  275. err = mbc_master_set_descriptor(&device_parameters[0], num_device_parameters);
  276. MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
  277. "mb controller set descriptor fail, returns(0x%x).", (int)err);
  278. ESP_LOGI(TAG, "Modbus master stack initialized...");
  279. return err;
  280. }
  281. void app_main(void)
  282. {
  283. // Initialization of device peripheral and objects
  284. ESP_ERROR_CHECK(master_init());
  285. vTaskDelay(10);
  286. master_operation_func(NULL);
  287. }