Cách kết nối nhiều Arduinos với Rpi để điều khiển đèn / công tắc nhà


8

Trong khi lên kế hoạch cho cơ sở hạ tầng sét (công tắc trên tường và đèn) của ngôi nhà mới của tôi (nó vẫn đang được xây dựng), tôi đã chọn đi qua con đường "tuyến đường tự động" và do nền tảng của tôi (tôi là một hệ thống / mạng "cũ" quản trị viên có kỹ năng lập trình và nhiều "niềm đam mê" và "vận động" nguồn mở) Tôi đang nghiêm túc cố gắng thực hiện nó với ba Arduinos và một RPi2.

Do số lượng / vị trí của các nút bấm và đèn tường, tôi muốn sử dụng ba MEGA giao thoa với cả các nút bấm tường và đèn chiếu sáng của các phòng. Hơn nữa, RPi2 (hoặc tương tự) sẽ được sử dụng làm "bộ điều khiển" để lập trình MEGAs đúng cách, khi cần và can thiệp vào một số thiết bị khác (màn hình cảm ứng, máy tính bảng wifi / điện thoại thông minh, điều khiển từ xa, v.v.) thông qua IP- mạng.

Lược đồ tôi đang cố gắng thực hiện tương tự như lược đồ này:

Dự thảo Schema nơi bạn nhìn thấy:

  • 9 đèn (L1 đến L9) mỗi đèn được điều khiển bằng rơle chuyên dụng riêng (từ 1 đến R9);
  • 7 nút ấn tường (WPB1 đến WPB7), được sử dụng để bật / tắt một hoặc nhiều đèn;
  • 3 MEGAs giao tiếp với nút nhấn và đèn tường;
  • 1 RPi2, đóng vai trò là "người giám sát" và "Cổng Internet / Ethernet".

Vấn đề kiến ​​trúc chính của tôi liên quan đến sự kết nối giữa RPI2 và MEGAs. Vì mỗi thiết bị sẽ được đặt cách nhau vài chục mét, tôi kết thúc bằng hai lựa chọn duy nhất (xin vui lòng, sửa cho tôi nếu tôi sai):

  1. Ethernet
  2. RS485

( BTW: Tôi rõ ràng loại trừ "kết nối không dây", vì tôi đã đặt tất cả các ống điện theo cách "tương thích". Nói cách khác: Tôi muốn tránh các công nghệ không dây trong "mạng điều khiển" )

Đối với Ethernet, tôi sẽ chọn nó làm tùy chọn thứ 2, do cả chi phí và độ phức tạp cao hơn một chút (cần có công tắc để bật nguồn; các vấn đề về cáp bổ sung; v.v.).

Tôi đã nghiên cứu rộng rãi " xe buýt RS485 " và thấy rằng nó tương đối dễ dàng - từ quan điểm vật lý - để thực hiện nó với hai dây trong cấu hình đa hướng .

Thật không may, từ "quan điểm ứng dụng", mọi thứ phức tạp hơn vì nó chỉ hỗ trợ truyền thông "bán song công" và tệ hơn nữa là không có điều khoản nào để tránh "va chạm" khi giao tiếp (đó là lý do tại sao, có lẽ là giao thức MODBUS - được sử dụng trên xe buýt RS485 - cung cấp một kịch bản "đơn chủ; nhiều nô lệ").

Trước các câu hỏi, tôi cần thêm một ràng buộc khác: Tôi muốn cơ sở hạ tầng "có khả năng chịu lỗi càng nhiều càng tốt", đặc biệt là đối với các vấn đề với BUS và / hoặc với "bộ điều khiển" (RPI2). Nói cách khác:

  • mỗi MEGA nên cho phép bật đèn riêng khi một trong những nút ấn riêng của nó yêu cầu điều này. Ví dụ:
    • nếu WPB1 điều khiển L2 và L3, nó cần phải hoạt động ngay cả khi BUS bị hỏng hoặc RPI2 bị tắt;
    • nếu WPB3 điều khiển L4 và L9, thì bus / RPI2 có vấn đề, chỉ L4 sẽ được bật khi nhấn WPB3;

