implementing freemodbus
Overview
I'm going to use MODBUS TCP to communicate with the historian. By linking other libraries I could also have used BACnet, some web friendly protocol, written the data out to a flat file or, injected it directly into a MySQL table. Since the other devices interact with the historian using either MODBUS or BACnet, it's better to be consistent and use one of those.
MODBUS supports only bit, integer and real data types. BACnet is far more robust at the cost of a more involved implementation. The few times it would be handy to present redundant string data (wind direction, local time zone, forecasts from the console, etc.) don't warrant the additional complexity.
FreeMODBUS is a light weight library that implements the MODBUS protocol and supports master or slave, ASCII, RTU and TCP.
Configuration
For this application I need to implement a slave (or server) over TCP. In mbconfig.h, I need to set three flags before I try to compile and link the library.
/*! \brief If Modbus ASCII support is enabled. */ #define MB_ASCII_ENABLED ( 0 ) /*! \brief If Modbus RTU support is enabled. */ #define MB_RTU_ENABLED ( 0 ) /*! \brief If Modbus TCP support is enabled. */ #define MB_TCP_ENABLED ( 1 )
Conveniently, the demo.c file in the LINUXTCP directory is almost exactly what I need for this application so I'll start there and just adapt it for this use. The library and the code is well documented so I won't duplicate those efforts here.
The data stream from the Davis station contains bit, integer and float data. While I could map those out to the appropriate MODBUS register types, I'm going to simply map everything to holding registers starting at address 40001 as is commonly done. This approach is a little less bandwidth efficient but since we're using TCP, the difference isn't worth the trouble.
#define REG_HOLDING_START 40001 #define REG_HOLDING_NREGS 300
The handler for the holding registers from the demo application is fine as it is so all I need to do now is unpack the data from the Davis LOOP packet and map it into the usRegHoldingBuf[] array. To make that job a little easier I'll define two macros to handle the real number format from the Davis protocol and a function to format a floating point value for MODBUS.
//
// Adjust buffer offset and pack a double byte to a word
//
#define DBTOW(x) (((ptr[x+2] << 8) & 0xff00) | (ptr[x+1] & 0x00ff))
//
// Adjust buffer offset and return a single byte
//
#define SBTOB(x) (ptr[x+1])
//
// Pack a floating point value into MODBUS big endian format (int32)
//
int vp2_pack_float(float real, uint16_t *dest) {
uint32_t i = 0;
memcpy(&i, &real, sizeof (i));
dest[0] = (uint16_t)(i >> 16);
dest[1] = (uint16_t) i;
return (2);
}




