Three things:
- As you're developing your own client and server components, I suggest you to use Modbus only if strictly required or convenient with an eye to openness (i.e. other manufacturers must be able to communicate with your client or server components by means of a standardized protocol - and Modbus fits).
- Be aware that Modbus TCP isn't just Modbus RTU(/ASCII) over TCP/IP (which is still allowed, of course, also UDP would be allowed). There are some important differences to take into account.
- I understand that you need to understand Modbus at a deeper level. At that point, once you have an open serial channel or (listening) TCP socket inside your C program, you may just start with simple Modbus requests/responses.
Take a look at this short but quite complete description, and also at the documentation of this constantly updated library.
Here's a super-simplified RTU example for Linux, based on libmodbus.
Allow me some C99 relaxation for compactness.
In the real world you should also properly handle signals like SIGTERM, etc...
There's also a modbus_rtu_set_serial_mode
(RS232 vs RS485) function for Linux kernels 2.6.28 onwards. You may find other libraries that make working with RS485 easier on your platform.
Master snippet
//Create a new RTU context with proper serial parameters (in this example,
//device name /dev/ttyS0, baud rate 9600, no parity bit, 8 data bits, 1 stop bit)
modbus_t *ctx = modbus_new_rtu("/dev/ttyS0", 9600, 'N', 8, 1);
if (!ctx) {
fprintf(stderr, "Failed to create the context: %s\n", modbus_strerror(errno));
exit(1);
}
if (modbus_connect(ctx) == -1) {
fprintf(stderr, "Unable to connect: %s\n", modbus_strerror(errno));
modbus_free(ctx);
exit(1);
}
//Set the Modbus address of the remote slave (to 3)
modbus_set_slave(ctx, 3);
uint16_t reg[5];// will store read registers values
//Read 5 holding registers starting from address 10
int num = modbus_read_registers(ctx, 10, 5, reg);
if (num != 5) {// number of read registers is not the one expected
fprintf(stderr, "Failed to read: %s\n", modbus_strerror(errno));
}
modbus_close(ctx);
modbus_free(ctx);
Slave snippet
//Prepare a Modbus mapping with 30 holding registers
//(plus no output coil, one input coil and two input registers)
//This will also automatically set the value of each register to 0
modbus_mapping_t *mapping = modbus_mapping_new(0, 1, 30, 2);
if (!mapping) {
fprintf(stderr, "Failed to allocate the mapping: %s\n", modbus_strerror(errno));
exit(1);
}
//Example: set register 12 to integer value 623
mapping->tab_registers[12] = 623;
modbus_t *ctx = modbus_new_rtu("/dev/ttyS0", 9600, 'N', 8, 1);
if (!ctx) {
fprintf(stderr, "Failed to create the context: %s\n", modbus_strerror(errno));
exit(1);
}
//Set the Modbus address of this slave (to 3)
modbus_set_slave(ctx, 3);
if (modbus_connect(ctx) == -1) {
fprintf(stderr, "Unable to connect: %s\n", modbus_strerror(errno));
modbus_free(ctx);
exit(1);
}
uint8_t req[MODBUS_RTU_MAX_ADU_LENGTH];// request buffer
int len;// length of the request/response
while(1) {
len = modbus_receive(ctx, req);
if (len == -1) break;
len = modbus_reply(ctx, req, len, mapping);
if (len == -1) break;
}
printf("Exit the loop: %s\n", modbus_strerror(errno));
modbus_mapping_free(mapping);
modbus_close(ctx);
modbus_free(ctx);