CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to support@ccsinfo.com

Basic MODBUS example
Goto page 1, 2  Next
 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
Neutone



Joined: 08 Sep 2003
Posts: 839
Location: Houston

View user's profile Send private message

Basic MODBUS example
PostPosted: Thu Feb 19, 2004 8:47 am     Reply with quote

The example only supports function code 3 and 6. The address range is limited to 128 16-bit words stored in the array REG_Map or Registry_Map that occupy the same memory space.

To use this simply call COMM1_Service() frequently to service packets that have arrived and start a reply. Everything else is interupt based. I have been running with a 20Mhz clock and CRC generation verification takes ~30uS per byte. Thats around 150 instructions per byte. I know that a lookup table would be better but have not had time to look into getting one to work.



Comments and suggestions are welcome.
Neutone



Joined: 08 Sep 2003
Posts: 839
Location: Houston

View user's profile Send private message

PostPosted: Thu Feb 19, 2004 8:48 am     Reply with quote

Code:

Int16 Registry_Map[128];
#locate Registry_Map = 0x100
Int8  REG_Map[256];
#locate REG_Map = Registry_Map

// MODBUS exception codes
#define Illegal_Function     1
#define Illegal_Data_Address 2
#define Illegal_Data_Value   3
#define Slave_Device_Failure 4
#define Acknowledge          5
#define slave_device_Busy    6
#define Negative_Acknoledge  7
#define Memory_Parity_Error  8

#use rs232(baud=19200,xmit=pin_c6,rcv=pin_c7,errors,stream=COMM_1)

struct Comm_Port
{  Int1  Respond;
   Int1  Initialized;
   Int1  Packet;
   Int8  Network_Address;
   Int8  CRClo_Index;
   Int8  CRChi_Index;
   Int8  Index;
   Int8  Start;
   Int8  Finish;
   Int8  Quantity;
   Int8  Baud;
   Int8  Parity;
   Int8  Buffer[256];
};
struct Comm_Port COMM1;
#locate COMM1 = 0x0200

Int16 crcdata,crcacc;
int8 x,y;
#byte crclo=crcacc
#byte crchi=crcacc+1

