How to Read M103 Modbus Doc?
-
Hi all,
ok, I manged to use the relays through modbus with libmodbus with my C-code. Now I am trying to use the DI/DO/AO/AI interfaces.
I see the Neuron Registers Map but I am only able to understand it partially...
I am aware the function modbus_read_bits() can be usedd to read the DIs. So I open a modbus_tcp connection with the address of DI2.1 for example:
modbus_read_bits(modbus_handle, 108, nb, *dest);
*dest is supposed to be uint16_t.
I was able to read the above- finally I have some data in *dest. But how do I have to read the data there? I expected something like "bit 1 tells if the DI is currently ON or OFF and bit2 is the counter while writing bit3 will reset the counter" or stuff like this.
So I am a little bit lost here to interpret the values I got.
Nearly the same is for writing. What values should I put into uint16_t to enable the DI?
And how can I configure the AO?
Thanks a lot for hints!
/KNEBB
-
@knebb said in How to Read M103 Modbus Doc?:
*dest is supposed to be uint16_t.
No, it is an uint8_t array. Every element of the array contains the value of a single digital channel, set to
TRUE
(that is, IIRC,1
) orFALSE
(that is surely0
).Here is my untested snippet:
// Reading one channel uint8_t my_digital; modbus_read_bits(bus, 108, 1, &my_digital); if (my_digital) { ... } // Reading multiple channels uint8_t my_digitals[2]; modbus_read_bits(bus, 108, 2, my_digitals); if (my_digitals[0]) { ... }
-
@ntd :Thanks a lot four your input, I really appreciate it.
I am just wondering as there seems to be no difference between uint8_t, uint16_t and uint32_t.
See my question here.
When using uint8_t the result is exactly the same. All seem to be 32bit long...So how does modbus deal with this? It writes only the least significant 8 bits? Others are set to zero?
/KNEBB
-
@knebb TL/DR: just use
uint8_t
. This is the right thing to do.There is no differences (because of integer promotion) only when you use that variable in an expression. Incidentally, and for different reasons [1], reading a single bit will work with whatever 0-initialized integer you throw at
modbus_read_bits
, although conceptually wrong. But I can assure you that, when reading multiple channels, using anything butuint8_t
will give you wrong results.[1] Raspberry Pi is little-endian. Setting the first byte of any 0-initialized
uint*
variable to a specific value (e.g.,TRUE
), makes that very same variable initialized to that value, regardless of its type.Here is an example that hopefully will shed some light:
#include <assert.h> #include <stdint.h> #include <string.h> #define TRUE 12 int main() { // Using an union to be able to set the first byte of everything // with only one instruction (m.byte = ...) union { uint8_t byte; uint16_t word; uint32_t dword; uint64_t qword; } m; // Let's set the first byte of 0 initialized memory memset(&m, 0, sizeof m); m.byte = TRUE; assert(m.byte == TRUE); // Ok assert(m.word == TRUE); // Ok on little-endian machines assert(m.dword == TRUE); // Ok on little-endian machines assert(m.qword == TRUE); // Ok on little-endian machines // Now let's try with random initialized memory memset(&m, 3, sizeof m); m.byte = TRUE; assert(m.byte == TRUE); // Ok assert(m.word == TRUE); // Error! assert(m.dword == TRUE); // Error! assert(m.qword == TRUE); // Error! return 0; }
-
Thanks for the explanation; I figured it already out Raspbian is littleEndian.
Just to make sure:
To read i.e. the counter for digital input 2.1 (address 103-104 with DWORD, 32bit in total) I would use:uint8_t counter[4]; modbus_read_bits(bus, 103, 4, counter);
Or this one?
uint8_t counter1[2],counter2[2]; modbus_read_bits(bus, 103, 2, counter1); modbus_read_bits(bus, 104, 2, counter2);
Would both work?
Sorry for my dumb question, I am really new to modbus...
/KNEBB
-
@knebb said:
Would both work?
None of them will. You should really read a ModBUS introduction... I think the wikipedia page should suffice.
The counter is an input register, not a bit, so:
union { uint16_t word[2]; uint32_t dword; } counter; if (modbus_read_registers(bus, 103, 2, counter.word) != 2) { printf("Error\n"); } else { printf("DI2.1 counter is %u\n", counter.dword); }
Not sure which is the less and the most significant word, so if the above gives wrong results just swap
counter.word[0]
andcounter.word[1]
. -
@ntd
Perfect!Got it finally. I mixed up modbus_read_bits and modbus_read_register. Now working like a charm!
Thanks a lot!
/KNEBB
-
@all
As I was struggling again with this I figured it out now finally (at least I hope so).
My issue was the interpretation of whatever value I got.
So for alle future readings:
In the linked Calc-Sheet above you have notes about the registers (read only registers, not bits!).
So for the M103 you have the "Registers - group 1" and "Registers - group 2". Use these. The coils tables are somehow misleading (at least for me).
Additionally be aware
modbus_read_registers()
need to write the data into anuint16_t
variable. With the help of @ntd and his suggestes "union" I was easy to "convert" them into the uint32_t. Unions access the same memory by different variables. See more here.In the code sniplet above
modbus_read_registers()
writes the content into the counter.word memory location and the prinft reads them as uint32_t. Easy going now to re-calculate the counter.dword variable to get the values you need.So to read the current settings and values of all relays and digital inputs I use the following code:
uint16_t state; union { uint16_t word[2]; uint32_t dword; } counter; /* Relais */ modbus_read_registers(modbusglob,101,1,&state); printf("Lesen Relay Register: %u\n",state); printf("\n"); /* DigitalIN 1 */ modbus_read_registers(modbusglob,0,1,&state); printf("Lesen DigIn1: %u\n",state); printf("\n"); /* Digital-In 1 Counter */ for (i=8;i<16;i=i+2) { modbus_read_registers(modbusglob,i,2,counter.word); printf("Counter %d DigIn1: %u\n",i,counter.dword); } printf("\n"); /* DigitalIN 2 */ modbus_read_registers(modbusglob,100,1,&state); printf("Lesen DigIn1: %d\n",state); printf("\n"); /* DigitalIN 2 Counter */ for (i=103;i<118;i=i+2) { modbus_read_registers(modbusglob,i,2,counter.word); printf("Counter %d DigIn2: %u\n",i,counter.dword); }
This works now perfectly.
/KNEBB
-
Just as an update.
I was struggling with multiple registers which are used i.e. for the AO1.1 port of my Neuron M103.
So for the AO registers 3000 and 3001 are given as "real".
I extended the above
union
as follows:union { uint16_t word[2]; uint32_t dword; float fl; } counter;
And I am reading the register like this:
modbus_read_registers(modbusglob, 3000, 2, &counter.word[0]);
It reads 2 registers starting at 3000 and writes the result in the word array where it is automatically converted to "float" by the union structure.
Works fine!
/KNEBB