master.c 16 KB

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