Modbus TCP Implementation

ESP_alisitsyn
Posts: 211
Joined: Fri Feb 01, 2019 4:02 pm
Contact:

Re: Modbus TCP Implementation

Postby ESP_alisitsyn » Thu Nov 05, 2020 4:32 pm

@dkaufmann,

I completed the tests and the TCP slave is able to define several areas mapped to Modbus registers per each register space.
The definition of modbus areas is performed as in code below:

Code: Select all

#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) >> 1))
#define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) >> 1))

#define MB_REG_DISCRETE_INPUT_START         (0x0000)
#define MB_REG_COILS_START                  (0x0000)
#define MB_REG_INPUT_START_AREA0            (INPUT_OFFSET(input_data0)) // register offset input area 0
#define MB_REG_INPUT_START_AREA1            (INPUT_OFFSET(input_data4)) // register offset input area 1
#define MB_REG_HOLDING_START_AREA0          (HOLD_OFFSET(holding_data0))
#define MB_REG_HOLDING_START_AREA1          (HOLD_OFFSET(holding_data4))
    
    reg_area.type = MB_PARAM_HOLDING; // Set type of register area
    reg_area.start_offset = MB_REG_HOLDING_START_AREA0; // Offset of register area in Modbus protocol
    reg_area.address = (void*)&holding_reg_params.holding_data0; // Set pointer to storage instance
    reg_area.size = sizeof(float) << 2; // Set the size of register storage instance
    ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));

    reg_area.type = MB_PARAM_HOLDING; // Set type of register area
    reg_area.start_offset = MB_REG_HOLDING_START_AREA1; // Offset of register area in Modbus protocol
    reg_area.address = (void*)&holding_reg_params.holding_data4; // Set pointer to storage instance
    reg_area.size = sizeof(float) << 2; // Set the size of register storage instance
    ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));
    
I define two descriptors for area (holding registers 40000 -40008 = holding_data0 - 3), 40158 - 40166(holding_data4 - 7)) to read four float values. The same is for input registers (input registers 30000 -30008 = input_data0 - 3), (input registers 30158 -30166 = input_data4 - 8).
The above is already changed in the attached example which also overrides parameter definitions and modbus component.
You need to unpack the archive to any folder in your computer with installed esp-idf the build and flash (idf.py -b 921600 flash monitor). This is part of official update in ESP-IDF per your feature request.

Please check the project and let me know if you have any issues.
Thanks.
Attachments
mb_tcp_multi_area.zip
Modbus TCP slave project with support of multiple area descriptors.
(251.35 KiB) Downloaded 554 times
modbus_poll.png
Modbus_poll project to test
modbus_poll.png (61.2 KiB) Viewed 9080 times

jas39_
Posts: 21
Joined: Mon Aug 29, 2016 8:26 pm

Re: Modbus TCP Implementation

Postby jas39_ » Sat Nov 07, 2020 7:25 pm

I'm definitely interested to help out verifying your code

ESP_alisitsyn
Posts: 211
Joined: Fri Feb 01, 2019 4:02 pm
Contact:

Re: Modbus TCP Implementation

Postby ESP_alisitsyn » Mon Nov 09, 2020 9:29 am

Ok, please check this and let me know if it works as expected on your side. Thanks.

dkaufmann
Posts: 24
Joined: Wed May 17, 2017 9:06 am

Re: Modbus TCP Implementation

Postby dkaufmann » Tue Nov 10, 2020 8:50 am

Thanks for the update. Now it works perfectly and I can define several areas of holding registers. But I have some general problems using this library (Modbus TCP-slave):

1) Like in your example: When updating the holding or input register value on read request, the old value is already returned to Modbus Master. So when the master only reads once and not continuously, the value will not be up to date (old value is returned). So if I don't want to update all holding registers regularly and not upon read, it will never be the newest value.