Vì vậy, sau tất cả những điều trên, đây là câu hỏi của tôi:

  1. là xe buýt đa năng RS485 hai dây, một lựa chọn phù hợp cho kịch bản của tôi?

  2. nếu không, tôi có thể điều tra những giải pháp nào (có thể rẻ và đơn giản)?

  3. nếu có, đó là logic mà tôi cần triển khai trên MEGAs, như:

    • a) họ cần đóng vai trò là "nô lệ", liên quan đến RPI2 khi bật đèn được chỉ huy bằng nút ấn được kết nối với các MEGA khác hoặc khi RP2 quyết định bật một số đèn (ví dụ: do truy cập từ xa / Internet) ;
    • b) họ cần đóng vai trò là "chủ nhân", khi nhấn một trong các nút ấn của họ và ... điều này cần được truyền tới RPI2 để gửi lệnh đến các MEGA khác để bật đèn "được lưu trữ";
  4. như đối với 3b), do có MEGAs đóng vai trò là chủ nhân khi nhấn WPB, tôi có thể triển khai logic "bỏ phiếu thường xuyên" trên RPI2 không? Nếu có, đó là một giá trị hợp lý cho một cuộc bỏ phiếu như vậy (1 cuộc thăm dò mỗi giây? 5 cuộc thăm dò mỗi giây? Quá nhiều? Quá thấp?)

Tôi hiểu rằng đây là một câu hỏi quá rộng, nhưng tôi thực sự đã nghiên cứu rất nhiều và mặc dù rất nhiều, rất nhiều và rất nhiều tài liệu trực tuyến, tôi không thể tìm được câu trả lời cho những câu hỏi đó.


Cập nhật 1

  • về tổng số, tôi sẽ có 31 đèn được điều khiển bằng 46 nút ấn trên tường, được phân bổ ít nhiều bằng nhau trong 4 bảng điều khiển riêng biệt;
  • Đối với lựa chọn của MEGA (so với UNO), tôi đã chọn MEGA do số PIN I / O lớn hơn. Tôi chỉ cần chọn bảng với số PIN tối đa;
  • Đối với RPI2, tôi đã chọn sử dụng một "máy tính" thích hợp (so với một vi điều khiển bổ sung), vì tôi muốn có một loại "tách rời" giữa "mạng điều khiển vật lý" và "Giao diện quản lý người dùng". Nói cách khác, tôi muốn việc quản lý các nút / tín hiệu vật lý được xử lý bằng thiết bị giống như PLC (Arduino: bật nguồn rất nhanh; loại hiệu suất thời gian thực; rất đáng tin cậy, các yếu tố "điện toán" nhỏ / không có bên ngoài giới thiệu sự chậm trễ / các vấn đề); đồng thời, tất cả giao diện người dùng được xử lý bởi một máy tính thực, nơi tôi có thể dễ dàng viết các dịch vụ web HTTP mạnh mẽ và / hoặc "logic" thực sự phức tạp, sử dụng các công nghệ và ngôn ngữ không phù hợp với Arduino (sau này - nhiều lần sau--, tôi dự định thêm vài $ 150 full-HD 10.1 " máy tính bảng Android liên kết với nhau, sẽ đóng vai trò là "máy khách" không dây hướng tới những gì cần phải là một máy chủ web "mạnh mẽ" (về khả năng tính toán); Ngoài ra tôi có kế hoạch để thêm các cảm biến khác nhau [nhiệt độ; độ ẩm; liên lạc; công tơ điện; vv] có dữ liệu cần được lưu trữ vì lý do xu hướng / lưu trữ). Do đó, tôi đã nghĩ về RPi2, hơn là có thể dễ dàng thay thế bằng thứ gì đó mạnh hơn, nếu cần.

Bạn có thể muốn một cái gì đó được kết nối với RPi có thể thực hiện liên lạc 9 bit trên RS-485. Linux không hỗ trợ UART 9 bit nên bạn phải chơi các thủ thuật xấu với bit chẵn lẻ mà không có.
Ignacio Vazquez-Abrams

Điều gì xảy ra nếu tôi kết nối, thông qua RS485, chỉ ARDUINO? Ý tôi là, vấn đề bạn đang đề cập được giới thiệu nghiêm ngặt bằng cách kết nối RPI với RS485, phải không? Tôi đang hỏi, vì cũng dựa trên câu trả lời @ nick-gammon bên dưới, tôi có thể sẽ chấm dứt bus RS485 trên Arduino bổ sung quảng cáo và có kết nối giữa RPI và Arduino bằng Ethernet (chứ không phải RS485). Trong trường hợp như vậy, RPI sẽ không cần nói RS485 và vấn đề sẽ biến mất, phải không?
Damiano Verzulli

1
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. Trừ khi bạn sống trong một sàn nhảy, điều này chỉ xảy ra vài lần một ngày trên mỗi đèn / công tắc.
Gerben

1
@Gerben: với hai đứa con trai nhỏ của tôi (2,5 và 5,5), tôi có thể đảm bảo với bạn rằng xác suất như vậy KHÔNG quá thấp như bạn mong đợi trong điều kiện "bình thường" :-). Đùa sang một bên, xem xét rằng mọi thứ sẽ chạy trơn tru, tôi dự định thêm RẤT NHIỀU cảm biến / thiết bị bổ sung sẽ được truy vấn thông qua "lưu lượng xe buýt" (cảm biến nhiệt độ / độ ẩm [một trên mỗi phòng]; hai cho mỗi cửa sổ], công tơ điện và đồng hồ sử dụng nước có thể tạo ra các "xung" được thu thập theo một cách nào đó. Vì vậy, một lần nữa, khi "va chạm" sẽ xảy ra, MEGAs của tôi sẽ "sống sót"? Sẽ cháy?
Damiano Verzulli

