Modbus TCP with register array > 255

I am trying to implement Compact M40 Modbus TCP support in our hardware using the sample code from appl_adimap_separate16.c as the base. Our modbus input registers and holding registers are simply declared as:
UINT16 iReg[65536];
UINT16 hReg[65536];
covering the maximum number of addressable registers by the Modbus protocol.
In appl_adimap_separate16.c, I changed the definition of APPL_asAdiEntryList to:
AD_AdiEntryType APPL_asAdiEntryList[] =
{
{ 10, “ABP_UINT16_SET”, ABP_UINT16, 32, APPL_READ_MAP_WRITE_ACCESS_DESC, { { hReg,NULL } }, NULL, SetAdi10Value },
{ 11, “ABP_UINT16_GET”, ABP_UINT16, 32, APPL_WRITE_MAP_READ_ACCESS_DESC, { { iReg,NULL } }, NULL, NULL },
};
The code works for the 32 registers declared in the list. Unfortunately, the bNumOfElements data type is UINT8, supporting only 255 registers.

How can I extend the code to support the full range of the Modbus TCP registers

Hello,

It looks like the max number of elements per ADI is capped at 255 which I think is what you’re running into with this issue. I don’t think that this is something that could be done with a single ADI.

image

In the Modbus TCP column, the max. no of elements is 32 per ADI. Does it mean that I have to insert 4000 lines of code, each line declare 1 ADI with 32 elements, into the AD_AdiEntryType array in order to be able to support the full range of Modbus TCP registers? Are there any alternatives?

Hello,

I found out with modbus it looks like there is a section of code that will allow you to up the limit of elements per ADI to 128, but that seems to be the limit for this application

image
See page 44 of this doc: https://www.anybus.com/docs/librariesprovider7/default-document-library/manuals-design-guides/hms-scm-1202-022.pdf?sfvrsn=c6aab9d6_12

Since a modbus register is 16-bits it means you could have 2 8-bit elements in an ADI for each modbus register so that puts you to 256 in theory.

Even if each ADI element can has 128 elements, I still need to insert 1024 ADIs to cover the full range of modbus input and holding registers. That is kind of very inconvenient. On the other hand, I find out that if I use 128 elements per ADI, the build-in WEB server of the M40 module cannot handle this number of elements and will crash. I have to settle to use 64 elements per ADI. Luckily, the active modbus register range in the application is 10000 (input + holding registers) and I need only insert 156 ADI entries. Using the build-in WEB server, the scheme seems working properly.

After some tests in the implementation, I ran into some strangle behavior. In the holding register section, there are only 3 active regions. In each regions, I have setup 3 separate callbacks if the registers in the regions are being updated. When I tried changing the holding registers using the WEB server, the corresponding callbacks are called and it is working properly. When I actually use the Modbus protocol “Write Multiple Registers (16)” to change the registers 1056 (mapped to ADI holding register 0), all 3 callbacks will be called one after the others. If I use the Modbus protocol “Write Multiple Registers (16)” to change the registers 5122 or 5377, none of the callbacks are being called.

The other issue is that even if I update only 1 register using the Modbus protocol, the callback parameters received always has the bStartIndex set to 0 and the bNumElements set to 64. I have no way to determine which register has been updated unless I perform a byte-to-byte comparison.to the master holding registers.

I have enclosed the ADI map implementation for your reference.

appl_adimap_separate16.c (33.9 KB)

I was taking a look at your adimapping and it looks like registers 5122 and 5377 aren’t mentioned here. Can you try doing this with 5088 and 5344 and see if the callbacks work?
image

Register 5122 is one of the 64 registers in the region appl_aiUint16_5088 and Register 5377 is one of the 64 registers in region appl_aiUnit16_5344.

Anyway, I did trying doing this with 5088 and 5344, the callbacks still did not work.
image

Can you send us a wireshark capture of when you’re trying to initiate these callbacks? Can you also go in and enable the ABCC_DRV_CFG.h to print out the debug information so we can see what the device is showing.

image

Note that it’s expected that these printouts can go to some kind of console where you can save the output to a logfile for us to inspect. this section can be found in the abcc_sw_port.h
image

