Tôi đã làm một bài viết dài về RS485 .
Đầu tiên, việc sử dụng Megas của bạn có vẻ quá mức, trừ khi bạn đã có sẵn chúng. Một Uno, hoặc một trong những bảng yếu tố hình thức nhỏ hơn dường như là hoàn toàn phù hợp để giám sát một vài công tắc và bật một vài đèn.
Ngay cả Rpi dường như không cần thiết. Một Uno khác có thể dễ dàng theo dõi các đường RS485 của bạn và kết nối qua Ethernet (tấm chắn Ethernet) với phần còn lại của ngôi nhà hoặc bất cứ điều gì bạn đang làm.
... không có điều khoản nào để tránh "va chạm" khi giao tiếp ...
Chà, bạn xây dựng địa chỉ đó. Bạn cung cấp cho mỗi Mega một địa chỉ (ví dụ: được lưu trữ trong EEPROM) và sau đó bạn "giải quyết" địa chỉ bạn muốn và sau đó chờ phản hồi. Ví dụ: trong mã từ trang của tôi ở trên:
Bậc thầy
#include "RS485_protocol.h"
#include <SoftwareSerial.h>
const byte ENABLE_PIN = 4;
const byte LED_PIN = 13;
SoftwareSerial rs485 (2, 3); // receive pin, transmit pin
// callback routines
void fWrite (const byte what)
{
rs485.write (what);
}
int fAvailable ()
{
return rs485.available ();
}
int fRead ()
{
return rs485.read ();
}
void setup()
{
rs485.begin (28800);
pinMode (ENABLE_PIN, OUTPUT); // driver output enable
pinMode (LED_PIN, OUTPUT); // built-in LED
} // end of setup
byte old_level = 0;
void loop()
{
// read potentiometer
byte level = analogRead (0) / 4;
// no change? forget it
if (level == old_level)
return;
// assemble message
byte msg [] = {
1, // device 1
2, // turn light on
level // to what level
};
// send to slave
digitalWrite (ENABLE_PIN, HIGH); // enable sending
sendMsg (fWrite, msg, sizeof msg);
digitalWrite (ENABLE_PIN, LOW); // disable sending
// receive response
byte buf [10];
byte received = recvMsg (fAvailable, fRead, buf, sizeof buf);
digitalWrite (LED_PIN, received == 0); // turn on LED if error
// only send once per successful change
if (received)
old_level = level;
} // end of loop
Bạn định cấu hình bộ thu phát RS485 để gửi hoặc nhận. Thông thường nó ở chế độ nhận và bạn chuyển sang chế độ gửi để gửi "gói" dữ liệu. (Xem phần "cho phép gửi" ở trên).
Bây giờ thiết bị được đánh địa chỉ chuyển bộ thu phát của nó sang chế độ "gửi" và trả lời. Mã trong thư viện tôi đã viết đã hết thời gian, vì vậy nếu nô lệ cụ thể đó đã chết, thời gian nhận sẽ hết. Bạn có thể nhớ rằng ở cuối chủ và cố gắng giao tiếp với nó ít thường xuyên hơn. Hoặc bạn có thể không quan tâm nếu thời gian chờ là ngắn.
Nô lệ
#include <SoftwareSerial.h>
#include "RS485_protocol.h"
SoftwareSerial rs485 (2, 3); // receive pin, transmit pin
const byte ENABLE_PIN = 4;
void fWrite (const byte what)
{
rs485.write (what);
}
int fAvailable ()
{
return rs485.available ();
}
int fRead ()
{
return rs485.read ();
}
void setup()
{
rs485.begin (28800);
pinMode (ENABLE_PIN, OUTPUT); // driver output enable
}
void loop()
{
byte buf [10];
byte received = recvMsg (fAvailable, fRead, buf, sizeof (buf));
if (received)
{
if (buf [0] != 1)
return; // not my device
if (buf [1] != 2)
return; // unknown command
byte msg [] = {
0, // device 0 (master)
3, // turn light on command received
};
delay (1); // give the master a moment to prepare to receive
digitalWrite (ENABLE_PIN, HIGH); // enable sending
sendMsg (fWrite, msg, sizeof msg);
digitalWrite (ENABLE_PIN, LOW); // disable sending
analogWrite (11, buf [2]); // set light level
} // end if something received
} // end of loop
Lưu ý : Mã ví dụ từ trang được liên kết của tôi không đọc địa chỉ từ EEPROM, tuy nhiên đó là chuyện nhỏ để thực hiện.
Tôi không thể thấy bất kỳ lý do cụ thể nào khiến bạn không thể thẩm vấn nô lệ khá thường xuyên. Ông chủ sẽ làm gì khác? Bạn cũng có thể thiết lập máy chủ như một máy chủ HTTP để bạn có thể nói chuyện với nó từ máy tính xách tay của bạn hoặc một số phần khác của ngôi nhà.
Hệ thống dây điện của tôi:
Để thể hiện ý tưởng, tôi đã thiết lập hai chiếc Unos, được kết nối qua khoảng 8 m dây chuông (thậm chí không xoắn đôi và chắc chắn không được che chắn).
Một Uno đang chạy bản phác thảo bảng ASCII tiêu chuẩn (ở 9600 baud), chân Tx của nó đi vào LTC1480, và sau đó các chân A / B đi đến dây chuông.
Uno khác được kết nối dưới dạng giao diện USB (Đặt lại được kết nối với mặt đất) và nó chỉ lặp lại bất cứ điều gì có trên chân Tx với USB.
Theo như tôi có thể thấy, nó hoạt động hoàn hảo.
Tôi sẽ không cần bất kỳ cuộc thăm dò nào, vì với cách tiếp cận của bạn, tôi có thể thực hiện bối cảnh "một chủ / nhiều nô lệ" ... nhưng với một chủ "di chuyển". Thê nay đung không?
Câu trả lời của tôi ở trên cho rằng nô lệ có nhiều khả năng thất bại hơn chủ (không thể nghĩ tại sao điều này lại xảy ra, nhưng có lẽ dựa trên thực tế là có nhiều nô lệ hơn chủ và chủ không điều khiển những thứ như ánh sáng ).
Tôi thấy Arduinos của mình cực kỳ đáng tin cậy khi làm những việc đơn giản (như mở khóa cửa khi xuất hiện thẻ RFID).
Bạn có thể hình dung xây dựng một vị trí dự phòng vào nô lệ. Rốt cuộc, nếu họ được thăm dò ý kiến mỗi giây và sau đó không có cuộc thăm dò nào, họ có thể hình dung một cách có thể hình dung là chủ nhân, có lẽ theo thứ tự tăng dần số thiết bị, để tránh xung đột. Một phần của cuộc bỏ phiếu này của "chủ mới" có thể là kiểm tra "chủ gốc" để xem liệu nó đã sẵn sàng để tiếp tục nhiệm vụ hay chưa.
Thư viện mà tôi mô tả trên trang được liên kết của mình có kiểm tra lỗi được tích hợp trong đó, ý tưởng là bằng cách kiểm tra CRC trên gói bạn đảm bảo rằng bạn không truy cập được một nửa thông qua gói và hiểu sai dữ liệu trong đó.
Bạn cũng có thể xây dựng một số lần ngẫu nhiên để giải quyết bế tắc nếu hai nô lệ cố gắng trở thành chủ nhân cùng một lúc. Nếu một nô lệ thất bại, nó có thể đợi một thời gian ngẫu nhiên (và tăng dần) trước khi thử lại để cho một nô lệ khác có cơ hội làm điều đó.
Tôi chỉ muốn lưu ý rằng cơ hội va chạm gói là khá thấp. Bạn chỉ gửi các gói khi nhấn công tắc hoặc khi cần bật đèn.
Gerben đúng, nhưng tôi sẽ lo lắng rằng một thông báo về việc chuyển đổi đã không được chú ý. Một cách giải quyết có thể xảy ra ở đây là thay vì những nô lệ trả lời các thay đổi trạng thái đối với truy vấn, họ trả lời với trạng thái hiện tại. Vì vậy, nó có thể đi:
Master: Slave 3, what is your status?
Slave 3: Lights 1 and 4 on, lights 2 and 3 off.
Tôi sẽ không cần bất kỳ cuộc thăm dò nào, vì với cách tiếp cận của bạn, tôi có thể thực hiện bối cảnh "một chủ / nhiều nô lệ" ... nhưng với một chủ "di chuyển". Thê nay đung không?
Tôi đã suy nghĩ về điều này một chút và tôi nghĩ bây giờ bạn có thể tạo ra một hệ thống về cơ bản là không có chủ. Nó có thể hoạt động như thế này:
Mỗi thiết bị có địa chỉ riêng, được lấy từ EEPROM (hoặc công tắc DIP). ví dụ. 1, 2, 3, 4, 5 ...
Bạn chọn một dải địa chỉ bạn sẽ sử dụng (ví dụ: tối đa là 10)
Khi thiết bị bật nguồn, đầu tiên nó sẽ lắng nghe các thiết bị khác "nói" trên xe buýt. Hy vọng nó sẽ nghe thấy ít nhất một cái khác (nếu không, xem bên dưới).
Chúng tôi quyết định một "gói tin" cố định, giả sử 50 byte bao gồm địa chỉ, CRC, v.v. Ở mức 9600 baud sẽ mất 52 ms để gửi.
Mỗi thiết bị có một "khe" thời gian và chờ đến lượt để nói chuyện với xe buýt.
Khi timelot của nó đến, nó sẽ chuyển sang chế độ đầu ra và phát gói tin bao gồm địa chỉ của chính nó. Do đó, tất cả các thiết bị khác hiện có thể đọc trạng thái của nó (và hành động theo nó nếu cần thiết). Ví dụ. thiết bị 1 có thể báo cáo rằng công tắc 3 đã đóng, có nghĩa là thiết bị 2 phải bật đèn.
Lý tưởng nhất là bạn biết thời gian của bạn đã đến vì địa chỉ thiết bị của bạn lớn hơn gói bạn vừa nghe. Ví dụ. Bạn là thiết bị 3. Bạn vừa nghe thiết bị 2 thông báo trạng thái của nó. Bây giờ đến lượt bạn. Tất nhiên, bạn quấn quanh với số lượng tối đa, vì vậy sau thiết bị 10, bạn quay lại thiết bị 1.
Nếu một thiết bị bị thiếu và không phản hồi, bạn cung cấp cho thiết bị đó một nửa thời gian để trả lời, và sau đó giả sử thiết bị đã chết và mọi thiết bị trên xe buýt đều cho rằng thời gian tiếp theo đã bắt đầu. (ví dụ: Bạn đã nghe thiết bị 2, thiết bị 3 sẽ phản hồi, sau 25 ms không hoạt động, thiết bị 4 hiện có thể phản hồi). Quy tắc này cung cấp cho thiết bị 25 ms để phản hồi, sẽ rất nhiều ngay cả khi thiết bị đang phục vụ ngắt hoặc đại loại như thế.
Nếu thiếu nhiều thiết bị theo trình tự, bạn sẽ tính khoảng cách 25 ms cho mỗi thiết bị bị thiếu, cho đến khi đến lượt của bạn.
Khi bạn nhận được ít nhất một phản hồi, thời gian được đồng bộ hóa, do đó, bất kỳ sự trôi dạt nào trong đồng hồ sẽ bị hủy bỏ.
Khó khăn duy nhất ở đây là khi bật nguồn ban đầu (có thể xảy ra đồng thời nếu mất điện cho tòa nhà và sau đó được khôi phục), hiện tại không có thiết bị nào phát sóng trạng thái của nó và do đó không có gì để đồng bộ hóa.
Trong trường hợp đó:
Nếu sau khi nghe đủ lâu để nghe tất cả các thiết bị (ví dụ: 250 ms) và không nghe thấy gì, thiết bị sẽ tạm thời cho rằng đó là thiết bị đầu tiên và phát sóng. Tuy nhiên, có thể hai thiết bị sẽ làm điều đó cùng một lúc và do đó không bao giờ nghe thấy nhau.
Nếu một thiết bị không nghe thấy từ một thiết bị khác, nó sẽ phân bổ thời gian giữa các lần phát ngẫu nhiên (có thể gieo hạt tạo số ngẫu nhiên từ số thiết bị của nó, để tránh tất cả các thiết bị "ngẫu nhiên" làm choáng các phát sóng với cùng một lượng).
Điều này đáng kinh ngạc bởi số lượng thời gian bổ sung sẽ không thành vấn đề, bởi vì dù sao cũng không có ai lắng nghe.
Sớm hay muộn một thiết bị sẽ được sử dụng độc quyền xe buýt và những thiết bị khác sau đó có thể đồng bộ hóa với nó theo cách thông thường.
Khoảng cách ngẫu nhiên này giữa các lần thử giao tiếp tương tự như Ethernet đã từng làm khi nhiều thiết bị chia sẻ một cáp đồng trục.
Bản demo của hệ thống miễn phí
Đây là một thử thách thú vị, vì vậy tôi đã đưa ra một bản demo thực hiện việc này mà không cần bất kỳ bậc thầy cụ thể nào, như được mô tả ở trên.
Trước tiên, bạn cần thiết lập địa chỉ thiết bị hiện tại và số lượng thiết bị, trong EEPROM, vì vậy hãy chạy bản phác thảo này, thay đổi myAddress
cho từng Arduino:
#include <EEPROM.h>
const byte myAddress = 3;
const byte numberOfDevices = 4;
void setup ()
{
if (EEPROM.read (0) != myAddress)
EEPROM.write (0, myAddress);
if (EEPROM.read (1) != numberOfDevices)
EEPROM.write (1, numberOfDevices);
} // end of setup
void loop () { }
Bây giờ tải nó lên từng thiết bị:
/*
Multi-drop RS485 device control demo.
Devised and written by Nick Gammon.
Date: 7 September 2015
Version: 1.0
Licence: Released for public use.
For RS485_non_blocking library see: http://www.gammon.com.au/forum/?id=11428
For JKISS32 see: http://forum.arduino.cc/index.php?topic=263849.0
*/
#include <RS485_non_blocking.h>
#include <SoftwareSerial.h>
#include <EEPROM.h>
// the data we broadcast to each other device
struct
{
byte address;
byte switches [10];
int status;
} message;
const unsigned long BAUD_RATE = 9600;
const float TIME_PER_BYTE = 1.0 / (BAUD_RATE / 10.0); // seconds per sending one byte
const unsigned long PACKET_LENGTH = ((sizeof (message) * 2) + 6); // 2 bytes per payload byte plus STX/ETC/CRC
const unsigned long PACKET_TIME = TIME_PER_BYTE * PACKET_LENGTH * 1000000; // microseconds
// software serial pins
const byte RX_PIN = 2;
const byte TX_PIN = 3;
// transmit enable
const byte XMIT_ENABLE_PIN = 4;
// debugging pins
const byte OK_PIN = 6;
const byte TIMEOUT_PIN = 7;
const byte SEND_PIN = 8;
const byte SEARCHING_PIN = 9;
const byte ERROR_PIN = 10;
// action pins (demo)
const byte LED_PIN = 13;
const byte SWITCH_PIN = A0;
// times in microseconds
const unsigned long TIME_BETWEEN_MESSAGES = 3000;
unsigned long noMessagesTimeout;
byte nextAddress;
unsigned long lastMessageTime;
unsigned long lastCommsTime;
unsigned long randomTime;
SoftwareSerial rs485 (RX_PIN, TX_PIN); // receive pin, transmit pin
// what state we are in
enum {
STATE_NO_DEVICES,
STATE_RECENT_RESPONSE,
STATE_TIMED_OUT,
} state;
// callbacks for the non-blocking RS485 library
size_t fWrite (const byte what)
{
rs485.write (what);
}
int fAvailable ()
{
return rs485.available ();
}
int fRead ()
{
lastCommsTime = micros ();
return rs485.read ();
}
// RS485 library instance
RS485 myChannel (fRead, fAvailable, fWrite, 20);
// from EEPROM
byte myAddress; // who we are
byte numberOfDevices; // maximum devices on the bus
// Initial seed for JKISS32
static unsigned long x = 123456789,
y = 234567891,
z = 345678912,
w = 456789123,
c = 0;
// Simple Random Number Generator
unsigned long JKISS32 ()
{
long t;
y ^= y << 5;
y ^= y >> 7;
y ^= y << 22;
t = z + w + c;
z = w;
c = t < 0;
w = t & 2147483647;
x += 1411392427;
return x + y + w;
} // end of JKISS32
void Seed_JKISS32 (const unsigned long newseed)
{
if (newseed != 0)
{
x = 123456789;
y = newseed;
z = 345678912;
w = 456789123;
c = 0;
}
} // end of Seed_JKISS32
void setup ()
{
// debugging prints
Serial.begin (115200);
// software serial for talking to other devices
rs485.begin (BAUD_RATE);
// initialize the RS485 library
myChannel.begin ();
// debugging prints
Serial.println ();
Serial.println (F("Commencing"));
myAddress = EEPROM.read (0);
Serial.print (F("My address is "));
Serial.println (int (myAddress));
numberOfDevices = EEPROM.read (1);
Serial.print (F("Max address is "));
Serial.println (int (numberOfDevices));
if (myAddress >= numberOfDevices)
Serial.print (F("** WARNING ** - device number is out of range, will not be detected."));
Serial.print (F("Packet length = "));
Serial.print (PACKET_LENGTH);
Serial.println (F(" bytes."));
Serial.print (F("Packet time = "));
Serial.print (PACKET_TIME);
Serial.println (F(" microseconds."));
// calculate how long to assume nothing is responding
noMessagesTimeout = (PACKET_TIME + TIME_BETWEEN_MESSAGES) * numberOfDevices * 2;
Serial.print (F("Timeout for no messages = "));
Serial.print (noMessagesTimeout);
Serial.println (F(" microseconds."));
// set up various pins
pinMode (XMIT_ENABLE_PIN, OUTPUT);
// demo action pins
pinMode (SWITCH_PIN, INPUT_PULLUP);
pinMode (LED_PIN, OUTPUT);
// debugging pins
pinMode (OK_PIN, OUTPUT);
pinMode (TIMEOUT_PIN, OUTPUT);
pinMode (SEND_PIN, OUTPUT);
pinMode (SEARCHING_PIN, OUTPUT);
pinMode (ERROR_PIN, OUTPUT);
// seed the PRNG
Seed_JKISS32 (myAddress + 1000);
state = STATE_NO_DEVICES;
nextAddress = 0;
randomTime = JKISS32 () % 500000; // microseconds
} // end of setup
// set the next expected address, wrap around at the maximum
void setNextAddress (const byte current)
{
nextAddress = current;
if (nextAddress >= numberOfDevices)
nextAddress = 0;
} // end of setNextAddress
// Here to process an incoming message
void processMessage ()
{
// we cannot receive a message from ourself
// someone must have given two devices the same address
if (message.address == myAddress)
{
digitalWrite (ERROR_PIN, HIGH);
while (true)
{ } // give up
} // can't receive our address
digitalWrite (OK_PIN, HIGH);
// handle the incoming message, depending on who it is from and the data in it
// make our LED match the switch of the previous device in sequence
if (message.address == (myAddress - 1))
digitalWrite (LED_PIN, message.switches [0]);
digitalWrite (OK_PIN, LOW);
} // end of processMessage
// Here to send our own message
void sendMessage ()
{
digitalWrite (SEND_PIN, HIGH);
memset (&message, 0, sizeof message);
message.address = myAddress;
// fill in other stuff here (eg. switch positions, analog reads, etc.)
message.switches [0] = digitalRead (SWITCH_PIN);
// now send it
digitalWrite (XMIT_ENABLE_PIN, HIGH); // enable sending
myChannel.sendMsg ((byte *) &message, sizeof message);
digitalWrite (XMIT_ENABLE_PIN, LOW); // disable sending
setNextAddress (myAddress + 1);
digitalWrite (SEND_PIN, LOW);
lastCommsTime = micros (); // we count our own send as activity
randomTime = JKISS32 () % 500000; // microseconds
} // end of sendMessage
void loop ()
{
// incoming message?
if (myChannel.update ())
{
memset (&message, 0, sizeof message);
int len = myChannel.getLength ();
if (len > sizeof message)
len = sizeof message;
memcpy (&message, myChannel.getData (), len);
lastMessageTime = micros ();
setNextAddress (message.address + 1);
processMessage ();
state = STATE_RECENT_RESPONSE;
} // end of message completely received
// switch states if too long a gap between messages
if (micros () - lastMessageTime > noMessagesTimeout)
state = STATE_NO_DEVICES;
else if (micros () - lastCommsTime > PACKET_TIME)
state = STATE_TIMED_OUT;
switch (state)
{
// nothing heard for a long time? We'll take over then
case STATE_NO_DEVICES:
if (micros () - lastCommsTime >= (noMessagesTimeout + randomTime))
{
Serial.println (F("No devices."));
digitalWrite (SEARCHING_PIN, HIGH);
sendMessage ();
digitalWrite (SEARCHING_PIN, LOW);
}
break;
// we heard from another device recently
// if it is our turn, respond
case STATE_RECENT_RESPONSE:
// we allow a small gap, and if it is our turn, we send our message
if (micros () - lastCommsTime >= TIME_BETWEEN_MESSAGES && myAddress == nextAddress)
sendMessage ();
break;
// a device did not respond in its slot time, move onto the next one
case STATE_TIMED_OUT:
digitalWrite (TIMEOUT_PIN, HIGH);
setNextAddress (nextAddress + 1);
lastCommsTime += PACKET_TIME;
digitalWrite (TIMEOUT_PIN, LOW);
state = STATE_RECENT_RESPONSE; // pretend we got the missing response
break;
} // end of switch on state
} // end of loop
Vì hiện tại, nếu bạn đóng một công tắc trên A0 (ngắn xuống đất), nó sẽ tắt một đèn LED (chân 13) trên thiết bị cao nhất tiếp theo. Điều này chứng tỏ rằng các thiết bị đang nói chuyện với nhau. Tất nhiên trong thực tế, bạn sẽ có một cái gì đó tinh vi hơn trong gói đang được phát sóng.
Tôi đã tìm thấy trong thử nghiệm rằng đèn LED dường như bật và tắt ngay lập tức.
Với tất cả các thiết bị bị ngắt kết nối, thiết bị đầu tiên được kết nối sẽ "săn" các thiết bị khác. Nếu bạn có đèn LED gỡ lỗi được kết nối như tôi đã làm, bạn có thể thấy đèn LED "tìm kiếm" bật sáng theo các khoảng thời gian ngẫu nhiên khi nó phát gói tin của nó với các khoảng trống thay đổi ngẫu nhiên. Khi bạn có kết nối thứ hai, họ ổn định và chỉ trao đổi thông tin. Tôi đã thử nghiệm với ba kết nối cùng một lúc.
Có lẽ nó sẽ đáng tin cậy hơn với Phần cứng - Tôi đã sử dụng SoftwareSerial để giúp gỡ lỗi. Một vài thay đổi nhỏ sẽ thực hiện điều đó.
Sửa đổi sơ đồ
Ảnh chụp màn hình của mã đang hoạt động
Những hình ảnh cho thấy mã làm việc. Đầu tiên, chỉ với một thiết bị được kết nối:
Bạn có thể thấy từ các xung ở đó thiết bị đang phát dữ liệu của mình theo các khoảng thời gian khác nhau ngẫu nhiên, để tránh tiếp tục đụng độ với một thiết bị khác được cấp nguồn cùng lúc.
Bây giờ chúng ta thấy các khối dữ liệu từ hai thiết bị, với khoảng cách kích thước gần giống nhau ở giữa. Tôi đã cấu hình nó cho bốn thiết bị, nhưng chỉ có hai thiết bị hiện diện, vì vậy chúng tôi thấy hai khối dữ liệu và hai khoảng trống.
Bây giờ với ba thiết bị trực tuyến, chúng tôi thấy ba khối dữ liệu và một khoảng trống, vì thiết bị bị thiếu được bỏ qua.
Nếu bạn đang kiểm tra các số liệu, chúng được thực hiện với tốc độ baud tăng gấp đôi, như một thử nghiệm, đến 19200 baud.
Kiểm tra chạy cáp
Để kiểm tra phần cứng phù hợp, tôi đã kết nối các thiết bị với hệ thống cáp UTP trong nhà. Tôi có cáp cat-5 chạy từ nhiều phòng khác nhau đến một bảng cắm trung tâm. Đi từ đầu này đến đầu kia (một quãng đường dài hợp lý) nó vẫn hoạt động tốt. Có 5 m cáp giữa Arduino và ổ cắm trên tường, để bắt đầu. Cộng thêm cáp 5 m ở đầu kia. Sau đó, có khoảng 2 x 15 m chạy từ các phòng đến phòng chuyển mạch, và bên trong đó có một dây cáp ngắn để kết nối chúng lại với nhau.
Điều này là với các bảng vẫn được lập trình để chạy ở 19200 baud.