espressif / esp-modbus

ESP-Modbus - the officially suppported library for Modbus protocol (serial RS485 + TCP over WiFi or Ethernet).
Apache License 2.0
104 stars 49 forks source link

Is it possible to change baudrate in runtime after modbus has started? (IDFGH-13586) #71

Open kotyara12 opened 2 weeks ago

kotyara12 commented 2 weeks ago

Hello!

At the moment I have encountered a problem with rs485/modbus. First, I will describe the situation in detail.

I have a small network of rs485/modbus sensors connected to the same physical RS485 bus and collecting data of different types. They all support a baud rate of 9600, and at this speed everything works fine.

But now I needed to connect a gas boiler adapter to the same bus, which works ONLY at a speed of 19200 and strictly at a specific address. There are no problems with the address, but with the baud rate - a big problem.

I tried to transfer all other devices on the bus to 19200. For most devices I succeeded. But there are some sensors bought on AliExpress (with a display), which either do not have a speed change register, or it is not documented. The seller pretends that "yours is mine, I do not understand", and refuses to help.

Now the question itself.

The solution to the problem would be the ability to change the transmission speed directly during operation, that is, before accessing the adapter, set the speed to 19200, and after the communication session, return everything to 9600. Question for experts: is this possible and how to do it?

PS: I work with ESP-IDF 5.2

#define CONFIG_GPIO_RS485_RX              25
#define CONFIG_GPIO_RS485_TX              32
#define CONFIG_GPIO_RS485_RTS             33
#define CONFIG_GPIO_RS485_CTS             -1
#define CONFIG_MODBUS_PORT                UART_NUM_1
#define CONFIG_MODBUS_SPEED               9600

  // Init RS485 & Modbus
  RE_OK_CHECK_EVENT(mbc_master_init(MB_PORT_SERIAL_MASTER, &_modbus), return);
  // Configure Modbus
  mb_communication_info_t comm;
  memset(&comm, 0, sizeof(comm));
  comm.mode = MB_MODE_RTU;
  comm.port = CONFIG_MODBUS_PORT;
  comm.baudrate = CONFIG_MODBUS_SPEED;
  comm.parity = UART_PARITY_DISABLE;
  RE_OK_CHECK_EVENT(mbc_master_setup((void*)&comm), return);
  // Set UART pins
  RE_OK_CHECK_EVENT(uart_set_pin(CONFIG_MODBUS_PORT, CONFIG_GPIO_RS485_TX, CONFIG_GPIO_RS485_RX, CONFIG_GPIO_RS485_RTS, CONFIG_GPIO_RS485_CTS), return);
  // Start Modbus
  RE_OK_CHECK_EVENT(mbc_master_start(), return);
  // Set UART mode
  RE_OK_CHECK_EVENT(uart_set_mode(CONFIG_MODBUS_PORT, UART_MODE_RS485_HALF_DUPLEX), return);
alisitsyn commented 1 week ago

Hello @kotyara12,

I understand your use case and it is different from typical Modbus Master use case. However, I think there is relatively simple way to accomplish this.

The solution to the problem would be the ability to change the transmission speed directly during operation, that is, before accessing the adapter, set the speed to 19200, and after the communication session, return everything to 9600. Question for experts: is this possible and how to do it?

I think this would work. The object dictionary has the optional fields to keep the user information for each CID. You can use this field to store the baudrate of your modbus devices. Below just a simple approach that can be modified for actual use case.

  1. Update the data dictionary to set the third field (for example) to desired speed of your device.
  2. The task function is updated to set the baudrate before sending the Modbus request.
  3. We have to wait to ensure that the slave transaction is completed before start of next sequence.
  4. If you have multi-thread access, this can be handled accordingly using the mutex.
const mb_parameter_descriptor_t device_parameters[] = {
    // { CID, Param Name, Units, Modbus Slave Addr, Modbus Reg Type, Reg Start, Reg Size, Instance Offset, Data Type, Data Size, Parameter Options, Access Mode}
    { CID_INP_DATA_0, STR("Data_channel_0"), STR("Volts"), MB_DEVICE_ADDR1, MB_PARAM_INPUT,
            TEST_INPUT_REG_START(input_data0), TEST_INPUT_REG_SIZE(input_data0),
            INPUT_OFFSET(input_data0), PARAM_TYPE_FLOAT, 4,
            OPTS( 0, 0, 19200), PAR_PERMS_READ_WRITE_TRIGGER },
    /* This modbus slave at address MB_DEVICE_ADDR2 uses the different baud rate settings. */
    { CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR2, MB_PARAM_HOLDING,
            TEST_HOLD_REG_START(holding_data0), TEST_HOLD_REG_SIZE(holding_data0),
            HOLD_OFFSET(holding_data0), PARAM_TYPE_FLOAT, 4,
            OPTS( 0, 0, 9600 ), PAR_PERMS_READ_WRITE_TRIGGER },
     }
.... other code
static void master_operation_func(void *arg) {
    err = mbc_master_get_cid_info(master_handle, cid, &param_descriptor);
    // .... Your other code, refer to example
    // This will override the UART baud rate before sending the request 
    err = uart_set_baudrate(MB_PORT_NUM, (uint32_t)param_descriptor->param_opts.opt3);
.....
    // The speed of UART is overriden, the request will be sent on optional baudrate
    err = mbc_master_get_parameter(handle, param_descriptor->cid, (uint8_t *)pinst, &type); 
    // process your errors and data accordingly, refer to example 
    // Note: the response to request can be received later than CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND
    // and the new request should not start earlier, otherwise it causes receive errors and then synchronization issues. 
    // The simple way is to just wait one more cycle of respond time before proceed.
    vTaskDelay(CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND / portTICK_PERIOD_MS);

I think it is possible to avoid possible issues if they happen. Will this work for your case?

kotyara12 commented 1 week ago

Hello @alisitsyn,

Yes, that should help. Thank you very much!!! After some time I will be able to check it and will write back / close the request. Thanks again