When I enabled only the 3 that you specified, I got only the following messages when the program started:
ANB_STATUS: ABP_ANB_STATE_SETUP
RSP MSG_DATA_FORMAT: 0
RSP MSG_GET_PARAM_SUPPORT: 1
RSP MSG_GET_MODULE_ID: 0x403
RSP MSG_GET_NETWORK_ID: 0x93
RSP MSG_GET_SERIAL_NUM: 0xA036D91A
RSP MSG_GET_FW_VERSION: 1.10.1
RSP MSG_MAP_IO_****
RSP MSG_MAP_IO_****
RSP MSG_MAP_IO_****
RSP MSG_MAP_IO_****
RSP MSG_SETUP_COMPLETE
Mapped PD size, RdPd 384 WrPd: 128
ANB_STATUS: ABP_ANB_STATE_NW_INIT
ANB_STATUS: ABP_ANB_STATE_WAIT_PROCESS
ANB_STATUS: ABP_ANB_STATE_PROCESS_ACTIVE

There was no other message when I sent the write multiple register commands.

After I enable the other two flags, I got messages. Enclosed are the console logfile and the wireshark capture. Use the filter “modbus.func_code==16” and you will see the two messages that I sent to write to the holding registers.putty20190823160445.log (128.1 KB)
M40_hRegCallback.pcapng (242.8 KB)

I also enclosed the wireshark capture when I used the WEB browser to change HR #5088 and HR #5344. Both did trigger the corresponding callbacks.M40_http2.pcapng (27.1 KB)

Hi @derekyu

Yeah I meant to say set all of those to true but it looks like you figured it out. Based on how you’re using 64 register modbus ADIs we think that you may be writing to the wrong location.

From looking at your M40_hRegCallback.pcapng which was showing 5088, this leads us to believe that the equivalent ADI would be this:

Note: 1010h = 4112d which is our first ADI in the holding registers

(5088 - 4112)/64 + 1 = ADI 16

In M40_http2.cpapng, it looks like you have an http request using update.json specifying ADI instance 77 which would come out to:

(77-1)*64 + 4112 = Holding Register 8976

It looks like in one case you’re trying to write ADI 16 and in the other you’re writing ADI 77, but only one of these has a callback. Therefore the holding register that you’re attempting to write to is probably not the intended one

Sorry I didn’t mentioned that I have the following set in abcc_obj_cfg.h:
/*
** Attribute 11: Modbus read/write registers command offset (Array of SINT16 - {0x0000-0xFFFF})
*/
#ifndef MOD_IA_RW_OFFSET_ENABLE
#define MOD_IA_RW_OFFSET_ENABLE TRUE
#define MOD_IA_RW_OFFSET_READ_VALUE 0xFBE0
#define MOD_IA_RW_OFFSET_WRITE_VALUE 0xFBE0
#endif
The 0xFBE0 is -1056. This effectively mapped ADI 0 to HR #1056. I learnt this technique back in 2018-09-24 on HMS support issue 1836-28565. As I mentioned earlier, if I write to HR #1056, all three callbacks were triggered and I can verify that the ADI 0 in the first callback has the correct value for the modbus write HR #1056 and no change in the other two callbacks.

Let me show you the case when I write to HR #1056. This is the Wireshark screen for the message write to HR #1056 with value 123:
image
I had a break point setup in callback SetAdi1056Value() for ADI #0 and the function was hit. You can see the piValuePtr is updated correctly:
image
I removed the break point and continue the debugging. The other two callbacks also got hit for unknown reason even though this is no other write holding register command and no change in the corresponding holding registers.

Wait so you’re saying that the other two callbacks for 5122 and 5377 are being called when you write to 1056?

Yes, if I write to HR #1056, all three callbacks are being called sequentially. If I write to HR #5122 or 5377, none of the callbacks are being called.

Hi @derekyu

Here’s what I heard back from some colleagues over in Sweden on this issue.

The callbacks for each individual ADI is the most useful when the ADI is not mapped to process data. The callbacks for all ADIs mapped to process data are called when the process data area is updated (even if the specified process data area where a specific ADI is mapped is not updated). This is what you see when writing to an ADI mapped to process data and all other callbacks are called.

There might be a misunderstanding regarding the Modbus registers. ADI 73 is located in Modbus register 8720 + 1056 = 9776 instead of 6088. For ADI 77 it is located in Modbus register 8976 + 1056 = 10032 instaed of 5344. This seems to be why you’re not getting callbacks when writing to those registers. The whole ADI (64*UINT16) must be written in order for the write to be accepted by the CompactCom (and for the callback to be called).