Câu trả lời:


8

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:

Hệ thống dây RS485


Để 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).

Bản thử nghiệm RS485

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 myAddresscho 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ơ đồ

Sơ đồ tổng thể di chuyển RS485


Ả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:

RS485 - một thiết bị

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.


RS485 - hai thiết bị

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.


RS485 - ba thiết bị

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.


1) Cảm ơn RẤT NHIỀU câu trả lời của bạn và hướng dẫn RS-485. Tôi chắc chắn sẽ sử dụng nó; Thứ 2) về sự tham gia của MEGA và RPI, tôi đã cập nhật OP. Xin vui lòng, tham khảo nó để biết chi tiết; Thứ 3) như đối với " 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 ", là 10 cuộc thăm dò mỗi giây, với tốc độ 9600 baud, một lựa chọn hợp lý? Và cuối cùng: nếu tôi hiểu chính xác, 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?
Damiano Verzulli

Xem câu trả lời cập nhật.
Nick Gammon

Xem câu trả lời sửa đổi trong đó thể hiện một khái niệm tổng thể di chuyển.
Nick Gammon

is a 10-polls per second, on a 9600 baud rate, a reasonable choice?- trong thử nghiệm hiện tại của tôi (xem ảnh chụp màn hình) Tôi đang kiểm tra 4 thiết bị liên tục trong 75 ms ở 19200 baud, do đó, có 13 báo cáo trạng thái một giây. Về cơ bản điều đó có nghĩa là một thiết bị sẽ phản ứng với thay đổi trạng thái (ví dụ: đóng công tắc) từ thiết bị khác trong 1/10 giây. Câu hỏi ban đầu của bạn đề cập đến 4 thiết bị, vì vậy bạn sẽ có thể nhận được kết quả tương tự.
Nick Gammon

bạn có thể vui lòng cho tôi biết nơi bạn có clip dây trắng trong ảnh với nền đỏ (clip được gắn vào bó dây)
jsotola

2

Hãy nhớ RS485 không phải là một giao thức, nó là định nghĩa của lớp vận chuyển vật lý. Với Mega như bạn đã chọn, bạn có thể sử dụng nối tiếp 1,2,3 và chạy chúng ở chế độ song công hoàn toàn qua mạng RS485, bạn có thể nhận được những gì bạn gửi.

Tôi có nó hoạt động trong một cấu hình đa chủ. Mỗi mega khi truyền nó cũng nhận được những gì nó vừa gửi và có thể xác định trên từng byte theo cơ sở nếu nó cần phải sao lưu. Độ trễ trước khi quay trở lại trên dòng được xác định bởi địa chỉ nút và khi xe buýt có sẵn.

Mã sẽ sử dụng chức năng ghi thay vì chức năng in cho phép kiểm tra từng byte. (Trong thời gian tôi có thể sẽ làm điều đó thông qua phần mềm và mô phỏng CAN hoặc chỉ sử dụng bộ điều khiển can). Có 8 bit hoạt động tốt, bit thứ chín là bit chẵn lẻ hoặc sử dụng khác tùy thuộc vào người định nghĩa nó.

Hệ thống kiểm tra của tôi đang hoạt động với các gói và gửi theo định dạng này:

| Độ dài | Mục tiêu | Nguồn | Trình tự | Lệnh | Cờ | Dữ liệu | Kiểm tra