/***********************************************************
*    COMM1 Receive Complete timer Interrupt                *
***********************************************************/
#int_TIMER1
TIMER1_isr()
{  disable_interrupts(int_TIMER1);                          // TMR1 Overflow Interrupt Enable bit off
   Comm1.Index--;                                           // Index last byte recieved
   Comm1.CRChi_Index = Comm1.Index;                         // Index last byte recieved
   Comm1.Packet=1;                                          // Tag packet for processing
   ++Registry_Map[0];                                       // Recieved packet count (for debugging)
}
/***********************************************************
*    COMM1 Receive Interrupt                               *
***********************************************************/
#int_RDA                                                    //**************
RDA_isr()                                                   // BYTE RECIEVED
{  x = fgetC(COMM_1);                                       // Get incomming byte from buffer
   if(!Comm1.Packet)                                        // Don't recieved while working on a packet or transmitting
   {  Comm1.Buffer[Comm1.Index] = x;                        // Place incomming byte in PacketBuffer
      Comm1.Index++;                                        // Place incomming byte in PacketBuffer
      set_timer1(63424);                                    // Wait 1.5 byte periods then interupt (set for 9600bps now)
      clear_interrupt(int_TIMER1);                          // Clear timer1 overflow Interrupt Flag bit
      enable_interrupts(int_TIMER1);                        // TMR1 Overflow Interrupt Enable bit on
   }
}
/***********************************************************
*    COMM1 Transmit Interrupt                              *
***********************************************************/
#int_TBE                                                    //**************
TBE_isr()                                                   // BYTE TRANSIMITED
{  if(Comm1.Index <= Comm1.CRChi_Index)                     // Transmit until the entire packet has been sent
   {  x = Comm1.Buffer[Comm1.Index];                        // Store the byte to be sent in a directly addressable location
      fputC(x,COMM_1);                                      // Start the byte transmition
      Comm1.Index++;                                        // Index the next byte to be sent
   }
   else
   {  disable_interrupts(INT_TBE);                          // Stop transmittion
      Comm1.Index = 0;                                      // Index for first byte to be recieved
   }
}
/***********************************************************
*    COMM1 Main Service                                    *
***********************************************************/
#inline
void COMM1_Service(void)
{  if(!COMM1.Initialized)
   {  setup_timer_1(T1_INTERNAL|T1_DIV_BY_1);               // Timer used by Network Ch A
      enable_interrupts(INT_RDA);                           // Enable recieved bytes interupt
      enable_interrupts(global);                            // Enable recieved bytes interupt
      Comm1.Index = 0;                                      // Index for first byte to be recieved
      Comm1.Packet = 0;                                     // Allow new packet reception to begin
      COMM1.Initialized=1;
      COMM1.Network_Address=1;
   }
   if(Comm1.Packet)                                         // Packet ready to process
   {  /****************************************************/
      /*     Mean time to decode CRC ~ 30uS per byte      */
      /****************************************************/
      crcacc = 0xFFFF;                                      // Prepair to generate CRC
      COMM1.CRClo_Index=Comm1.CRChi_Index-1;                // Solve this index once instead of once per byte
      Comm1.Index = 0;                                      // Start at begining of packet
      while(Comm1.Index < COMM1.CRClo_Index)                // Use all bytes prior to CRC
      {  crcdata = Comm1.Buffer[Comm1.Index];               // Use direct addressing on copy of data value
         crcacc=crcacc^crcdata;
         for(x=8;x>0;x--)                                   // Process the data bits
         {  if(bit_test(crcacc,0))                          // Should XOR be performed
            {  crcacc = crcacc >> 1;                        // Shift CRC 1 bit right load high with 0
               crcacc = crcacc^0xA001;                      // XOR with A001
            }
            else                                            // No XOR performed
            {  crcacc = crcacc >> 1;                        // Shift CRC 1 bit right load high with 0
            }
         }                                                  // Processed all bits of byte
         Comm1.Index++;                                     // Advance index to the next byte
      }
      if(crclo != Comm1.Buffer[COMM1.CRClo_Index])          // Detect Bad CRClo
      {  Comm1.Packet = 0;                                  // Allow new packet reception to begin
      }
      if(crchi != Comm1.Buffer[Comm1.CRChi_Index])          // Detect Bad CRChi
      {  Comm1.Packet = 0;                                  // Allow new packet reception to begin
      }
      Comm1.Index = 0;                                      // Zero index for recieving
      if(Comm1.Buffer[0]==Comm1.Network_Address)            // This device is directly addressed
      {  Comm1.Respond=1;                                   // This packet must be replied to
      }
      else
      {  Comm1.Respond=0;                                   // This packet must not be replied to
         if(Comm1.Buffer[0]=!0)                             // This is not a network brodcast on address 0
         {  if(Comm1.Buffer[0]=!255)                        // This is not a network brodcast on address 255
            {  Comm1.Packet = 0;                            // Allow new packet reception to begin
            }
         }
      }
      if(Comm1.Packet)                                      // Decode by modbus functions
      {  Comm1.CRChi_Index=0;                               // Prepair to test for invalid functions
         if(Comm1.Buffer[1] == 3)
         {  Comm1.start=Comm1.Buffer[3];                    // Starting register address
            Comm1.Quantity=Comm1.Buffer[5];                 // Quantity of registers to read
            if(Comm1.Buffer[2])  Comm1.Start=256;           // Invalid Address Range
            if(Comm1.Buffer[4])  Comm1.Start=256;           // Invalid Address Range
            Comm1.finish=Comm1.start+Comm1.quantity;        // Ending register address
            Comm1.Buffer[2]=Comm1.Quantity<<1;              // Data Byte Count
            if(Comm1.finish<=128)                           // Access to first 128 words
            {  Comm1.Index=3;                               // Set the index to the first byte
               for(y=Comm1.start;y<Comm1.finish;y++)
               {  x=(y*2)+1;
                  Comm1.Buffer[Comm1.Index] = REG_Map[x];
                  Comm1.Index++;
                  --x;
                  Comm1.Buffer[Comm1.Index] = REG_Map[x];
                  Comm1.Index++;
               }
               Comm1.CRChi_Index=Comm1.Index+1;
            }
            else
            {  bit_set(Comm1.Buffer[1],7);                  // Report_Exception
               Comm1.Buffer[2]=Illegal_Data_Address;        // Report_Exception type
               Comm1.CRChi_Index=4;                         // Index CRChi
            }
         }
         if(Comm1.Buffer[1] == 6)
         {  Comm1.start=Comm1.Buffer[3];                    // Starting register address
            if(Comm1.Buffer[2])  Comm1.Start=256;           // Invalid Address Range
            if(Comm1.start<=127)
            {  Comm1.Index=Comm1.start*2;
               REG_Map[Comm1.Index]=Comm1.Buffer[5];
               Comm1.Index++;
               REG_Map[Comm1.Index]=Comm1.Buffer[4];
               Comm1.CRChi_Index=7;                         // Index CRChi
            }
            else
            {  bit_set(Comm1.Buffer[1],7);                  // Report_Exception
               Comm1.Buffer[2]=Illegal_Data_Address;        // Report_Exception type
               Comm1.CRChi_Index=4;                         // Index CRChi
            }
         }
         if(Comm1.CRChi_Index == 0)
         {  bit_set(Comm1.Buffer[1],7);                     // Report_Exception
            Comm1.Buffer[2]=Illegal_Function;               // Report_Exception type
            Comm1.CRChi_Index=4;                            // Index CRChi
         }
         if(!Comm1.Respond)
         {  Comm1.Packet = 0;                               // Allow new packet reception to begin
            Comm1.Index = 0;                                // Start at begining of packet
         }
      }
      if(Comm1.Respond)                                     // Add CRC to outgoing data
      {  crcacc = 0xFFFF;                                   // Prepair to generate CRC
         COMM1.CRClo_Index=Comm1.CRChi_Index-1;             // Solve this index once instead of once per byte
         Comm1.Index = 0;                                   // Start at begining of packet
         while(Comm1.Index < COMM1.CRClo_Index)             // Use all bytes prior to CRC
         {  crcdata = Comm1.Buffer[Comm1.Index];            // Use direct addressing on copy of data value
            crcacc=crcacc^crcdata;
            for(x=8;x>0;x--)                                // Process the data bits
            {  if(bit_test(crcacc,0))                       // Should XOR be performed
               {  crcacc = crcacc >> 1;                     // Shift CRC 1 bit right load high with 0
                  crcacc = crcacc^0xA001;                   // XOR with A001
               }
               else                                         // No XOR performed
               {  crcacc = crcacc >> 1;                     // Shift CRC 1 bit right load high with 0
               }
            }                                               // Processed all bits of byte
            Comm1.Index++;                                  // Advance index to the next byte
         }
         Comm1.Index = 0;                                   // Zero index for Transmition
         Comm1.Buffer[COMM1.CRClo_Index]=crclo;             // Place CRClo within packet
         Comm1.Buffer[Comm1.CRChi_Index]=crchi;             // Place CRChi within packet
         enable_interrupts(INT_TBE);                        // Kick Off Xmit of Data
         ++Registry_Map[1];                                 // Count responce packets (for debugging)
      }
      Comm1.Packet = 0;                                     // Allow new packet reception to begin
   }
}
yerpa



