45so 发表于 2015-3-16 22:38:31

Arduino rs485 Modbus 求助!!

各位好,我是Arduino 的新手,
最近遇到一個難題,我想用Arduino 去取得 數位電表中的電流值,
卻碰到 Modbus 這項讓我覺得苦惱的通訊協議,

我是使用 TTL 轉 RS485 (MAX485)模塊來達成與數位電表的RS485通訊,
搞不懂的地方是有

問題1 :
如果是 Arduino 去向 數位電表 讀取資料,
那Arduino 應該要設定成 Modbus Master 對吧?!

問題2 :
數位電表的說明文件是這樣寫的..

假設今天我電表是 slave 編號1
我是不是傳送 01 03 00 3A 00 02 +crc
就能夠讀取到 KW1 的數值呢?!

這段要求命令有沒有大大能講解一下,特別是 HI LO 到底是什麼?!
位址後面的括弧 又是什麼呢?!


奢求大大有沒有範例提點一下阿!!!~ 先謝了!!

bigwolf 发表于 2015-3-17 07:52:13

hi lo是高位和地位的意思,Modbus是问答式的,我觉得你的命令应该是正确,括号里面有可能是4000开始的地址吧,不行多试试。

i7456 发表于 2015-3-17 09:33:02

http://playground.arduino.cc/Code/ModbusMaster

45so 发表于 2015-3-17 12:41:22

本帖最后由 45so 于 2015-3-17 12:42 编辑

先感謝二位回覆
剛剛才注意到我文章發錯區了
應該發在求助區才對~真是抱歉

指令的部分我想也是沒問題的
用PC模擬Master 是能夠正確讀取到數值
但Arduino 上怎麼搞都是回傳連線錯誤

另外官方的文件小弟也有看過了,
但那個庫不知道是太久沒更新還是?
編譯一直過不了

網路上其他的庫也有試過,但不是無法連線不然就是回傳亂碼
亂碼的部分我也有確認過baud rate 是沒設定錯誤的!~

苦惱好幾天了啊

bigwolf 发表于 2015-3-17 12:58:00

用PC和Arduino连上试试,Arduino的外围接口用连仪表的那部分,看能不能收到PC的 01 03 00 3A 00 02 +crc指令。

i7456 发表于 2015-3-17 13:03:58

https://github.com/search?utf8=%E2%9C%93&q=arduino+modbus

45so 发表于 2015-3-17 14:12:18

我嘗試用這個庫來實驗
https://github.com/angeloc/simplemodbusng/tree/master/SimpleModbusMaster

並使用範例做修改
如下

#include <SimpleModbusMaster.h>

/* To communicate with a slave you need to create a
   packet that will contain all the information
   required to communicate to that slave.
   
   There are numerous counters for easy diagnostic.
   These are variables already implemented in a
   packet. You can set and clear these variables
   as needed.
   
   There are general modbus information counters:
   requests - contains the total requests to a slave
   successful_requests - contains the total successful requests
   total_errors - contains the total errors as a sum
   timeout - contains the total time out errors
   incorrect_id_returned - contains the total incorrect id returned errors
   incorrect_function_returned - contains the total incorrect function returned errors
   incorrect_bytes_returned - contains the total incorrect bytes returned errors
   checksum_failed - contains the total checksum failed errors
   buffer_errors - contains the total buffer errors

   And there are modbus specific exception counters:
   illegal_function - contains the total illegal function errors
   illegal_data_address - contains the total illegal data_address errors
   illegal_data_value - contains the total illegal data value errors
   misc_exceptions - contains the total miscellaneous returned exceptions
       
   And finally there is a variable called "connection" that
   at any given moment contains the current connection
   status of the packet. If true then the connection is
   active if false then communication will be stopped
   on this packet untill the programmer sets the "connection"
   variable to true explicitly. The reason for this is
   because of the time out involved in modbus communication.
   EACH faulty slave that's not communicating will slow down
   communication on the line with the time out value. E.g.
   Using a time out of 1500ms, if you have 10 slaves and 9 of them
   stops communicating the latency burden placed on communication
   will be 1500ms * 9 = 13,5 seconds!!!!

   modbus_update() returns the previously scanned false connection.
   You can use this as the index to your packet array to find out
   if the connection has failed in that packet and then react to it.
   You can then try to re-enable the connecion by setting the
   packet->connection attribute to true.
   The index will only be available for one loop cycle, after that
   it's cleared and ready to return the next false connection index
   if there is one else it will return the packet array size indicating
   everything is ok.
   
   All the error checking, updating and communication multitasking
   takes place in the background!

   In general to communicate with to a slave using modbus
   RTU you will request information using the specific
   slave id, the function request, the starting address
   and lastly the number of registers to request.
   Function 3 and 16 are supported. In addition to
   this broadcasting (id = 0) is supported on function 16.
   Constants are provided for:
   Function 3 -READ_HOLDING_REGISTERS
   Function 16 - PRESET_MULTIPLE_REGISTERS
   
   The example sketch will read a packet consisting
   of 3 registers from address 0 using function 3 from
   the GM7U LS Industrial PLC (id = 2) and then write
   another packet containing the same 3 registers and
   the counter information of both packets using function 16
   to address 3 in the PLC. Using the supplied PLC software
   you can then view in realtime what the values are in
   the PLC's registers.
*/   

