// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + // + FileName: ModBus.cpp Author: S.Hammond // + // + Version: 1 Date: 24/June/1999 // + // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + // + This file contains the code to handle the ModBus transactions and // + code to handle the CAL instrument specific ModBus operations. // + It contains the following user access functions: // + // + CModBus(); // + This function is called when the ModBus class is created and // + resets the pMessageHeader and pResponseHeader pointers to point to // + the command and response buffers respectively. These pointers are used // + to access the data in the command and response message buffers. // + // + ~CModBus(); // + This function is called when the ModBus class is destroyed. // + // + ModReadSingle(BYTE Slave,WORD Addr) // + This function is used to read a single register from the // + specified ModBus slave address and specified ModBus register address. // + The function does not return a value and the ValidResponse() function // + is used to check if the ModBus transaction completed without error. // + If a valid response was obtained then the data in the resposne can be // + accessed using the GetByte() and GetWord() functions. // + // + ModWriteSingle(BYTE Slave,WORD Addr, WORD Data) // + This function is used to write a single register to the // + specified ModBus slave address and specified ModBus register address // + using the specified data. The function does not return a value and // + the ValidResponse() function is used to check if the ModBus transaction // + completed without error. If a valid response was obtained then the data // + in the response can be accessed using the GetByte() and GetWord() // + functions. // + // + ModEnterProgram(BYTE bSlave) // + This function is used to issue the command sequence to a CAL // + instrument to enter program mode. The function will make 10 attempts // + to enter program mode before returning false for an error. // + // + ModExitProgram(BYTE bSlave) // + This function is used to issue the command sequence to a CAL // + instrument to exit program mode. The function will make 10 attempts // + to exit program mode before returning false for an error. // + // + ValidResponse(void) // + This function is used to check if a valid response was // + received to a ModBus command sent to an instrument. This function // + checks the number of bytes received to check that a valid ModBus // + message is in the response buffer. // + // + GetByte(int Index) // + This function is used to fetch a byte from the response // + buffer from the last ModBus response message. The index into the // + message is zero based and starts at the first byte if the message. // + // + GetWord(int Index) // + This function is used to fetch a word from the response // + buffer from the last ModBus response message. The index into the // + message is zero based and starts at the first byte if the message. // + Note that the function returns a word byte the index into the response // + buffers is a byte index. // + // + QueryDevice(BYTE Slave,char *Model) // + This function is used to fetch the CAL instrument model number // + and convert it into a text description of the instrument. // + // + The file also contains the following internal access functions: // + // + StartMessage(void) // + This function is used to reset the command message buffer st // + the start of a new message. // + // + AddByte(BYTE bData) // + This function is used to add a byte to the end of the current // + command message buffer. // + // + AddWord(WORD wData) // + This function is used to add a word to the end of the current // + command message buffer. // + // + AddWordLSBFirst(WORD wData) // + This function is used to add a word to the end of the current // + command message buffer in least significant byte first order. // + // + GenCRC(void) // + This function is used to generate a CRC value on the message // + in the command buffer. // + // + EndMessage(void) // + This function is used to calculate the CRC on the message in // + command buffer and increase the message size to include the newly // + calculated CRC value. // + // + WriteMessage(void) // + This function is used to write the message in the command // + buffer out to the currently open communications channel. // + // + ReadMessage(void) // + This function is used to read a message from the currently // + open communications channel. Once received the message is checked // + to ensure it has the correct slave address and CRC value. // + // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #include "stdafx.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #define _MODBUS_C_ #include "CalPoll.h" #include "Comms.h" #include "ModBus.h" #include "CommsDevice.h" // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ CModBus::CModBus() { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Setup the pointers to the message buffers for later use // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ pMessageHeader=(PMODBUSHEADER) &MessageBuffer; pResponseHeader=(PMODBUSHEADER) &ResponseBuffer; } CModBus::~CModBus() { } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Reset the message buffer for a new message // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void CModBus::StartMessage(void) { BufferLength=0; } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void CModBus::EndMessage(void) { GenCRC(); } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Add a byte to the current message buffer // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void CModBus::AddByte(BYTE bData) { MessageBuffer[BufferLength]=bData; BufferLength++; } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Add a word to the current message buffer // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void CModBus::AddWord(WORD wData) { MessageBuffer[BufferLength]=(BYTE)(wData>>8); BufferLength++; MessageBuffer[BufferLength]=(BYTE)(wData&0x0ff); BufferLength++; } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Add a word to the current message buffer // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void CModBus::AddWordLSBFirst(WORD wData) { MessageBuffer[BufferLength]=(BYTE)(wData&0x0ff); BufferLength++; MessageBuffer[BufferLength]=(BYTE)(wData>>8); BufferLength++; } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Generate the CRC for the given message // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void CModBus::GenCRC(void) { int f; int i; WORD CRCValue; // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Reset the CRC value // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ CRCValue=0xffff; // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Loop for all bytes in message // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ for (f=0;f>1)^0xa001); } else { CRCValue=(WORD)(CRCValue>>1); } } } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Save the CRC value at the end of the message // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ AddWordLSBFirst(CRCValue); } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Write a completed message to the communications device // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void CModBus::WriteMessage(void) { DWORD dwWritten; // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Send the message out // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ if ( !CommsDevice.WriteDevice(MessageBuffer,BufferLength,&dwWritten) ) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + The communications write has failed // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ AfxMessageBox("Unable to write to communications device"); CommsDevice.CloseComms(); } } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Read a message from the communications device // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void CModBus::ReadMessage(void) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Fetch a message from the communications device // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ResponseLength=0; if ( !CommsDevice.ReadDevice(ResponseBuffer,(DWORD *)&ResponseLength) ) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + The communications read has failed // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ AfxMessageBox("Unable to read from communications device"); CommsDevice.CloseComms(); return; } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Check response is valid // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ if ( !ValidResponse() ) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Invalid response, problem already reported to user // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ return; } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Check message is from correct slave device // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ if ( pMessageHeader->SlaveAddr!=pResponseHeader->SlaveAddr ) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Discard messages from the wrong slave address // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ AfxMessageBox("Received message from the wrong slave address"); ResponseLength=0; return; } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Check message CRC is correct // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ memcpy(MessageBuffer,ResponseBuffer,ResponseLength); BufferLength=ResponseLength-2; GenCRC(); if ( memcmp(MessageBuffer,ResponseBuffer,ResponseLength)!=0 ) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + The CRC in the reply message does not match the generated // + CRC // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ AfxMessageBox("Received message has an incorrect CRC value"); ResponseLength=0; return; } } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Send a write single command out // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void CModBus::ModWriteSingle(BYTE Slave,WORD Addr, WORD Data) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Construct the message // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ StartMessage(); AddByte(Slave); AddByte(MODBUS_WRITE_SINGLE); AddWord(Addr); AddWord(Data); EndMessage(); // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Send the message to the communications device // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ WriteMessage(); // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Read from the COM device // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ReadMessage(); } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Send a Read Single Holding Register Command out // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void CModBus::ModReadSingle(BYTE Slave,WORD Addr) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Construct the message // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ StartMessage(); AddByte(Slave); AddByte(MODBUS_READ_SINGLE); AddWord(Addr); AddWord(1); // Register count EndMessage(); // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Send the message to the communications device // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ WriteMessage(); // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Read from the COM device // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ReadMessage(); } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Attempt to place the instrument into program mode // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ BOOL CModBus::ModEnterProgram(BYTE bSlave) { int Attempts; for (Attempts=0;Attempts<10;Attempts++) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Send the security byte for enter program mode first // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ModWriteSingle(bSlave,MODBUS_CAL_SECURITY,MODBUS_CAL_ENTERBYTE); if ( ValidResponse() ) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + The response was valid but was it an exact match ? // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ if ( memcmp(MessageBuffer,ResponseBuffer,ResponseLength)==0 ) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + The security byte was written without error, // + So write the enter program mode command // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ModWriteSingle(bSlave,MODBUS_CAL_ENTERPROG,0); if ( ValidResponse() ) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Again the response must be identical // +++++++++++++++++++++++++++++++++++++++++++++++++++++++ if ( memcmp(MessageBuffer,ResponseBuffer, ResponseLength)==0 ) { // +++++++++++++++++++++++++++++++++++++++++++++++++++ // + We have managed to enter program mode // +++++++++++++++++++++++++++++++++++++++++++++++++++ return(TRUE); } } } } } return(FALSE); } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Attempt to remove the instrument from program mode // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ BOOL CModBus::ModExitProgram(BYTE bSlave) { int Attempts; for (Attempts=0;Attempts<10;Attempts++) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Send the security byte for exit program mode first // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ModWriteSingle(bSlave,MODBUS_CAL_SECURITY,MODBUS_CAL_EXITBYTE); if ( ValidResponse() ) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + The response was valid but was it an exact match ? // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ if ( memcmp(MessageBuffer,ResponseBuffer,ResponseLength)==0 ) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + The security byte was written without error, // + So write the enter program mode command // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ModWriteSingle(bSlave,MODBUS_CAL_EXITPROG,0); if ( ValidResponse() ) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Again the response must be identical // +++++++++++++++++++++++++++++++++++++++++++++++++++++++ if ( memcmp(MessageBuffer,ResponseBuffer, ResponseLength)==0 ) { // +++++++++++++++++++++++++++++++++++++++++++++++++++ // + We have managed to exit program mode // +++++++++++++++++++++++++++++++++++++++++++++++++++ return(TRUE); } } } } } return(FALSE); } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Was their a valid response ? // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ BOOL CModBus::ValidResponse(void) { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + If response length has been set to -1 then we have already // + reported the error and can just return FALSE // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ if ( ResponseLength==-1 ) { return(FALSE); } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + If response length is zero then no reply was received // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ if ( ResponseLength==0 ) { ResponseLength=-1; return(FALSE); } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Check message is long enough to check further // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ if ( ResponseLengthbData[Index-2]); } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Return the requested word from the last response message // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ WORD CModBus::GetWord(int Index) { WORD wTemp; wTemp=(WORD) (pResponseHeader->bData[Index-2]<<8); wTemp=(WORD)(wTemp + pResponseHeader->bData[Index-1]); return(wTemp); } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Get model information from a slave device and add to list // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void CModBus::QueryDevice(BYTE Slave,char *Model) { char TempString[256]; // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Default the string to an error message // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sprintf(Model,"Address: 0x%02x Unable to get model type", Slave); // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Fill in the start of the string // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sprintf(TempString,"Address: 0x%02x ",Slave); // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + Extract the model type register // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ModReadSingle(Slave,MODBUS_CAL_MODEL); if ( !ValidResponse() ) { return; } switch( GetByte(3) ) { case 0x01: strcat(TempString,"CAL 33/9300 RLY/SSD"); break; case 0x02: strcat(TempString,"CAL 33/9300 SSD/SSD"); break; case 0x03: strcat(TempString,"CAL 33/9300 RLY/RLY"); break; case 0x07: strcat(TempString,"CAL 9400 RLY/SSD"); break; case 0x08: strcat(TempString,"CAL 9400 SSD/SSD"); break; case 0x09: strcat(TempString,"CAL 9400 RLY/RLY"); break; case 0x0a: strcat(TempString,"CAL 9500 RLY/SSD"); break; case 0x0b: strcat(TempString,"CAL 9500 SSD/SSD"); break; case 0x0c: strcat(TempString,"CAL 9500 RLY/RLY"); break; case 0x0d: strcat(TempString,"CAL 9500 ANLG/RLY"); break; case 0x0e: strcat(TempString,"CAL 9500 ANLG/SSD"); break; default: sprintf(TempString,"%sUnknown Model {%u}", TempString,GetByte(4)); break; } sprintf(Model,"%s Version: %02x",TempString,GetByte(4)); }