| 
	
	|  |  |  
	
		| View previous topic :: View next topic |  
		| Author | Message |  
		| Neutone 
 
 
 Joined: 08 Sep 2003
 Posts: 839
 Location: Houston
 
 
			    
 
 | 
			
				| Basic MODBUS example |  
				|  Posted: Thu Feb 19, 2004 8:47 am |   |  
				| 
 |  
				| 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
 
 
			    
 
 | 
			
				|  |  
				|  Posted: Thu Feb 19, 2004 8:48 am |   |  
				| 
 |  
				|  	  | 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
 
 
			      
 
 | 
			
				| Re: MODBUS interface |  
				|  Posted: Thu Feb 19, 2004 3:45 pm |   |  
				| 
 |  
				| This looks interesting.  Is it Modbus ASCII or Modbus RTU? |  |  
		|  |  
		| Neutone 
 
 
 Joined: 08 Sep 2003
 Posts: 839
 Location: Houston
 
 
			    
 
 | 
			
				|  |  
				|  Posted: Thu Feb 19, 2004 5:04 pm |   |  
				| 
 |  
				| It's RTU, it would have to be modified slightly for ASCII. |  |  
		|  |  
		| Guest 
 
 
 
 
 
 
 
			
			
			
			
			
			
			
			
			
 
 | 
			
				|  |  
				|  Posted: Fri Feb 20, 2004 11:54 am |   |  
				| 
 |  
				| 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 highorder 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 loworder 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
 
 
			    
 
 | 
			
				|  |  
				|  Posted: Fri Feb 20, 2004 11:58 am |   |  
				| 
 |  
				| My username appeared as Guest... Don't know why... it was mvaraujo writting... sorry! |  |  
		|  |  
		| Neutone 
 
 
 Joined: 08 Sep 2003
 Posts: 839
 Location: Houston
 
 
			    
 
 | 
			
				| Thanks for posting. |  
				|  Posted: Fri Feb 20, 2004 3:53 pm |   |  
				| 
 |  
				| I'll see about intergrating that and then post my results. |  |  
		|  |  
		| Neutone 
 
 
 Joined: 08 Sep 2003
 Posts: 839
 Location: Houston
 
 
			    
 
 | 
			
				| New and Improved |  
				|  Posted: Sat Feb 21, 2004 4:59 pm |   |  
				| 
 |  
				| 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
 
 
			    
 
 | 
			
				|  |  
				|  Posted: Sun Feb 22, 2004 4:51 pm |   |  
				| 
 |  
				| 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
 
 
			    
 
 | 
			
				| ModBus RTU Code |  
				|  Posted: Thu Apr 07, 2005 3:21 pm |   |  
				| 
 |  
				| 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
 
 
			      
 
 | 
			
				|  |  
				|  Posted: Thu Apr 07, 2005 5:18 pm |   |  
				| 
 |  
				| 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
 
 
			    
 
 | 
			
				|  |  
				|  Posted: Fri Apr 08, 2005 8:44 am |   |  
				| 
 |  
				| 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
 
 
			    
 
 | 
			
				| Thanks |  
				|  Posted: Mon Apr 11, 2005 1:08 pm |   |  
				| 
 |  
				| 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
 
 
			            
 
 | 
			
				| Basic Modbus Example |  
				|  Posted: Tue Apr 12, 2005 11:55 am |   |  
				| 
 |  
				| 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
 
 
			      
 
 | 
			
				| Re: Basic Modbus Example |  
				|  Posted: Tue Apr 12, 2005 1:22 pm |   |  
				| 
 |  
				|  	  | 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().
 |  |  
		|  |  
		|  |  
  
	| 
 
 | 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
 
 |