This library meant to allow mbed boards (tested on F401RE) to act as modbus slave. I don't own this work, I just made some simple modifications so that it runs on mbed. My modification were labeled with "afdhal". Summary: Modified modbus Arduino library for mbed. See readme for more details. Feel free to use it as you want. Special thanks to the original authors.
Dependents: ModbusRTU-RS232 FoodComputerARM-alpha
Diff: ModbusSlave232.cpp
- Revision:
- 0:6262fc7582a9
- Child:
- 1:35fdc7056f66
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ModbusSlave232.cpp Thu Jul 21 19:48:35 2016 +0000 @@ -0,0 +1,656 @@ +/* + Modbus over serial line - RTU Slave Arduino Sketch + + By Juan Pablo Zometa : jpmzometa@gmail.com + http://sites.google.com/site/jpmzometa/ + Samuel Marco: sammarcoarmengol@gmail.com + and Andras Tucsni. + + These functions implement functions 3, 6, and 16 (read holding registers, + preset single register and preset multiple registers) of the + Modbus RTU Protocol, to be used over the Arduino serial connection. + + This implementation DOES NOT fully comply with the Modbus specifications. + + This Arduino adaptation is derived from the work + By P.Costigan email: phil@pcscada.com.au http://pcscada.com.au + + These library of functions are designed to enable a program send and + receive data from a device that communicates using the Modbus protocol. + + Copyright (C) 2000 Philip Costigan P.C. SCADA LINK PTY. LTD. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + The functions included here have been derived from the + Modicon Modbus Protocol Reference Guide + which can be obtained from Schneider at www.schneiderautomation.com. + + This code has its origins with + paul@pmcrae.freeserve.co.uk (http://www.pmcrae.freeserve.co.uk) + who wrote a small program to read 100 registers from a modbus slave. + + I have used his code as a catalist to produce this more functional set + of functions. Thanks paul. + */ + + +//#include "Arduino.h" //afdhal +#include "millis.h" +#include "MODSERIAL.h" //afdhal +MODSERIAL pc(USBTX, USBRX); // tx, rx //afdhal + +#ifndef ModbusSlave232_h + #include "ModbusSlave232.h" +#endif + + +/**************************************************************************** + * BEGIN MODBUS RTU SLAVE FUNCTIONS + ****************************************************************************/ + +/* constants */ +enum { + MAX_READ_REGS = 0x7D, + MAX_WRITE_REGS = 0x7B, + MAX_MESSAGE_LENGTH = 256 +}; + + +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 +}; + +/* positions inside the query/response array */ +enum { + SLAVE = 0, + FUNC, + START_H, + START_L, + REGS_H, + REGS_L, + BYTE_CNT +}; + + +/* enum of supported modbus function codes. If you implement a new one, put its function code here ! */ +enum { + FC_READ_REGS = 0x03, //Read contiguous block of holding register + FC_WRITE_REG = 0x06, //Write single holding register + FC_WRITE_REGS = 0x10 //Write block of contiguous registers +}; + +/* supported functions. If you implement a new one, put its function code into this array! */ +const unsigned char fsupported[] = { FC_READ_REGS, FC_WRITE_REG, FC_WRITE_REGS }; + + +/* +CRC + + INPUTS: + buf -> Array containing message to be sent to controller. + start -> Start of loop in crc counter, usually 0. + cnt -> Amount of bytes in message being sent to controller/ + OUTPUTS: + temp -> Returns crc byte for message. + COMMENTS: + This routine calculates the crc high and low byte of a message. + Note that this crc is only used for Modbus, not Modbus+ etc. + ****************************************************************************/ + +unsigned int ModbusSlave232::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); +} + + + + +/*********************************************************************** + * + * The following functions construct the required query into + * a modbus query packet. + * + ***********************************************************************/ + +/* + * Start of the packet of a read_holding_register response + */ +void ModbusSlave232::build_read_packet(unsigned char function, +unsigned char count, unsigned char *packet) +{ + packet[SLAVE] = slave; + packet[FUNC] = function; + packet[2] = count * 2; +} + +/* + * Start of the packet of a preset_multiple_register response + */ +void ModbusSlave232::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; +} + +/* + * Start of the packet of a write_single_register response + */ +void ModbusSlave232::build_write_single_packet(unsigned char function, + unsigned int write_addr, unsigned int reg_val, unsigned char* packet) +{ + packet[SLAVE] = slave; + packet[FUNC] = function; + packet[START_H] = write_addr >> 8; + packet[START_L] = write_addr & 0x00ff; + packet[REGS_H] = reg_val >> 8; + packet[REGS_L] = reg_val & 0x00ff; +} + + +/* + * Start of the packet of an exception response + */ +void ModbusSlave232::build_error_packet( unsigned char function, +unsigned char exception, unsigned char *packet) +{ + packet[SLAVE] = slave; + packet[FUNC] = function + 0x80; + packet[2] = exception; +} + + +/************************************************************************* + * + * modbus_query( packet, length) + * + * Function to add a checksum to the end of a packet. + * Please note that the packet array must be at least 2 fields longer than + * string_length. + **************************************************************************/ + +void ModbusSlave232::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; +} + + + +/*********************************************************************** + * + * send_reply( query_string, query_length ) + * + * Function to send a reply to a modbus master. + * Returns: total number of characters sent + ************************************************************************/ + +int ModbusSlave232::send_reply(unsigned char *query, unsigned char string_length) +{ + unsigned char i; + + /*if (txenpin > 1) { // set MAX485 to speak mode //afdhal + UCSR0A=UCSR0A |(1 << TXC0); + digitalWrite( txenpin, HIGH); + delay(1); + }*/ + + modbus_reply(query, string_length); + string_length += 2; + + for (i = 0; i < string_length; i++) { + //Serial.print(char(query[i])); //afdhal + pc.putc(char(query[i])); //afdhal + + } + + return i; /* it does not mean that the write was succesful, though */ +} + +/*********************************************************************** + * + * receive_request( array_for_data ) + * + * Function to monitor for a request from the modbus master. + * + * Returns: Total number of characters received if OK + * 0 if there is no request + * A negative error code on failure + ***********************************************************************/ + +int ModbusSlave232::receive_request(unsigned char *received_string) +{ + int bytes_received = 0; + + /* FIXME: does Serial.available wait 1.5T or 3.5T before exiting the loop? */ + //while (Serial.available()) { //afdhal + while (pc.readable()) { //afdhal + received_string[bytes_received] = pc.getc(); //Serial.read(); //afdhal + //Serial.print(received_string[bytes_received], DEC); + bytes_received++; + if (bytes_received >= MAX_MESSAGE_LENGTH) + return NO_REPLY; /* port error */ + } + + return (bytes_received); +} + + +/********************************************************************* + * + * modbus_request(request_data_array) + * + * Function to the correct request is returned and that the checksum + * is correct. + * + * Returns: string_length if OK + * 0 if failed + * Less than 0 for exception errors + * + * Note: All functions used for sending or receiving data via + * modbus return these return values. + * + **********************************************************************/ + +int ModbusSlave232::modbus_request(unsigned char *data) +{ + 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) { + crc_calc = crc(data, 0, response_length - 2); + recv_crc_hi = (unsigned) data[response_length - 2]; + recv_crc_lo = (unsigned) data[response_length - 1]; + crc_received = data[response_length - 2]; + crc_received = (unsigned) crc_received << 8; + crc_received = + crc_received | (unsigned) data[response_length - 1]; + + /*********** check CRC of response ************/ + if (crc_calc != crc_received) { + return NO_REPLY; + } + + /* check for slave id */ + if (slave != data[SLAVE]) { + return NO_REPLY; + } + } + return (response_length); +} + +/********************************************************************* + * + * validate_request(request_data_array, request_length, available_regs) + * + * Function to check that the request can be processed by the slave. + * + * Returns: 0 if OK + * A negative exception code on error + * + **********************************************************************/ + +int ModbusSlave232::validate_request(unsigned char *data, unsigned char length, +unsigned int regs_size) +{ + int i, fcnt = 0; + unsigned int regs_num = 0; + unsigned int start_addr = 0; + unsigned char max_regs_num; + + /* check function code */ + for (i = 0; i < sizeof(fsupported); i++) { + if (fsupported[i] == data[FUNC]) { + fcnt = 1; + break; + } + } + if (0 == fcnt) + return EXC_FUNC_CODE; + + if (FC_WRITE_REG == data[FUNC]) { + /* For function write single reg, this is the target reg.*/ + regs_num = ((int) data[START_H] << 8) + (int) data[START_L]; + if (regs_num >= regs_size) + return EXC_ADDR_RANGE; + return 0; + } + + /* For functions read/write regs, this is the range. */ + regs_num = ((int) data[REGS_H] << 8) + (int) data[REGS_L]; + + /* check quantity of registers */ + if (FC_READ_REGS == data[FUNC]) + max_regs_num = MAX_READ_REGS; + else if (FC_WRITE_REGS == data[FUNC]) + max_regs_num = MAX_WRITE_REGS; + + if ((regs_num < 1) || (regs_num > max_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 + regs_num) > regs_size) + return EXC_ADDR_RANGE; + + return 0; /* OK, no exception */ +} + + + +/************************************************************************ + * + * write_regs(first_register, data_array, registers_array) + * + * writes into the slave's holding registers the data in query, + * starting at start_addr. + * + * Returns: the number of registers written + ************************************************************************/ + +int ModbusSlave232::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; +} + +/************************************************************************ + * + * preset_multiple_registers(first_register, number_of_registers, + * data_array, registers_array) + * + * Write the data from an array into the holding registers of the slave. + * + *************************************************************************/ + +int ModbusSlave232::preset_multiple_registers(unsigned int start_addr, +unsigned char count, +unsigned char *query, +int *regs) +{ + unsigned char function = FC_WRITE_REGS; /* Preset Multiple Registers */ + 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)) { + status = send_reply(packet, RESPONSE_SIZE); + } + + return (status); +} + +/************************************************************************ + * + * write_single_register(slave_id, write_addr, data_array, registers_array) + * + * Write a single int val into a single holding register of the slave. + * + *************************************************************************/ + +int ModbusSlave232::write_single_register(unsigned int write_addr, unsigned char *query, int *regs) +{ + unsigned char function = FC_WRITE_REG; /* Function: Write Single Register */ + int status = 0; + unsigned int reg_val; + unsigned char packet[RESPONSE_SIZE + CHECKSUM_SIZE]; + + reg_val = query[REGS_H] << 8 | query[REGS_L]; + build_write_single_packet(function, write_addr, reg_val, packet); + regs[write_addr] = (int) reg_val; +/* + written.start_addr=write_addr; + written.num_regs=1; +*/ + status = send_reply(packet, RESPONSE_SIZE); + + return (status); +} + +/************************************************************************ + * + * read_holding_registers(first_register, number_of_registers, + * registers_array) + * + * reads the slave's holdings registers and sends them to the Modbus master + * + *************************************************************************/ + +int ModbusSlave232::read_holding_registers( unsigned int start_addr, + +unsigned char reg_count, int *regs) +{ + unsigned char function = FC_READ_REGS; /* Read Holding Registers */ + int packet_size = 3; + int status; + unsigned int i; + unsigned char packet[MAX_MESSAGE_LENGTH]; + + build_read_packet(function, reg_count, packet); + + 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); +} + +/* + * configure(slave, baud, parity, txenpin) + * + * sets the communication parameters for of the serial line. + * + * slave: identification number of the slave in the Modbus network (1 to 127) + * baud: baudrate in bps (typical values 9600, 19200... 115200) + * parity: a single character sets the parity mode (character frame format): + * 'n' no parity (8N1); 'e' even parity (8E1), 'o' for odd parity (8O1). + * txenpin: arduino pin number that controls transmision/reception + * of an external half-duplex device (e.g. a RS485 interface chip). + * 0 or 1 disables this function (for a two-device network) + * >2 for point-to-multipoint topology (e.g. several arduinos) + */ + +void ModbusSlave232::configure(unsigned char slave, long baud, char parity) +{ + this->slave = slave; + //this->txenpin = 2; //afdhal + + + + //Serial.begin(baud); //afdhal + pc.baud(baud); + + switch (parity) { + case 'e': // 8E1 + //UCSR0C |= ((1<<UPM01) | (1<<UCSZ01) | (1<<UCSZ00)); //afdhal + pc.format(8, SerialBase::Even, 1); //afdhal + // UCSR0C &= ~((1<<UPM00) | (1<<UCSZ02) | (1<<USBS0)); + break; + case 'o': // 8O1 + //UCSR0C |= ((1<<UPM01) | (1<<UPM00) | (1<<UCSZ01) | (1<<UCSZ00)); //afdhal + pc.format(8, SerialBase::Odd, 1); //afdhal + // UCSR0C &= ~((1<<UCSZ02) | (1<<USBS0)); + break; + case 'n': // 8N1 + //UCSR0C |= ((1<<UCSZ01) | (1<<UCSZ00)); //afdhal + pc.format(8, SerialBase::None, 1); //afdhal + // UCSR0C &= ~((1<<UPM01) | (1<<UPM00) | (1<<UCSZ02) | (1<<USBS0)); + break; + default: + break; + } + + /* + if (txenpin > 1) { // pin 0 & pin 1 are reserved for RX/TX + pinMode(txenpin, OUTPUT); + digitalWrite(txenpin, LOW); + }*/ + + return; +} + + +/* + * update(regs, regs_size) + * + * checks if there is any valid request from the modbus master. If there is, + * performs the requested action + * + * regs: an array with the holding registers. They start at address 1 (master point of view) + * regs_size: total number of holding registers. + * returns: 0 if no request from master, + * NO_REPLY (-1) if no reply is sent to the master + * an exception code (1 to 4) in case of a modbus exceptions + * the number of bytes sent as reply ( > 4) if OK. + */ +unsigned long Nowdt = 0; +unsigned int lastBytesReceived; +const unsigned long T35 = 5; + +int ModbusSlave232::update(int *regs, +unsigned int regs_size) +{ + unsigned char query[MAX_MESSAGE_LENGTH]; + unsigned char errpacket[EXCEPTION_SIZE + CHECKSUM_SIZE]; + unsigned int start_addr; + int exception; + int length = pc.rxBufferGetCount(); //Serial.available(); //afdhal + unsigned long now = millis(); + + + + 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); + if (length < 1) + return length; + + exception = validate_request(query, length, regs_size); + if (exception) { + build_error_packet( query[FUNC], exception, + errpacket); + send_reply(errpacket, EXCEPTION_SIZE); + return (exception); + } + + start_addr = + ((int) query[START_H] << 8) + + (int) query[START_L]; + + switch (query[FUNC]) { + case FC_READ_REGS: + return read_holding_registers( + start_addr, + query[REGS_L], + regs); + break; + case FC_WRITE_REGS: + return preset_multiple_registers( + start_addr, + query[REGS_L], + query, + regs); + break; + case FC_WRITE_REG: + write_single_register( + start_addr, + query, + regs); + break; + } + +} + + +