Joined: 19 Feb 2004
Posts: 58
Location: Wisconsin

View user's profile Send private message Visit poster's website

Re: MODBUS interface
PostPosted: Thu Feb 19, 2004 3:45 pm     Reply with quote

This looks interesting. Is it Modbus ASCII or Modbus RTU?
Neutone



Joined: 08 Sep 2003
Posts: 839
Location: Houston

View user's profile Send private message

PostPosted: Thu Feb 19, 2004 5:04 pm     Reply with quote

It's RTU, it would have to be modified slightly for ASCII.
Guest








PostPosted: Fri Feb 20, 2004 11:54 am     Reply with quote

Hi Neutone,

I'm working on a Modbus slave device also. I suggest you to use this CRC16 code for table lookup. Performance is great.

Code:

long int CRC16(char *message, int length)
// Calcula o CRC16 de uma mensagem por table lookup
{
   char CRC_Hi = 0xFF;            // carrega Hi byte inicialmente
   char CRC_Lo = 0xFF;         // carrega Lo byte inicialmente
   long int CRC;            // temporario de CRC16
   int index=0;               // indexador para busca nas tabelas
   while (length--)
   {
      index = CRC_Hi ^ *message++;         // calcula o CRC
      CRC_Hi = CRC_Lo ^ Table_CRC_Hi[index];
      CRC_Lo = Table_CRC_Lo[index];
   }
   CRC = CRC_Hi;                  // formata e retorna o CRC
   CRC = (CRC<<8) | CRC_Lo;
   return CRC;
}


