|
|
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
|