在上一篇文章 《初试 W5500 模块给 Arduino 增加 Ethernet 功能》里实现了 Arduino 的网络功能,本篇 LT 将带你尝试 Arduino 增加 Modbus TCP 功能。
一、Arduino 硬件
本次测试采用 Arduino UNO R3,接线如下:
W5500引脚 Arduino引脚 SCK D13 MISO D12 MOSI D11 CS D10 RST RESET 5V 5V GND GND
注意:Pin13 这根 CLK 线要和其他数据线拉开距离,不然高频信号会耦合而影响通讯(图中左端的灰色杜邦线)。
二、Modbus TCP 库
测试采用 Modbus library http://myarduinoprojects.com/modbus.html
Arduino 作为 Modbus Slave,也即 Server,IP 地址设为 192.168.1.177。
示例 MgsModbus_test_Slave 代码如下
#include <SPI.h> #include <Ethernet.h> #include "MgsModbus.h" MgsModbus Mb; int inByte = 0; // incoming serial byte // Ethernet settings (depending on MAC and Local network) byte mac[] = {0x90, 0xA2, 0xDA, 0x0E, 0x94, 0xB5 }; IPAddress ip(192, 168, 1, 177); IPAddress gateway(192, 168, 1, 177); IPAddress subnet(255, 255, 255, 0); void setup() { // serial setup Serial.begin(9600); Serial.println("Serial interface started"); // initialize the ethernet device Ethernet.begin(mac, ip, gateway, subnet); // start etehrnet interface Serial.println("Ethernet interface started"); // print your local IP address: Serial.print("My IP address: "); for (byte thisByte = 0; thisByte < 4; thisByte++) { // print the value of each byte of the IP address: Serial.print(Ethernet.localIP()[thisByte], DEC); Serial.print("."); } Serial.println(); // Fill MbData // Mb.SetBit(0,false); Mb.MbData[0] = 0; Mb.MbData[1] = 0; Mb.MbData[2] = 0; Mb.MbData[3] = 0; Mb.MbData[4] = 0; Mb.MbData[5] = 0; Mb.MbData[6] = 0; Mb.MbData[7] = 0; Mb.MbData[8] = 0; Mb.MbData[9] = 0; Mb.MbData[10] = 0; Mb.MbData[11] = 0; // print MbData for (int i=0;i<12;i++) { Serial.print("address: "); Serial.print(i); Serial.print("Data: "); Serial.println(Mb.MbData[i]); } // print menu Serial.println("0 - print the first 12 words of the MbData space"); Serial.println("1 - fill MbData with 0x0000 hex"); Serial.println("2 - fill MbData with 0xFFFF hex"); Serial.println("3 - fill MbData with 0x5555 hex"); Serial.println("4 - fill MbData with 0xAAAA hex"); } void loop() { if (Serial.available() > 0) { // get incoming byte: inByte = Serial.read(); if (inByte == '0') { // print MbData for (int i=0;i<12;i++) { Serial.print("address: "); Serial.print(i); Serial.print("Data: "); Serial.println(Mb.MbData[i]); } } if (inByte == '1') {for (int i=0;i<12;i++) {Mb.MbData[i] = 0x0000;}} if (inByte == '2') {for (int i=0;i<12;i++) {Mb.MbData[i] = 0xFFFF;}} if (inByte == '3') {for (int i=0;i<12;i++) {Mb.MbData[i] = 0x5555;}} if (inByte == '4') {for (int i=0;i<12;i++) {Mb.MbData[i] = 0xAAAA;}} } // Mb.MbmRun(); Mb.MbsRun(); }
三、MgsModbus 源码
1. MgsModbus.h
/* MgsModbus.h - an Arduino library for a Modbus TCP master and slave. V-0.1.1 Copyright (C) 2013 Marco Gerritse written and tested with Arduino 1.0 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 3 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, see <http://www.gnu.org/licenses/>. For this library the following library is used as start point: [1] Mudbus.h - an Arduino library for a Modbus TCP slave. Copyright (C) 2011 Dee Wykoff [2] Function codes 15 & 16 by Martin Pettersson The following references are used to write this library: [3] Open Modbus/Tcp specification, Release 1.0, 29 March 1999 By Andy Swales, Schneider Electric [4] Modbus application protocol specification V1.1b3, 26 april 202 From http:/www.modbus.org External software used for testing: [5] modpoll - www.modbusdriver.com/modpoll.html [6] ananas - www.tuomio.fi/ananas [7] mod_rssim - www.plcsimulator.org [8] modbus master - www.cableone.net/mblansett/ This library use a single block of memory for all modbus data (mbData[] array). The same data can be reached via several modbus functions, either via a 16 bit access or via an access bit. The length of MbData must at least 1. For the master the following modbus functions are implemented: 1, 2, 3, 4, 5, 6, 15, 16 For the slave the following modbus functions are implemented: 1, 2, 3, 4, 5, 6, 15, 16 The internal and external addresses are 0 (zero) based V-0.1.1 2013-06-02 bugfix V-0.1.0 2013-03-02 initinal version */ #include "Arduino.h" #include <SPI.h> #include <Ethernet.h> #ifndef MgsModbus_h #define MgsModbus_h #define MbDataLen 30 // length of the MdData array #define MB_PORT 502 enum MB_FC { MB_FC_NONE = 0, MB_FC_READ_COILS = 1, MB_FC_READ_DISCRETE_INPUT = 2, MB_FC_READ_REGISTERS = 3, MB_FC_READ_INPUT_REGISTER = 4, MB_FC_WRITE_COIL = 5, MB_FC_WRITE_REGISTER = 6, MB_FC_WRITE_MULTIPLE_COILS = 15, MB_FC_WRITE_MULTIPLE_REGISTERS = 16 }; class MgsModbus { public: // general MgsModbus(); word MbData[MbDataLen]; // memory block that holds all the modbus user data boolean GetBit(word Number); boolean SetBit(word Number,boolean Data); // returns true when the number is in the MbData range // modbus master void Req(MB_FC FC, word Ref, word Count, word Pos); void MbmRun(); IPAddress remSlaveIP; // modbus slave void MbsRun(); word GetDataLen(); private: // general MB_FC SetFC(int fc); // modbus master uint8_t MbmByteArray[260]; // send and recieve buffer MB_FC MbmFC; int MbmCounter; void MbmProcess(); word MbmPos; word MbmBitCount; //modbus slave uint8_t MbsByteArray[260]; // send and recieve buffer MB_FC MbsFC; }; #endif
2. MgsModbus.cpp
#include "MgsModbus.h" // For Arduino 1.0 EthernetServer MbServer(MB_PORT); EthernetClient MbmClient; // #define DEBUG MgsModbus::MgsModbus() { } //****************** Send data for ModBusMaster **************** void MgsModbus::Req(MB_FC FC, word Ref, word Count, word Pos) { MbmFC = FC; byte ServerIp[] = {192,168,0,12}; MbmByteArray[0] = 0; // ID high byte MbmByteArray[1] = 1; // ID low byte MbmByteArray[2] = 0; // protocol high byte MbmByteArray[3] = 0; // protocol low byte MbmByteArray[5] = 6; // Lenght low byte; MbmByteArray[4] = 0; // Lenght high byte MbmByteArray[6] = 1; // unit ID MbmByteArray[7] = FC; // function code MbmByteArray[8] = highByte(Ref); MbmByteArray[9] = lowByte(Ref); //****************** Read Coils (1) & Read Input discretes (2) ********************** if(FC == MB_FC_READ_COILS || FC == MB_FC_READ_DISCRETE_INPUT) { if (Count < 1) {Count = 1;} if (Count > 125) {Count = 2000;} MbmByteArray[10] = highByte(Count); MbmByteArray[11] = lowByte(Count); } //****************** Read Registers (3) & Read Input registers (4) ****************** if(FC == MB_FC_READ_REGISTERS || FC == MB_FC_READ_INPUT_REGISTER) { if (Count < 1) {Count = 1;} if (Count > 125) {Count = 125;} MbmByteArray[10] = highByte(Count); MbmByteArray[11] = lowByte(Count); } //****************** Write Coil (5) ********************** if(MbmFC == MB_FC_WRITE_COIL) { if (GetBit(Pos)) {MbmByteArray[10] = 0xFF;} else {MbmByteArray[10] = 0;} // 0xFF coil on 0x00 coil off MbmByteArray[11] = 0; // always zero } //****************** Write Register (6) ****************** if(MbmFC == MB_FC_WRITE_REGISTER) { MbmByteArray[10] = highByte(MbData[Pos]); MbmByteArray[11] = lowByte(MbData[Pos]); } //****************** Write Multiple Coils (15) ********************** // not fuly tested if(MbmFC == MB_FC_WRITE_MULTIPLE_COILS) { if (Count < 1) {Count = 1;} if (Count > 800) {Count = 800;} MbmByteArray[10] = highByte(Count); MbmByteArray[11] = lowByte(Count); MbmByteArray[12] = (Count + 7) /8; MbmByteArray[4] = highByte(MbmByteArray[12] + 7); // Lenght high byte MbmByteArray[5] = lowByte(MbmByteArray[12] + 7); // Lenght low byte; for (int i=0; i<Count; i++) { bitWrite(MbmByteArray[13+(i/8)],i-((i/8)*8),GetBit(Pos+i)); } } //****************** Write Multiple Registers (16) ****************** if(MbmFC == MB_FC_WRITE_MULTIPLE_REGISTERS) { if (Count < 1) {Count = 1;} if (Count > 100) {Count = 100;} MbmByteArray[10] = highByte(Count); MbmByteArray[11] = lowByte(Count); MbmByteArray[12] = (Count*2); MbmByteArray[4] = highByte(MbmByteArray[12] + 7); // Lenght high byte MbmByteArray[5] = lowByte(MbmByteArray[12] + 7); // Lenght low byte; for (int i=0; i<Count;i++) { MbmByteArray[(i*2)+13] = highByte (MbData[Pos + i]); MbmByteArray[(i*2)+14] = lowByte (MbData[Pos + i]); } } //****************** ?? ****************** if (MbmClient.connect(ServerIp,502)) { #ifdef DEBUG Serial.println("connected with modbus slave"); Serial.print("Master request: "); for(int i=0;i<MbmByteArray[5]+6;i++) { if(MbmByteArray[i] < 16){Serial.print("0");} Serial.print(MbmByteArray[i],HEX); if (i != MbmByteArray[5]+5) {Serial.print(".");} else {Serial.println();} } #endif for(int i=0;i<MbmByteArray[5]+6;i++) { MbmClient.write(MbmByteArray[i]); } MbmCounter = 0; MbmByteArray[7] = 0; MbmPos = Pos; MbmBitCount = Count; } else { #ifdef DEBUG Serial.println("connection with modbus master failed"); #endif MbmClient.stop(); } } //****************** Recieve data for ModBusMaster **************** void MgsModbus::MbmRun() { //****************** Read from socket **************** while (MbmClient.available()) { MbmByteArray[MbmCounter] = MbmClient.read(); if (MbmCounter > 4) { if (MbmCounter == MbmByteArray[5] + 5) { // the full answer is recieved MbmClient.stop(); MbmProcess(); #ifdef DEBUG Serial.println("recieve klaar"); #endif } } MbmCounter++; } } void MgsModbus::MbmProcess() { MbmFC = SetFC(int (MbmByteArray[7])); #ifdef DEBUG for (int i=0;i<MbmByteArray[5]+6;i++) { if(MbmByteArray[i] < 16) {Serial.print("0");} Serial.print(MbmByteArray[i],HEX); if (i != MbmByteArray[5]+5) {Serial.print("."); } else {Serial.println();} } #endif //****************** Read Coils (1) & Read Input discretes (2) ********************** if(MbmFC == MB_FC_READ_COILS || MbmFC == MB_FC_READ_DISCRETE_INPUT) { word Count = MbmByteArray[8] * 8; if (MbmBitCount < Count) { Count = MbmBitCount; } for (int i=0;i<Count;i++) { if (i + MbmPos < MbDataLen * 16) { SetBit(i + MbmPos,bitRead(MbmByteArray[(i/8)+9],i-((i/8)*8))); } } } //****************** Read Registers (3) & Read Input registers (4) ****************** if(MbmFC == MB_FC_READ_REGISTERS || MbmFC == MB_FC_READ_INPUT_REGISTER) { word Pos = MbmPos; for (int i=0;i<MbmByteArray[8];i=i+2) { if (Pos < MbDataLen) { MbData[Pos] = (MbmByteArray[i+9] * 0x100) + MbmByteArray[i+1+9]; Pos++; } } } //****************** Write Coil (5) ********************** if(MbmFC == MB_FC_WRITE_COIL){ } //****************** Write Register (6) ****************** if(MbmFC == MB_FC_WRITE_REGISTER){ } //****************** Write Multiple Coils (15) ********************** if(MbmFC == MB_FC_WRITE_MULTIPLE_COILS){ } //****************** Write Multiple Registers (16) ****************** if(MbmFC == MB_FC_WRITE_MULTIPLE_REGISTERS){ } } //****************** Recieve data for ModBusSlave **************** void MgsModbus::MbsRun() { //****************** Read from socket **************** EthernetClient client = MbServer.available(); if(client.available()) { delay(10); int i = 0; while(client.available()) { MbsByteArray[i] = client.read(); i++; } MbsFC = SetFC(MbsByteArray[7]); //Byte 7 of request is FC } int Start, WordDataLength, ByteDataLength, CoilDataLength, MessageLength; //****************** Read Coils (1 & 2) ********************** if(MbsFC == MB_FC_READ_COILS || MbsFC == MB_FC_READ_DISCRETE_INPUT) { Start = word(MbsByteArray[8],MbsByteArray[9]); CoilDataLength = word(MbsByteArray[10],MbsByteArray[11]); ByteDataLength = CoilDataLength / 8; if(ByteDataLength * 8 < CoilDataLength) ByteDataLength++; CoilDataLength = ByteDataLength * 8; MbsByteArray[5] = ByteDataLength + 3; //Number of bytes after this one. MbsByteArray[8] = ByteDataLength; //Number of bytes after this one (or number of bytes of data). for(int i = 0; i < ByteDataLength ; i++) { MbsByteArray[9 + i] = 0; // To get all remaining not written bits zero for(int j = 0; j < 8; j++) { bitWrite(MbsByteArray[9 + i], j, GetBit(Start + i * 8 + j)); } } MessageLength = ByteDataLength + 9; client.write(MbsByteArray, MessageLength); MbsFC = MB_FC_NONE; } //****************** Read Registers (3 & 4) ****************** if(MbsFC == MB_FC_READ_REGISTERS || MbsFC == MB_FC_READ_INPUT_REGISTER) { Start = word(MbsByteArray[8],MbsByteArray[9]); WordDataLength = word(MbsByteArray[10],MbsByteArray[11]); ByteDataLength = WordDataLength * 2; MbsByteArray[5] = ByteDataLength + 3; //Number of bytes after this one. MbsByteArray[8] = ByteDataLength; //Number of bytes after this one (or number of bytes of data). for(int i = 0; i < WordDataLength; i++) { MbsByteArray[ 9 + i * 2] = highByte(MbData[Start + i]); MbsByteArray[10 + i * 2] = lowByte(MbData[Start + i]); } MessageLength = ByteDataLength + 9; client.write(MbsByteArray, MessageLength); MbsFC = MB_FC_NONE; } //****************** Write Coil (5) ********************** if(MbsFC == MB_FC_WRITE_COIL) { Start = word(MbsByteArray[8],MbsByteArray[9]); if (word(MbsByteArray[10],MbsByteArray[11]) == 0xFF00){SetBit(Start,true);} if (word(MbsByteArray[10],MbsByteArray[11]) == 0x0000){SetBit(Start,false);} MbsByteArray[5] = 2; //Number of bytes after this one. MessageLength = 8; client.write(MbsByteArray, MessageLength); MbsFC = MB_FC_NONE; } //****************** Write Register (6) ****************** if(MbsFC == MB_FC_WRITE_REGISTER) { Start = word(MbsByteArray[8],MbsByteArray[9]); MbData[Start] = word(MbsByteArray[10],MbsByteArray[11]); MbsByteArray[5] = 6; //Number of bytes after this one. MessageLength = 12; client.write(MbsByteArray, MessageLength); MbsFC = MB_FC_NONE; } //****************** Write Multiple Coils (15) ********************** if(MbsFC == MB_FC_WRITE_MULTIPLE_COILS) { Start = word(MbsByteArray[8],MbsByteArray[9]); CoilDataLength = word(MbsByteArray[10],MbsByteArray[11]); MbsByteArray[5] = 6; for(int i = 0; i < CoilDataLength; i++) { SetBit(Start + i,bitRead(MbsByteArray[13 + (i/8)],i-((i/8)*8))); } MessageLength = 12; client.write(MbsByteArray, MessageLength); MbsFC = MB_FC_NONE; } //****************** Write Multiple Registers (16) ****************** if(MbsFC == MB_FC_WRITE_MULTIPLE_REGISTERS) { Start = word(MbsByteArray[8],MbsByteArray[9]); WordDataLength = word(MbsByteArray[10],MbsByteArray[11]); ByteDataLength = WordDataLength * 2; MbsByteArray[5] = 6; for(int i = 0; i < WordDataLength; i++) { MbData[Start + i] = word(MbsByteArray[ 13 + i * 2],MbsByteArray[14 + i * 2]); } MessageLength = 12; client.write(MbsByteArray, MessageLength); MbsFC = MB_FC_NONE; } } //****************** ?? ****************** MB_FC MgsModbus::SetFC(int fc) { MB_FC FC; FC = MB_FC_NONE; if(fc == 1) FC = MB_FC_READ_COILS; if(fc == 2) FC = MB_FC_READ_DISCRETE_INPUT; if(fc == 3) FC = MB_FC_READ_REGISTERS; if(fc == 4) FC = MB_FC_READ_INPUT_REGISTER; if(fc == 5) FC = MB_FC_WRITE_COIL; if(fc == 6) FC = MB_FC_WRITE_REGISTER; if(fc == 15) FC = MB_FC_WRITE_MULTIPLE_COILS; if(fc == 16) FC = MB_FC_WRITE_MULTIPLE_REGISTERS; return FC; } word MgsModbus::GetDataLen() { return MbDataLen; } boolean MgsModbus::GetBit(word Number) { int ArrayPos = Number / 16; int BitPos = Number - ArrayPos * 16; boolean Tmp = bitRead(MbData[ArrayPos],BitPos); return Tmp; } boolean MgsModbus::SetBit(word Number,boolean Data) { int ArrayPos = Number / 16; int BitPos = Number - ArrayPos * 16; boolean Overrun = ArrayPos > MbDataLen * 16; // check for data overrun if (!Overrun){ bitWrite(MbData[ArrayPos],BitPos,Data); } return Overrun; }
扩展阅读
- 《用 ADAM-6052 学习 ModBus TCP 协议》系列文章索引
http://davidrobot.com/2017/01/adam6052_modbus-tcp_index.html