The tables are here:
Code:

/* Table of CRC values for high–order byte */
const char Table_CRC_Hi[256] = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
0x40
} ;

/* Table of CRC values for low–order byte */
const char Table_CRC_Lo[256] = {
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
0x40
};


I had a difficult time until I got this routine, my application is very time demanding.

Marcus[/code]
mvaraujo



Joined: 20 Feb 2004
Posts: 59
Location: Brazil

View user's profile Send private message

PostPosted: Fri Feb 20, 2004 11:58 am     Reply with quote

My username appeared as Guest... Don't know why... it was mvaraujo writting... sorry!
Neutone



Joined: 08 Sep 2003
Posts: 839
Location: Houston

View user's profile Send private message

Thanks for posting.
PostPosted: Fri Feb 20, 2004 3:53 pm     Reply with quote

I'll see about intergrating that and then post my results.
Neutone



Joined: 08 Sep 2003
Posts: 839
Location: Houston

View user's profile Send private message

New and Improved
PostPosted: Sat Feb 21, 2004 4:59 pm     Reply with quote

I'm not sure yet how much faster the lookup table is but it does work. The bit shift and XOR was running ~150 instructions per byte. I think there may be a better way to store and or access the table. I pulled the buffer out of the structure because it was generating extra code when indirectly addressed.
Code:

// MODBUS exception codes
#define Illegal_Function     1
#define Illegal_Data_Address 2
#define Illegal_Data_Value   3
#define Slave_Device_Failure 4
#define Acknowledge          5
#define slave_device_Busy    6
#define Negative_Acknoledge  7
#define Memory_Parity_Error  8

/* Table of CRC values for high order byte */
const char Table_CRC_Hi[256] = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
0x40
} ;

/* Table of CRC values for low order byte */
const char Table_CRC_Lo[256] = {
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
0x40
};

#use rs232(baud=19200,xmit=pin_c6,rcv=pin_c7,errors,stream=COMM_1)

struct Comm_Port
{  Int8  Network_Address;
   Int8  CRClo_Index;
   Int8  CRChi_Index;
   int8  CRC_Lo;
   int8  CRC_Hi;
   Int8  Index;
   Int8  Table_Index;
   Int8  Start;
   Int8  x;
   Int8  y;
   Int8  Finish;
   Int8  Quantity;
   Int8  Baud;
   Int8  Parity;
   Int1  Respond;
   Int1  Initialized;
   Int1  Packet;
};
struct Comm_Port COMM1;