và mong đợi một ACK để đáp ứng. Đây là lần đầu tiên tương đối đơn giản và không có mã bất hợp pháp. Tất cả các byte là Hex nhưng bạn có thể sử dụng những gì bạn muốn.

Điều này mang lại cho tôi Carrier Sense, nhiều quyền truy cập nhưng với trọng tài phá hoại. Nếu bạn muốn CSMANDA chỉ cần sử dụng lớp vật lý CAN hoặc bit bang dữ liệu bạn gửi thì bạn có thể phân xử từng chút một. Nó sẽ không khác nhiều về phần mềm, sau đó là phần truyền tải.

Tôi đang lên kế hoạch sử dụng RPi với Linux làm nút chính. Với dữ liệu 8 bit, nó sẽ hoạt động tốt. Tôi mới bắt đầu khoảng hai tuần trước về cấu hình này và cho đến nay nó đang diễn ra tốt đẹp. Tôi đang hoạt động ở 9600 baud và có rất nhiều thời gian rảnh.

Gil


Làm thế nào để bạn có những thứ được thiết lập để cảm nhận sự va chạm? Con chip cụ thể mà bạn đang sử dụng cho giao diện RS-485 có trả lại dữ liệu được cảm nhận trên liên kết trên dòng RX cùng lúc với khi bạn gửi dữ liệu trên dòng TX không và nó có truyền dữ liệu đó không?
cjs

0

Nếu bạn muốn sử dụng giao thức của Nick Gammon, bạn có thể thấy điều này hữu ích: https://github.com/Sthing/Nick-Gammon-RS485

Đây là triển khai của tôi về giao thức trong Python (để sử dụng trên RaspberryPi). Tôi mới chỉ thử nghiệm python-to-python, thử nghiệm đối với mã của Nick Gammon là tiếp theo trong danh sách việc cần làm của tôi.

/Điều


Gần đây, tôi đã rất thành công khi thử nghiệm giao thức không chặn RS485 của Nick Gammon, giữa ba MEGAs trong kịch bản "một chủ, hai nô lệ". Tôi đã gặp một số vấn đề, chủ yếu được giới thiệu bởi việc sử dụng các serial vật lý (và không phải SoftwareSerials). May mắn thay, tôi đã có thể sửa chữa mọi thứ. Tôi đã xuất bản mã chạy (với một số chi tiết khác) tại đây: github.com/verzulli/arduino-smart-home - Tôi hiện đang tích cực làm việc với dự án đó và dự định cập nhật nó trong những tuần tới.
Damiano Verzulli

0

Tôi nghĩ rằng giao thức CDBUS cho RS485 là chính xác những gì bạn muốn, nó giới thiệu một cơ chế trọng tài tự động tránh các xung đột như bus CAN. Tôi thậm chí có thể chuyển luồng video qua nó:

nhập mô tả hình ảnh ở đây Video đầy đủ: https://youtu.be/qX5dh4wcfSk

Raspberry Pi có thể xuất video xem trước và lệnh điều khiển cùng một lúc. Chúng tôi có thể theo dõi quá trình nhận dạng trên PC. Khi gặp sự cố, thật thuận tiện để biết lý do và điều chỉnh các tham số, và ngắt kết nối PC sẽ không ảnh hưởng đến hoạt động demo.

nhập mô tả hình ảnh ở đây Ngoài ra, Raspberry Pi có thể truy cập internet bất cứ lúc nào thông qua PC và rất dễ dàng để cập nhật phần mềm và điều khiển từ xa.

Chi tiết về CDBUS:

Cập nhật: Kết nối bộ điều khiển CDCTL-Bx với Arduino: arduino và cdctl


Việc đề cập đến video không liên quan đến câu hỏi và chủ đề Arduino của trang web này, và do đó có một chút mất tập trung từ nó. Một khối lượng dữ liệu như vậy cũng có thể gây ra vấn đề cho các hệ thống bị hạn chế tài nguyên trừ khi được xử lý một cách tao nhã. Nếu bạn muốn đề xuất sơ đồ này, bạn nên giải thích cách nó phù hợp với vấn đề của câu hỏi và cho Arduino ... nếu nó thực sự là như vậy.
Chris Stratton

Nói cách khác, đối với Arduino, thay thế Raspberry Pi Zero trong sơ đồ của tôi thành Arduino, nói cách khác, thêm một bảng điều khiển bên ngoài của bộ điều khiển RS485 cho Arduino, giao tiếp qua giao diện I2c hoặc SPI.
2024827
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.