2) I have some function, where the TCP slave should return a value based on the address which should be read from another device in real time. So a READ and a WRITE callback would be very helpful. If there was a read and write callback function where I could return a value depending on the Modbus address, it would fulfill all my requirements. In our application, we give access via Modbus to all our device parameters, So I would need to reserve a lot (!) of holding registers which I all would need to update regularly. I would prefer a logic (callbacks) to only update the requested holding register and give it back as return value of the callback on request.

Do you think 2) could also be realized with the current variant? Any idea how I could use the library based on my request?

Thanks and best regards
DK

jas39_
Posts: 21
Joined: Mon Aug 29, 2016 8:26 pm

Re: Modbus TCP Implementation

Postby jas39_ » Wed Nov 11, 2020 6:46 pm

Looks good, and I can have multiple areas defined.

Would be great to have each area defined with a memory pointer to one "read area" and one "write area" in the call. And using them accordingly in the read and write functions.

Then you can avoid overwriting results if not wanted. Setting the memory pointer to the same area would give same behaviour as today.

dkaufmann
Posts: 24
Joined: Wed May 17, 2017 9:06 am

Re: Modbus TCP Implementation

Postby dkaufmann » Thu Nov 12, 2020 12:46 pm

Okay thank you for your efforts to improve this ESP freemodbus port. I now use your example you shared and I changed the callback function:

Code: Select all

eMBErrorCode mbc_reg_holding_slave_cb(UCHAR * reg_buffer, USHORT address, 
				USHORT n_regs, eMBRegisterMode mode)
according my wishes. It all works perfectly. The only thing i don't like is, that I need to change things in the modbus library. So if I update esp-idf and reset the changes from the modbus component, it won't work anymore. I saw that it is already prepared to map a customized callback function instead of the default:

Code: Select all

eMBErrorCode eMBRegHoldingCB(UCHAR * pucRegBuffer, USHORT usAddress,
                                USHORT usNRegs, eMBRegisterMode eMode)
{
    eMBErrorCode error = MB_ENOERR;
    MB_SLAVE_CHECK((slave_interface_ptr != NULL),
                    ESP_ERR_INVALID_STATE,
                    "Slave interface is not correctly initialized.");

    if (slave_interface_ptr->slave_reg_cb_holding) {
        error = slave_interface_ptr->slave_reg_cb_holding(pucRegBuffer, usAddress, usNRegs, eMode);
    } else {
        error = mbc_reg_holding_slave_cb(pucRegBuffer, usAddress, usNRegs, eMode);
    }
    return error;
}
But in the file mbc_tcp_slave.c this function pointer is = NULL:

Code: Select all

mbs_interface_ptr->slave_reg_cb_holding = NULL;
Is there a possibility to make customized function outside the modbus component and set this callback function pointer accordingly? I think this would be a proper solution. Until now I could not find a possibility to add my custom callback function without changing any code in the mb_component.

ESP_alisitsyn
Posts: 211
Joined: Fri Feb 01, 2019 4:02 pm
Contact:

Re: Modbus TCP Implementation

Postby ESP_alisitsyn » Mon Nov 16, 2020 1:00 pm

@dkaufmann, jas39_,