Int8  COMM1_Buffer[256];                                    // Accessing an array that is part of a structure is
#locate COMM1_Buffer = 0x0100                               // Not handled in an efficient manner by the compiler

Int16 Registry_Map[128];
#locate Registry_Map = 0x200
Int8  REG_Map[256];
#locate REG_Map = Registry_Map

Int16 Recieved_Packets;                                     // Accessed in holding register 400001 via MODBUS
#locate Recieved_Packets = Registry_Map

Int16 Reply_Packets_This_Second;                            // Accessed in holding register 400002 via MODBUS
#locate Reply_Packets_This_Second = Registry_Map + 2

/***********************************************************
*    COMM1 Receive Complete timer Interrupt                *
***********************************************************/
#int_TIMER1
TIMER1_isr()
{  disable_interrupts(int_TIMER1);                          // TMR1 Overflow Interrupt Enable bit off
   Comm1.Index--;                                           // Index last byte recieved
   Comm1.CRChi_Index = Comm1.Index;                         // Index last byte recieved
   Comm1.Packet=1;                                          // Tag packet for processing
   ++Recieved_Packets;                                      // Recieved packet count (for debugging)
}
/***********************************************************
*    COMM1 Receive Interrupt                               *
***********************************************************/
#int_RDA                                                    //**************
RDA_isr()                                                   // BYTE RECIEVED
{  Comm1.x = fgetC(COMM_1);                                 // Get incomming byte from buffer
   if(!Comm1.Packet)                                        // Don't recieved while working on a packet or transmitting
   {  COMM1_Buffer[Comm1.Index] = Comm1.x;                  // Place incomming byte in PacketBuffer
      Comm1.Index++;                                        // Place incomming byte in PacketBuffer
      set_timer1(63424);                                    // Wait 1.5 byte periods then interupt (set for 9600bps now)
      clear_interrupt(int_TIMER1);                          // Clear timer1 overflow Interrupt Flag bit
      enable_interrupts(int_TIMER1);                        // TMR1 Overflow Interrupt Enable bit on
   }
}
/***********************************************************
*    COMM1 Transmit Interrupt                              *
***********************************************************/
#int_TBE                                                    //**************
TBE_isr()                                                   // BYTE TRANSIMITED
{  if(Comm1.Index <= Comm1.CRChi_Index)                     // Transmit until the entire packet has been sent
   {  Comm1.x = COMM1_Buffer[Comm1.Index];                  // Store the byte to be sent in a directly addressable location
      fputC(Comm1.x,COMM_1);                                // Start the byte transmition
      Comm1.Index++;                                        // Index the next byte to be sent
   }
   else
   {  disable_interrupts(INT_TBE);                          // Stop transmittion
      Comm1.Index = 0;                                      // Index for first byte to be recieved
   }
}
/***********************************************************
*    COMM1 Main Service                                    *
***********************************************************/
#inline
void COMM1_Service(void)
{  if(!COMM1.Initialized)
   {  setup_timer_1(T1_INTERNAL|T1_DIV_BY_1);               // Timer used by Network Ch A
      enable_interrupts(INT_RDA);                           // Enable recieved bytes interupt
      enable_interrupts(global);                            // Enable recieved bytes interupt
      Comm1.Index = 0;                                      // Index for first byte to be recieved
      Comm1.Packet = 0;                                     // Allow new packet reception to begin
      COMM1.Initialized=1;
      COMM1.Network_Address=1;
   }
   if(Comm1.Packet)                                         // Packet ready to process
   {  /****************************************************/
      /*     Mean time to decode CRC ~ xxuS per byte      */
      /****************************************************/
      Comm1.CRC_Lo = 0xFF;                                  // Prepair to generate CRC
      Comm1.CRC_Hi = 0xFF;                                  // Prepair to generate CRC
      COMM1.CRClo_Index=Comm1.CRChi_Index-1;                // Solve this index once instead of once per byte
      Comm1.Index = 0;                                      // Start at begining of packet
      while(Comm1.Index < COMM1.CRClo_Index)                // Use all bytes prior to CRClo_Index
      {  Comm1.Table_Index = COMM1_Buffer[Comm1.Index];     // Generate CRC
         Comm1.Table_Index ^= Comm1.CRC_Lo;                 // Generate CRC
         Comm1.CRC_Lo = Table_CRC_Hi[Comm1.Table_Index];    // Generate CRC
         Comm1.CRC_Lo ^= Comm1.CRC_Hi;                      // Generate CRC
         Comm1.CRC_Hi = Table_CRC_Lo[Comm1.Table_Index];    // Generate CRC
         Comm1.Index++;
      }
      Comm1.Index = 0;                                      // Zero index for Transmition
      COMM1_Buffer[COMM1.CRClo_Index]=Comm1.CRC_Lo;         // Place CRC_Lo within packet
      COMM1_Buffer[Comm1.CRChi_Index]=Comm1.CRC_Hi;         // Place CRC_Hi within packet
      if(Comm1.CRC_Lo != COMM1_Buffer[COMM1.CRClo_Index])   // Detect Bad CRClo
      {  Comm1.Packet = 0;                                  // Allow new packet reception to begin
      }
      if(Comm1.CRC_Hi != COMM1_Buffer[Comm1.CRChi_Index])   // Detect Bad CRChi
      {  Comm1.Packet = 0;                                  // Allow new packet reception to begin
      }
      Comm1.Index = 0;                                      // Zero index for recieving
      if(COMM1_Buffer[0]==Comm1.Network_Address)            // This device is directly addressed
      {  Comm1.Respond=1;                                   // This packet must be replied to
      }
      else
      {  Comm1.Respond=0;                                   // This packet must not be replied to
         if(COMM1_Buffer[0]=!0)                             // This is not a network brodcast on address 0
         {  if(COMM1_Buffer[0]=!255)                        // This is not a network brodcast on address 255
            {  Comm1.Packet = 0;                            // Allow new packet reception to begin
            }
         }
      }
      if(Comm1.Packet)                                      // Decode by modbus functions
      {  Comm1.CRChi_Index=0;                               // Prepair to test for invalid functions
         if(COMM1_Buffer[1] == 3)
         {  Comm1.start=COMM1_Buffer[3];                    // Starting register address
            Comm1.Quantity=COMM1_Buffer[5];                 // Quantity of registers to read
            if(COMM1_Buffer[2])  Comm1.Start=256;           // Invalid Address Range
            if(COMM1_Buffer[4])  Comm1.Start=256;           // Invalid Address Range
            Comm1.finish=Comm1.start+Comm1.quantity;        // Ending register address
            COMM1_Buffer[2]=Comm1.Quantity<<1;              // Data Byte Count
            if(Comm1.finish<=128)                           // Access to first 128 words
            {  Comm1.Index=3;                               // Set the index to the first byte
               for(Comm1.y=Comm1.start;Comm1.y<Comm1.finish;Comm1.y++)
               {  Comm1.x=(Comm1.y*2)+1;
                  COMM1_Buffer[Comm1.Index] = REG_Map[Comm1.x];
                  Comm1.Index++;
                  --Comm1.x;
                  COMM1_Buffer[Comm1.Index] = REG_Map[Comm1.x];
                  Comm1.Index++;
               }
               Comm1.CRChi_Index=Comm1.Index+1;
            }
            else
            {  bit_set(COMM1_Buffer[1],7);                  // Report_Exception
               COMM1_Buffer[2]=Illegal_Data_Address;        // Report_Exception type
               Comm1.CRChi_Index=4;                         // Index CRChi
            }
         }
         if(COMM1_Buffer[1] == 6)
         {  Comm1.start=COMM1_Buffer[3];                    // Starting register address
            if(COMM1_Buffer[2])  Comm1.Start=256;           // Invalid Address Range
            if(Comm1.start<=127)
            {  Comm1.Index=Comm1.start*2;
               REG_Map[Comm1.Index]=COMM1_Buffer[5];
               Comm1.Index++;
               REG_Map[Comm1.Index]=COMM1_Buffer[4];
               Comm1.CRChi_Index=7;                         // Index CRChi
            }
            else
            {  bit_set(COMM1_Buffer[1],7);                  // Report_Exception
               COMM1_Buffer[2]=Illegal_Data_Address;        // Report_Exception type
               Comm1.CRChi_Index=4;                         // Index CRChi
            }
         }
         if(Comm1.CRChi_Index == 0)
         {  bit_set(COMM1_Buffer[1],7);                     // Report_Exception
            COMM1_Buffer[2]=Illegal_Function;               // Report_Exception type
            Comm1.CRChi_Index=4;                            // Index CRChi
         }
         if(!Comm1.Respond)
         {  Comm1.Packet = 0;                               // Allow new packet reception to begin
            Comm1.Index = 0;                                // Start at begining of packet
         }
      }
      if(Comm1.Respond)                                     // Add CRC to outgoing data
      {  Comm1.CRC_Lo = 0xFF;                               // Prepair to generate CRC
         Comm1.CRC_Hi = 0xFF;                               // Prepair to generate CRC
         COMM1.CRClo_Index=Comm1.CRChi_Index-1;             // Solve this index once instead of once per byte
         Comm1.Index = 0;                                   // Start at begining of packet
         while(Comm1.Index < COMM1.CRClo_Index)             // Use all bytes prior to CRClo_Index
         {  Comm1.Table_Index = COMM1_Buffer[Comm1.Index];  // Generate CRC
            Comm1.Table_Index ^= Comm1.CRC_Lo;              // Generate CRC
            Comm1.CRC_Lo = Table_CRC_Hi[Comm1.Table_Index]; // Generate CRC
            Comm1.CRC_Lo ^= Comm1.CRC_Hi;                   // Generate CRC
            Comm1.CRC_Hi = Table_CRC_Lo[Comm1.Table_Index]; // Generate CRC
            Comm1.Index++;
         }
         Comm1.Index = 0;                                   // Zero index for Transmition
         COMM1_Buffer[COMM1.CRClo_Index]=Comm1.CRC_Lo;      // Place CRC_Lo within packet
         COMM1_Buffer[Comm1.CRChi_Index]=Comm1.CRC_Hi;      // Place CRC_Hi within packet
         enable_interrupts(INT_TBE);                        // Kick Off Xmit of Data
         ++Reply_Packets_This_Second;                       // Count responce packets (for debugging)
      }
      Comm1.Packet = 0;                                     // Allow new packet reception to begin
   }
}
mvaraujo