// led to indicate that a communication error is present
#define connection_error_led 13

//////////////////// Port information ///////////////////
#define baud 9600
#define timeout 1000
#define polling 200 // the scan rate

// If the packets internal retry register matches
// the set retry count then communication is stopped
// on that packet. To re-enable the packet you must
// set the "connection" variable to true.
#define retry_count 10

// used to toggle the receive/transmit pin on the driver
#define TxEnablePin 2

// This is the easiest way to create new packets
// Add as many as you want. TOTAL_NO_OF_PACKETS
// is automatically updated.
enum
{
PACKET1,
// leave this last entry
TOTAL_NO_OF_PACKETS
};

// Create an array of Packets for modbus_update()
Packet packets;

// Create a packetPointer to access each packet
// individually. This is not required you can access
// the array explicitly. E.g. packets.id = 2;
// This does become tedious though...
packetPointer packet1 = &packets;
// The data from the PLC will be stored
// in the regs array
unsigned int regs;

void setup()
{
// read 3 registers starting at address 0
packet1->id = 1;
packet1->function = READ_HOLDING_REGISTERS;
packet1->address = 88;
packet1->no_of_registers = 2;
packet1->register_array = regs;
// P.S. the register_array entries above can be different arrays

// Initialize communication settings etc...
modbus_configure(baud, timeout, polling, retry_count, TxEnablePin, packets, TOTAL_NO_OF_PACKETS);

pinMode(connection_error_led, OUTPUT);
}

void loop()
{
unsigned int connection_status = modbus_update(packets);

if (connection_status != TOTAL_NO_OF_PACKETS)
{
    digitalWrite(connection_error_led, HIGH);
    // You could re-enable the connection by:
    //packets.connection = true;
}
else
    digitalWrite(connection_error_led, LOW);

// update the array with the counter data
regs = packet1->requests;
regs = packet1->successful_requests;
regs = packet1->total_errors;
}

但卻一直出現這樣的亂碼




Github 的庫我大多都找來用過了,只是新手遇到亂碼又沒範例參考真的好頭痛啊

i7456 发表于 2015-3-17 14:28:47

你想让程序实现什么功能,你上面的程序又实现了什么功能?
你打开的串口监视器,是监视的什么数据?

45so 发表于 2015-3-17 14:36:51

您問得我好心虛
我希望Arduino 能夠讀取數位電表內的讀數
上面的程序我認為是Arduino 作為 Master 向 Slave 的數位電表要求指定的記憶體位址的數值

串口監視器這段我最心虛
其實我看程序內似乎沒有Serial.print 什麼東西出來的樣子

還請大大盡量指教,我會虛心學習的!!

i7456 发表于 2015-3-17 17:02:33

本帖最后由 i7456 于 2015-3-17 17:03 编辑

45so 发表于 2015-3-17 14:36 static/image/common/back.gif
您問得我好心虛
我希望Arduino 能夠讀取數位電表內的讀數
上面的程序我認為是Arduino 作為 Master 向 Sla ...


// read 2 registers starting at address 88
packet1->id = 1;
packet1->function = READ_HOLDING_REGISTERS;
packet1->address = 88;
packet1->no_of_registers = 2;
packet1->register_array = regs;
上面这个packert1的参数配置,说明是要读id为1 的modbus slave的HOLDING_REGISTERS,起始地址是88,读的寄存器的个数是2个。
这里的参数需要改为你的电表中对应的地址及个数。
从电表里都回的数据,会保存在reg[]这个数组中。