Thank you for you testing results and advices.
My answers are below:
1) Like in your example: When updating the holding or input register value on read request, the old value is already returned to Modbus Master. So when the master only reads once and not continuously, the value will not be up to date (old value is returned). So if I don't want to update all holding registers regularly and not upon read, it will never be the newest value.
Hope I understand your issue correctly. The example updates the characteristic value on read/write request but this is just simplified example to show how to work with API. You can modify the logic in app_main() cycle or in your dedicated modbus task as you need. For example, you can create update task, read ADC value or whatever data on a timer timeout and update the value of characteristic accordingly in critical section. Then on read access the characteristic will be read by host.
2) I have some function, where the TCP slave should return a value based on the address which should be read from another device in real time. So a READ and a WRITE callback would be very helpful. If there was a read and write callback function where I could return a value depending on the Modbus address,
I can not provide this functionality because these user callback functions will be executed in context of modbus controller task. This can slow down the event processing and even cause critical errors if the callback function has some errors. I do not need additional compains for this in Modbus. This behavior can be realised in user task which realizes modbus behavior. As an example it is possible to use subject observer pattern to notify each registered observer on its characteristic access and do appropriate action in its update() method.
3) Would be great to have each area defined with a memory pointer to one "read area" and one "write area" in the call.
This requires the change of API. I can not change it now as required. However it can be realise in user application to handle read/write access appropriately.
4) Is there a possibility to make customized function outside the mod bus component and set this callback function pointer accordingly? I think this would be a proper solution. Until now I could not find a possibility to add my custom callback function without changing any code in the mb_component.
Is this really required? I can not do this without changes in the common interface. If it is absolutely required, you can add your concrete modbus port to the library, redefine the callback functions there (or even IO and other behavior) and leave the API the same. I don't think the callbacks should be overridden from user task. This can be implemented later as a feature request.

I will take your advises into account and will try to realize within the scope of current architecture later. I have to avoid the functionality in the Mod bus library that can cause other issues depending on user implementation.
Last edited by ESP_alisitsyn on Tue Nov 17, 2020 6:16 pm, edited 2 times in total.

dkaufmann
Posts: 24
Joined: Wed May 17, 2017 9:06 am

Re: Modbus TCP Implementation

Postby dkaufmann » Mon Nov 16, 2020 1:42 pm

thanks for you comments. Please find my comment to 1), 2) and 4) below:

I understand your point and it would also work as you suggested:
1) define register areas of holding registers
2) write a modbus task to update holding register continuously, so all holding registers contain valid information in RAM all the time

But I have many holding registers, that I don't want to update all of them regularly. I don't even want to keep the holding register data in the RAM. So I don't even need to create holding register areas.
I would prefer to only update the holding register on request (from Modbus master) because I have to get all my holding register data from a I2C connected device. This could be done using a read callback routine which gets the requested holding register value (based in Modbus address) directly from the I2C connected device, without actually store the value in RAM all the time. I know, holding registers are supposed to keep the value in RAM by default, but it would not be memory friendly in my case as I have more than 1000 registers I have to serve.
Additionally the register map is dynamic as it depends on the connected I2C device. So I know that this might be a special case, but I used the freemodbus library directly for other projects and there it was possible to use the read / write callback directly as I described above.

best regards
DK

ESP_alisitsyn
Posts: 211
Joined: Fri Feb 01, 2019 4:02 pm
Contact:

Re: Modbus TCP Implementation

Postby ESP_alisitsyn » Mon Nov 16, 2020 2:13 pm

@dkaufmann,

As I understand this does not work for you as you planned. The project you referenced is the custom project. I cannot realize the user customized callback functions to read each register like there. I have to maintain the modbus library but this functionality is the risk in case of user errors in callbacks. This critical behavior must be outside of the library according to requirements. I propose you to realize customized callback functions using lists similar to what I did for area descriptors. You can add function pointers there for each area to call them on access to area. It's possible to realize this for each register but it will slow down the processing and can cause a risk of side effects to modbus due to timeouts. These things can be realized in your application layer or modification of modbus controller but are outside of current requirements. I will think about it and will let you know if there any possibility later. I can not address support of all special cases in the library.

dkaufmann
Posts: 24
Joined: Wed May 17, 2017 9:06 am

Re: Modbus TCP Implementation

Postby dkaufmann » Mon Nov 16, 2020 2:21 pm

I understand this. I will try to handle it by myself with a customized callback in the modbus component part. I hope there will be a supported possibility later which allows to return a value based on a the requested address (or to write a value based on the specific address). The difference would be, that the read/write request would not directly access the RAM variable of a holding register, instead it would give back a value based on the callback.
best regards
DK

Who is online

Users browsing this forum: Bing [Bot], Majestic-12 [Bot] and 65 guests