Joined: 20 Feb 2004
Posts: 59
Location: Brazil

View user's profile Send private message

PostPosted: Sun Feb 22, 2004 4:51 pm     Reply with quote

Neutone,

I did only a few tests on the CRC table look up routines but it did have been much quickier that using CRC real calculation.

I understand your comments. I really don't know if there is another way to implement this table routine.

One other comment that I could add to this post is that, basically, this code is an adaptation of the routine found in the Modbus over serial document from Modbus.org

Good luck with your project!
Barney



Joined: 18 Oct 2004
Posts: 41
Location: Newark, CA

View user's profile Send private message

ModBus RTU Code
PostPosted: Thu Apr 07, 2005 3:21 pm     Reply with quote

I am very interested in implementing Modbus RTU on a RS485 PIC network at home and in the interest of discussion, I have two questions:

1. The COMM1_Service() routine checks the CRC first, then the slave address. Wouldn't it be more efficient to check the slave address first and not process the CRC if that slave wasn't addressed? Otherwise it seems to me that all of the slaves but one are wasting their time.

2. There are two network broadcase if statements that seem to be typos:
if(Comm1.Buffer[0]=!0) and
if(Comm1.Buffer[0]=!255)

Shouldn't these be != or 'not equal", instead of =! or "assign not" ??
Mark



