Hi all!
I've been working for some months with Arduino and Modbus/RTU. I've taken the code from http://sites.google.com/site/jpmzometa/arduino-mbrt/arduino-modbus-slave and I've turned it into a class. I don't want to make it a library.
Here you'll find the code:
/* supported functions ! */
const unsigned char fsupported[] = {
0x03, 0x04, 0x10 };
unsigned long Nowdt = 0;
int lastBytesReceived = 0;
#define MAX_MESSAGE_LENGTH 256
/* constants */
enum {
RESPONSE_SIZE = 6,
EXCEPTION_SIZE = 3,
CHECKSUM_SIZE = 2
};
/* exceptions code */
enum {
NO_REPLY = -1,
EXC_FUNC_CODE = 1,
EXC_ADDR_RANGE = 2,
EXC_REGS_QUANT = 3,
EXC_EXECUTE = 4
};
typedef enum parity {
P_8N1,P_8E1,P_8O1};
/* positions inside the query/response array */
enum {
SLAVE = 0,
FUNC,
START_H,
START_L,
REGS_H,
REGS_L,
BYTE_CNT
};
class MB_SLAVE {
private:
unsigned char slave;
int TXEN_PIN;
unsigned int crc(unsigned char *buf, unsigned char start, unsigned char cnt);
void build_read_packet(unsigned char function, unsigned char count, unsigned char *packet);
void build_write_packet(unsigned char function, unsigned int start_addr, unsigned char count, unsigned char *packet);
void build_error_packet(unsigned char function,unsigned char exception, unsigned char *packet);
void modbus_reply(unsigned char *packet, unsigned char string_length);
int send_reply(unsigned char *query, unsigned char string_length);
int receive_request(unsigned char *received_string);
int modbus_request(unsigned char *data, int *regs);
int validate_request(unsigned char *data, unsigned char length);
int write_regs(unsigned int start_addr, unsigned char *query, int *regs);
int preset_multiple_registers(unsigned int start_addr,unsigned char count,unsigned char *query,int *regs);
int read_holding_registers(unsigned int start_addr, unsigned char reg_count, int *regs);
public:
MB_SLAVE(int TXEN_PIN);
void begin( parity par, long baud, unsigned char slave);
int start_mb_slave(int *regs);
};
// constructor
MB_SLAVE::MB_SLAVE(int TXEN_PIN) {
this->TXEN_PIN = TXEN_PIN;
}
// slave comm's start-up
void MB_SLAVE::begin( parity par, long baud, unsigned char slave) {
// define digital-output for half-duplex mode
digitalWrite( TXEN_PIN, HIGH );
pinMode( TXEN_PIN, OUTPUT );
// define slave address
this->slave = slave;
// set parity
switch (par) {
case P_8E1: // 8E1
{
UCSR0C |= ((1<<UPM01) | (1<<UCSZ01) | (1<<UCSZ00));
// UCSR0C &= ~((1<<UPM00) | (1<<UCSZ02) | (1<<USBS0));
}
break;
case P_8O1: // 8O1
{
UCSR0C |= ((1<<UPM01) | (1<<UPM00) | (1<<UCSZ01) | (1<<UCSZ00));
// UCSR0C &= ~((1<<UCSZ02) | (1<<USBS0));
}
break;
default:
case P_8N1: // 8N1
{
UCSR0C |= ((1<<UCSZ01) | (1<<UCSZ00));
// UCSR0C &= ~((1<<UPM01) | (1<<UPM00) | (1<<UCSZ02) | (1<<USBS0));
}
break;
}
Serial.begin(baud);
}
unsigned int MB_SLAVE::crc(unsigned char *buf, unsigned char start,unsigned char cnt)
{
unsigned char i, j;
unsigned temp, temp2, flag;
temp = 0xFFFF;
for (i = start; i < cnt; i++) {
temp = temp ^ buf[i];
for (j = 1; j <= 8; j++) {
flag = temp & 0x0001;
temp = temp >> 1;
if (flag)
temp = temp ^ 0xA001;
}
}
/* Reverse byte order. */
temp2 = temp >> 8;
temp = (temp << 8) | temp2;
temp &= 0xFFFF;
return (temp);
}
void MB_SLAVE::build_read_packet(unsigned char function,unsigned char count, unsigned char *packet)
{
packet[SLAVE] = slave;
packet[FUNC] = function;
packet[2] = count * 2;
}
void MB_SLAVE::build_write_packet(unsigned char function,unsigned int start_addr,unsigned char count,unsigned char *packet)
{
packet[SLAVE] = slave;
packet[FUNC] = function;
packet[START_H] = start_addr >> 8;
packet[START_L] = start_addr & 0x00ff;
packet[REGS_H] = 0x00;
packet[REGS_L] = count;
}
void MB_SLAVE::build_error_packet(unsigned char function,unsigned char exception, unsigned char *packet)
{
packet[SLAVE] = slave;
packet[FUNC] = function + 0x80;
packet[2] = exception;
D[ 12 ] ++;
}
void MB_SLAVE::modbus_reply(unsigned char *packet, unsigned char string_length)
{
int temp_crc;
temp_crc = crc(packet, 0, string_length);
packet[string_length] = temp_crc >> 8;
string_length++;
packet[string_length] = temp_crc & 0x00FF;
}
int MB_SLAVE::send_reply(unsigned char *query, unsigned char string_length)
{
unsigned char i, test;
// set MAX485 to speak mode
UCSR0A=UCSR0A |(1 << TXC0);
digitalWrite( TXEN_PIN, HIGH);
delay(1);
modbus_reply(query, string_length);
string_length += 2;
// xmit message
for (i = 0; i < string_length; i++) {
Serial.print(query[i], BYTE);
}
while (!(UCSR0A & (1 << TXC0)));
digitalWrite( TXEN_PIN, LOW);
return i; // it does not mean that the write was succesful, though
}
int MB_SLAVE::receive_request(unsigned char *received_string)
{
int bytes_received = 0;
while (Serial.available()) {
received_string[bytes_received] = Serial.read();
bytes_received++;
if (bytes_received >= MAX_MESSAGE_LENGTH)
return NO_REPLY; // port error
}
return (bytes_received);
}
int MB_SLAVE::modbus_request(unsigned char *data, int *regs)
{
int response_length;
unsigned int crc_calc = 0;
unsigned int crc_received = 0;
unsigned char recv_crc_hi;
unsigned char recv_crc_lo;
response_length = receive_request(data);
if (response_length > 0) {
regs[ MSG_CNT] ++;
crc_calc = crc(data, 0, response_length - 2);
crc_received = data[response_length - 2];
crc_received = (unsigned) crc_received << 8;
crc_received =
crc_received | (unsigned) data[response_length - 1];
if (crc_calc != crc_received) {
regs[ BAD_CRC_CNT ] ++;
return NO_REPLY;
}
/* check for slave id */
if ((slave != data[SLAVE]) && (data[SLAVE] != 0)) {
regs[ NO_REPLY_CNT ] ++;
return NO_REPLY;
}
}
return (response_length);
}
if ((data[FUNC] == 0x03) && (data[SLAVE]==0)) fcnt = 0;
if (0 == fcnt)
return EXC_FUNC_CODE;
reg_cnt = ((int) data[REGS_H] << 8) + (int) data[REGS_L];
if ((reg_cnt < 1) || (reg_cnt > REGS_NUM))
return EXC_REGS_QUANT;
/* check registers range, start address is 0 */
start_addr = ((int) data[START_H] << 8) + (int) data[START_L];
if ((start_addr + reg_cnt) > REGS_NUM)
return EXC_ADDR_RANGE;
return 0; /* OK, no exception */
}
int MB_SLAVE::write_regs(unsigned int start_addr, unsigned char *query, int *regs)
{
int temp;
unsigned int i;
for (i = 0; i < query[REGS_L]; i++) {
/* shift reg hi_byte to temp */
temp = (int) query[(BYTE_CNT + 1) + i * 2] << 8;
/* OR with lo_byte */
temp = temp | (int) query[(BYTE_CNT + 2) + i * 2];
regs[start_addr + i] = temp;
}
return i;
}
int MB_SLAVE::preset_multiple_registers(unsigned int start_addr,unsigned char count,
unsigned char *query,int *regs)
{
unsigned char function = 0x10;
int status = 0;
unsigned char packet[RESPONSE_SIZE + CHECKSUM_SIZE];
build_write_packet(function, start_addr, count, packet);
if (write_regs(start_addr, query, regs)) {
if (query[SLAVE] !=0) {
regs[ REPLY_CNT ] ++;
regs[ MSG_CNT ] ++;
status = send_reply(packet, RESPONSE_SIZE);
}
else {
regs[ NO_REPLY_CNT ] ++;
}
}
return (status);
}
int MB_SLAVE::read_holding_registers(unsigned int start_addr,unsigned char reg_count, int *regs) {
unsigned char function = 0x03;
int packet_size = 3;
int status;
unsigned int i;
unsigned char packet[MAX_MESSAGE_LENGTH];
build_read_packet(function, reg_count, packet);
regs[ REPLY_CNT ] ++;
regs[ MSG_CNT ] ++;
for (i = start_addr; i < (start_addr + (unsigned int) reg_count); i++) {
packet[packet_size] = regs[i] >> 8;
packet_size++;
packet[packet_size] = regs[i] & 0x00FF;
packet_size++;
}
status = send_reply(packet, packet_size);
return (status);
}
int MB_SLAVE::start_mb_slave(int *regs)
{
unsigned char query[MAX_MESSAGE_LENGTH];
unsigned char errpacket[EXCEPTION_SIZE + CHECKSUM_SIZE];
unsigned int start_addr;
const unsigned long T35 = 5;
int exception;
int length = Serial.available();
unsigned long now = millis();
digitalWrite( TXEN_PIN, LOW);
if (length == 0) {
lastBytesReceived = 0;
return 0;
}
if (lastBytesReceived != length) {
lastBytesReceived = length;
Nowdt = now + T35;
return 0;
}
if (now < Nowdt) return 0;
lastBytesReceived = 0;
length = modbus_request(query, regs);
if (length < 1) {
return length;
}
else {
exception = validate_request(query, length);
if (exception) {
regs[ EXCEPT_CNT ] ++;
if (query[SLAVE] != 0) {
regs[ REPLY_CNT ] ++;
regs[ MSG_CNT ] ++;
build_error_packet(query[FUNC], exception, errpacket);
send_reply(errpacket, EXCEPTION_SIZE);
}
else {
regs[ NO_REPLY_CNT ] ++;
}
return (exception);
}
else {
start_addr =
((int) query[START_H] << 8) +
(int) query[START_L];
if ((0x03 == query[FUNC]) || (0x04 == query[FUNC]))
return read_holding_registers(start_addr,query[REGS_L],regs);
if (0x10 == query[FUNC])
return preset_multiple_registers(start_addr,query[REGS_L],query,regs);
}
}
}
/me