To use callback functions, it is best not to map ADIs to process data (Modbus registers 0000h-02FFh and 0800h-0AFFh). If the ADIs are mapped to process data it is better to handle the data handling in e.g. a cyclically called function and not use callbacks. Instead use Modbus register area 1010h-FFFFh for ADI accesses

  1. You said “The callbacks for all ADIs mapped to process data are called when the process data area is updated (even if the specified process data area where a specific ADI is mapped is not updated).” Why is that when I used the WEB browser to update one register, only the corresponding callback is being called. Unlike using the Modbus protocol that always report the whole ADI, it even reported only the register that has been updated. This behavior is very inconsistent.
  2. It does not explain why when I wrote to HR#5088 or HR#5344, none of the callbacks are being called.
  3. I don’t quite understand the relationship between ADI and Modbus registers. To help myself understanding this, I added a callback to the second ADI instance.When I wrote to HR#1120, all 4 callbacks are being hit and the callback SetAdi1120Value() showed the correct register was being updated. As the ADIs are arranged sequentially, I have reason to believe that the ADI to Modbus mapping is correct.
  4. My implementation is purely based on the original appl_adimap_separate16.c. Can you give me an example that I do not need to map ADI to process data and being able to use the callback functions? Can you give me an example on how I can “handle the data handling in e.g. a cyclically called function and not use callbacks. Instead use Modbus register area 1010h-FFFFh for ADI accesses”. I cannot find this information in the examples provided by the API.
    To help you understand what I am talking about, I have enclosed the updated version of appl_adimap_separate16.c for your reference.
    appl_adimap_separate16.c (35.3 KB)
  1. When you’re using the web interface, only the specific ADI element is updated, but when you write to the process data area (this is what you’re doing when you send the request to HR#1120 (see modbus map below)) the whole process data area is uploaded, and all callbacks connected to ADIs mapped to process data are called. When using the WEB interface, it uses a different command (SET_INDEXED_ATTR instead of SET_ATTR). This is why you can update a single element through the WEB interface.

  2. From the previous post-
    I think we still have a misunderstanding regarding the Modbus registers. ADI 73 is located in Modbus register 8720+1056=9776 (not 5088), and ADI 77 is located in Modbus register 8976+1056=10032 (not 5344) that is why he does not get the callbacks when writing to those registers. The whole ADI >>(64*UINT16) must be written in order for the write to be accepted by the CompactCom (and for the callback to be called).
    If you still want to have it mapped to process data (as the mapping is done now), the correct Modbus addresses in the process data area are: FIRST_INSTANCE: 1056, FIRST_INSTANCE+1:1120, FIRST_INSTANCE+63:1184, FIRST_INSTANCE+67:1248, and FIRST_INSTANCE+NUM_HOLDING_REG_ARRAY: 6160.

  3. Here’s the memory map on the Modbus side (taken from the manual)
    image
    In the Read Process Data area and the Write Process Data area, ADIs that are mapped to process data will end up in the order of maping. In you example the mapping is done here:
    image
    Here, all the ADI:s we have talked about so far are mapped (that is the reason all callbacks are called even if only one is updated because the whole process data area is handled at the same time). If you disable all process data mappings you will have no ADI:s mapped to the process data areas.
    image
    Now the ADI:s are only accessible via registers 1010h-FFFFh. But since you have added an offset (+1056) this area is moved to 1430h-FFFFh.

Example:

The FIRST_INSTANCE (ADI #10) is accessed with the Modbus Registers 5744-5807 (1670h-16AFh)

The FIRST_INSTANCE+1 (ADO #11) is accessed with the Modbus Registers 5808-5871 (16B0-16EFh)

The FIRST_INSTANCE+63 (ADI #73) is accessed with the Modbus Registers 9776-9839 (2630h-266Fh)

The FIRST_INSTANCE+67 (ADI #77) is accessed with the Modbus Registers 10032-10095 (2730h-276Fh)

The FIRST_INSTANCE + NUM_HOLDING_REG_ARRAY (ADI #88) is accessed with the Modbus Registers 10736-10799 (29F0h-2A2Fh)

  1. See above how to remove the mapping of ADIs to the process data area.

In the file you sent you have an example how to cyclically handle the data. Here you can access the ADI variables. In my example below I am just looping the first word in appl_aiUint16_1056 to the first word in appl_aiUint16_11.

image

After I remove the mappings of ADIs to the process data area, how can I know there are updates in the holding registers without the callbacks?

Hi Derekyu,

Sorry for the delay on this, I’m just checking this over with some colleagues from Belgium. I’ll try and get you an answer on this before the end of the day