Joined: 07 Sep 2003
Posts: 2838
Location: Atlanta, GA

View user's profile Send private message Send e-mail

PostPosted: Thu Apr 07, 2005 5:18 pm     Reply with quote

Well this certainly looks funny to me
Code:
      Comm1.Index = 0;                                      // Zero index for Transmition
      COMM1_Buffer[COMM1.CRClo_Index]=Comm1.CRC_Lo;         // Place CRC_Lo within packet
      COMM1_Buffer[Comm1.CRChi_Index]=Comm1.CRC_Hi;         // Place CRC_Hi within packet
      if(Comm1.CRC_Lo != COMM1_Buffer[COMM1.CRClo_Index])   // Detect Bad CRClo
      {  Comm1.Packet = 0;                                  // Allow new packet reception to begin
      }
      if(Comm1.CRC_Hi != COMM1_Buffer[Comm1.CRChi_Index])   // Detect Bad CRChi
      {  Comm1.Packet = 0;                                  // Allow new packet reception to begin
      }
      Comm1.Index = 0;                                      // Zero index for recieving
Neutone



Joined: 08 Sep 2003
Posts: 839
Location: Houston

View user's profile Send private message

PostPosted: Fri Apr 08, 2005 8:44 am     Reply with quote

Yea Mark setting the variables equal and then checking if they are equal seems counter productive. Barney your correct those should have been != operators to mask out broadcast functions. Address zero is a brodcast address IAW MODBUS spec and I was using 255 as well. With those two problems it still worked quite well except when there was a CRC error or a brodcast packet. I will correct my post in the Code Library.
Barney