为了方便调试,建议你用有2个串口的arduino来调试程序,一个用来与电表通讯,一个用来在串口监视器中观察读回的数据。这个库文件是使用Arduino的Serial来做通信的(在这里你能看到定义https://github.com/angeloc/simplemodbusng/blob/master/SimpleModbusMaster/SimpleModbusMaster.cpp),所以你需要用别的串口来输出你调试时想看到的数据。

// update the array with the counter data
regs = packet1->requests;
regs = packet1->successful_requests;
regs = packet1->total_errors;
这里的赋值,示例的程序是想向modbus slave中写入的数据,你只想读取数据,这里的变量赋值都是没用的。

45so 发表于 2015-3-17 18:18:10

這樣的意思就是如果我用 Arduino +RS485 模組~
用下圖的接法


就不能使用USB 來做監視用途是嗎?!

我有看到另一個範例有加上 SoftwareSerial 去定義其他點做串口使用

我再試試看!!~

i7456 发表于 2015-3-17 21:56:07

45so 发表于 2015-3-17 18:18 static/image/common/back.gif
這樣的意思就是如果我用 Arduino +RS485 模組~
用下圖的接法



是的,这样接线的话,电脑的串口和485芯片的TX都连在了mega328的rx上,容易出现问题。
另外你若想监视下arduino发出给电表的数据,换别的串口监视软件,看16进制数据。

45so 发表于 2015-3-18 00:57:42

感謝大大不斷解答小弟的疑惑,
想再請教關於 CRC 校驗的部分,
從數位電表的說明文件上看來,是必須要有CRC 校驗的,
依我目前修改的範例似乎是沒有CRC 這部分,
那這樣應該會無法完成通訊吧?!

我有找到一個庫,範例本身就帶了CRC 校驗的部分,
https://github.com/GDV0/ArduiModbus




#include <Modbus_RTU.h>

// Defines 1 Client and 1 Server devices
Modbus_RTU myServer = Modbus_RTU(0);
Modbus_RTU myClient = Modbus_RTU(0);

// Defines a message and data buffers
Modbus_Frame myFrame;
Modbus_Data myData;

unsigned short CRC16;

t_baud Baudrate;
unsigned long MsgTimeout;
int i;

void setup()
{
// Force type for each device
myServer.SetType(MDB_SERVER);
myClient.SetType(MDB_CLIENT);

// Preset Server address
myServer.Server_SetAddress(5);

// Initialize serial line
myClient.GetBaudrate(&Baudrate);
Serial.begin(Baudrate);

// Get the inter-frame time (equivalent to 3,5 char)
myClient.GetFrameTimeout(&MsgTimeout);

Serial.println("");
Serial.println("Test Modbus_RTU library");
Serial.println("=======================");

// Modbus test type
Serial.println("");
Serial.println("   Test Function code 03: Read Holding Registers");
Serial.println("   ---------------------------------------------");
}

void loop()
{
Serial.println("");
Serial.println("--> Read 5 registers from address 10");
Serial.println("      Result should be 0x1111, 0x2222, 0x3333, 0x4444, 0x5555");

// Build Client request
Serial.println("");
Serial.println("Request sent by the Client");
myClient.Client_ReadHoldingRegisters(5, 10, 5, &myFrame);
DisplayFrame(&myFrame);
   
// Build Server response
if (myServer.Server_Update(&myFrame))
{
    Serial.println("--> OK Response available");
    Serial.println("");
    Serial.println("Packet sent by the Server");
    DisplayFrame(&myFrame);
   
    // extract Data received by the Client
    myClient.Client_Update(&myFrame, &myData);
    Serial.println("");
    Serial.println("Data");
    DisplayData(&myData);
}
else
    Serial.println("--> No response available");

while(1)
{
}
}

// Function to display a complete frame (Debug mode)
void DisplayFrame(Modbus_Frame* msg)
{
int i;

Serial.print("Frame size ");
Serial.print(msg->length, DEC);
Serial.print(" -> ");
if (msg->length > 0)
{
    for (i = 0; i < msg->length; i++)
    {
      Serial.print((unsigned char)(msg->data)>>4, HEX);
      Serial.print((unsigned char)(msg->data)&0x0F, HEX);
      Serial.print(" ");
    }
Serial.println();
}
}

// Function to display Data received
void DisplayData(Modbus_Data* Data)
{
int i;

Serial.print("    ==> Number of data received = ");
Serial.println(Data->length, DEC);
if (Data->length > 0)
{
    Serial.print("      Data type = ");
    switch (Data->type)
    {
      case MDB_BIT:
          Serial.println("BIT");
          Serial.print("      Data = ");
          for (i = 0; i < Data->length; i++)
          {
            Serial.print(Data->data & 1, DEC);
            Serial.print((Data->data & 2)>>1, DEC);
            Serial.print((Data->data & 4)>>2, DEC);
            Serial.print((Data->data & 8)>>3, DEC);
            Serial.print((Data->data & 18)>>4, DEC);
            Serial.print((Data->data & 32)>>5, DEC);
            Serial.print((Data->data & 64)>>6, DEC);
            Serial.print((Data->data & 128)>>7, DEC);
          }
          break;
      case MDB_BYTE:
          Serial.println("BYTE");
          Serial.print("      Data = ");
          for (i = 0; i < Data->length; i++)
          {
            Serial.print("0x");
            Serial.print((unsigned char)(Data->data)>>4, HEX);
            Serial.print((unsigned char)(Data->data)&0x0F, HEX);
            Serial.print(" ");
          }
          break;
      case MDB_WORD:
          Serial.println("WORD");
          Serial.print("      Data = ");
          for (i = 0; i < Data->length; i++)
          {
            Serial.print("0x");
            Serial.print((unsigned char)(Data->data)>>4, HEX);
            Serial.print((unsigned char)(Data->data)&0x0F, HEX);
            Serial.print((unsigned char)(Data->data)>>4, HEX);
            Serial.print((unsigned char)(Data->data)&0x0F, HEX);
            Serial.print(" ");
          }
          break;
      default:
          Serial.println("Unknown");
          break;
    }
Serial.println();
}
}

void DisplayDataOnly(Modbus_Data* Data)
{
int i;
if (Data->length > 0)
{
    for (i = 0; i < Data->length; i++)
    {
      if (Data->type == MDB_BIT)
      {
      Serial.print(Data->data & 1, DEC);
      Serial.print((Data->data & 2)>>1, DEC);
      Serial.print((Data->data & 4)>>2, DEC);
      Serial.print((Data->data & 8)>>3, DEC);
      Serial.print((Data->data & 18)>>4, DEC);
      Serial.print((Data->data & 32)>>5, DEC);
      Serial.print((Data->data & 64)>>6, DEC);
      Serial.print((Data->data & 128)>>7, DEC);
      }
      if (Data->type == MDB_BYTE)
      {
      Serial.print("0x");
      Serial.print((unsigned char)(Data->data)>>4, HEX);
      Serial.print((unsigned char)(Data->data)&0x0F, HEX);
      Serial.print(" ");
      }
      if (Data->type == MDB_WORD)
      {
      Serial.print("0x");
      Serial.print((unsigned char)(Data->data)>>4, HEX);
      Serial.print((unsigned char)(Data->data)&0x0F, HEX);
      Serial.print((unsigned char)(Data->data)>>4, HEX);
      Serial.print((unsigned char)(Data->data)&0x0F, HEX);
      Serial.print(" ");
      }
    }
    Serial.println();
}
}


/******************************************************************************
*Callback functions
*Allow the user to define all device objects
******************************************************************************/

/******************************************************************************
* t_status Modbus_CB_GetRegister (unsigned short Addr, int* Value)
*   Callback function to read register value
* Parameters:
*   - Addr: Address of the register from 0x0000 to 0xFFFF
*   - Value: pointer to a variable which will contain the register value
* Return value:
*   - OK if register address exists
*   - NOK if register address doesn't exist
******************************************************************************/
t_status Modbus_CB_GetRegister(unsigned short Addr, int* Value)
{
t_status Status = OK;

// Value = f(Addr) To be defined by application
switch(Addr)
{
    case 10:
      *Value = 0x1111;
      break;
    case 11:
      *Value = 0x2222;
      break;
   case 12:
      *Value = 0x3333;
      break;
   case 13:
      *Value = 0x4444;
      break;
    case 14:
      *Value = 0x5555;
      break;
    default:
      Status = NOK;
}
return (Status);
}

這個範例看起來像是一個模擬通訊的功能,
因為我就算沒接上 RS485模組,他也能運行,
自己當自己是Master 同時也是 Slave,是這樣嗎?!

這個就自帶了CRC 校驗~
研究了一會,還是搞不太懂該如何將他實際運用...
從// Build Server response 之後的就看不懂了~

bigwolf 发表于 2015-3-18 07:58:56

要是我调试的话,先在Arduino上用最简单的串口通信把“ 01 03 00 3A 00 02 +crc”直接发给电表,然后把电表返回的代码收回来显示出来。用这个过程先看看硬件回路有没有问题。

硬件回路没问题后再切换到调试Modbus库,其实如果你不考虑扩展的话,固定几句话发发就行了,Modbus库都不需要:lol:lol:lol。

45so 发表于 2015-3-18 08:05:42

這樣聽起來感覺可行度滿高的!!~
直接使用 Serial.print()
跟Serial.read()
這樣嗎?!
页: [1] 2
查看完整版本: Arduino rs485 Modbus 求助!!