Joined: 18 Oct 2004
Posts: 41
Location: Newark, CA

View user's profile Send private message

Thanks
PostPosted: Mon Apr 11, 2005 1:08 pm     Reply with quote

Neutone,

I have been working with your code over the weekend and I wanted to thank you for posting it. I want to set up a RTU RS-485 network at home and your code gave me a great jump start!

I am using a 16F88, so I had to make some modifications to get the code to compile and fit into my address spaces. For example:

Reduce the number of registers to 32 to fit in a single 16F88 bank.
Remove the #locate 0x100 and 0x200 statements
Decrease the Timer1 start value to match 1.5 byte times at 9600 baud.
Added the 0x11 Report Slave ID.
Check the slave address before checking CRC.
Figure out a way to shoe horn the CRC look up table into the code.

When it is stable, I will post it for the next user with a mid-size PIC. I also had to roll my own RTU master for my PC since I am so cheap I am still using Win98SE and VB4. The Sapia free download doesn't work with such old stuff. I will post the Master code as well.

Muchos for the leg up
chrisatwan



Joined: 07 Apr 2005
Posts: 3
Location: newark, nj

View user's profile Send private message Send e-mail AIM Address Yahoo Messenger MSN Messenger

Basic Modbus Example
PostPosted: Tue Apr 12, 2005 11:55 am     Reply with quote

I was looking the code posted and wanted to know how "void COMM1_Service(void)" is called. I do not see where this is called from.

Is this called from the interrupt?

Thank you,
Chris Atwan
Mark



Joined: 07 Sep 2003
Posts: 2838
Location: Atlanta, GA

View user's profile Send private message Send e-mail

Re: Basic Modbus Example
PostPosted: Tue Apr 12, 2005 1:22 pm     Reply with quote

chrisatwan wrote:
I was looking the code posted and wanted to know how "void COMM1_Service(void)" is called. I do not see where this is called from.

Is this called from the interrupt?

Thank you,
Chris Atwan


Quote:
To use this simply call COMM1_Service() frequently to service packets that have arrived and start a reply.


That means that it is up the the programmer to call the function, probably in main().
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group