So verbinden Sie mehrere Arduinos mit einem Rpi, um Home-Lichter / Schalter zu steuern

8

Bei der Planung der Blitzinfrastruktur (Wandschalter und Beleuchtung) meines neuen Hauses (das sich noch im Bau befindet) habe ich mich aufgrund meines Hintergrunds für die "automatisierte Route" entschieden und bin ein "altes" System / Netzwerk Administrator mit Programmierkenntnissen und viel Open-Source "Leidenschaft" und "Anwaltschaft") Ich versuche ernsthaft, es mit drei Arduinos und einem RPi2 zu implementieren.

Aufgrund der Anzahl / Position der Wandtasten und Lichter möchte ich drei MEGA-Schnittstellen verwenden, die sowohl die Wandtasten als auch die Lichter der umgebenden Räume miteinander verbinden. Darüber hinaus wird ein RPi2 (oder ähnliches) als "Controller" verwendet, um MEGAs bei Bedarf ordnungsgemäß zu programmieren und einige andere Geräte (Touchscreen, WLAN-Tablet / Smartphone, Fernbedienungen usw.) über die IP-Adresse anzuschließen. Netzwerk.

Das Schema, das ich zu implementieren versuche, ähnelt dem folgenden:

Entwurf eines Schemas Wo siehst du:

  • 9 Lichter (L1 bis L9), die jeweils von einem eigenen Relais (R1 bis R9) gesteuert werden;
  • 7 Wandtasten (WPB1 bis WPB7) zum Ein- und Ausschalten eines oder mehrerer Lichter;
  • 3 MEGAs, die Wandtasten und Lichter verbinden;
  • 1 RPi2 als "Supervisor" und "Internet / Ethernet-Gateway".

Mein Hauptproblem in der Architektur betrifft die Verbindung zwischen RPI2 und MEGAs. Da sich jedes Gerät mehrere zehn Meter voneinander entfernt befindet, endete ich mit nur zwei Optionen (bitte korrigieren Sie mich, wenn ich falsch liege):

  1. Ethernet
  2. RS485

( Übrigens: Ich schließe "drahtlose Verbindungen" ausdrücklich aus, da ich bereits alle elektrischen Leitungen auf "kompatible" Weise verlegt habe. Mit anderen Worten: Ich möchte drahtlose Technologien innerhalb des "steuernden Netzwerks" vermeiden. )

Ethernet werde ich aufgrund der etwas höheren Kosten und Komplexität als zweite Option wählen (zum Einschalten muss ein Switch benötigt werden; zusätzliche Verkabelungsprobleme usw.).

Ich habe den " RS485-Bus " ausgiebig untersucht und festgestellt, dass es aus physikalischer Sicht relativ einfach ist, ihn mit zwei Drähten in einer Multidrop-Konfiguration zu implementieren .

Aus "Anwendungssicht" sind die Dinge leider komplexer, da nur "Halbduplex" -Kommunikation unterstützt wird und, noch schlimmer, es keine Vorkehrungen gibt, um "Kollisionen" bei der Kommunikation zu vermeiden (deshalb wahrscheinlich das MODBUS-Protokoll) - Wird normalerweise auf dem RS485-Bus verwendet - bietet ein "Single-Master; Multiple-Slaves" -Szenario.

Vor den Fragen muss ich noch eine weitere Einschränkung hinzufügen: Ich möchte, dass die Infrastruktur "so fehlertolerant wie möglich" ist, insbesondere gegenüber Problemen mit dem BUS und / oder mit dem "Controller" (dem RPI2). Mit anderen Worten:

  • Jedes MEGA sollte es erlauben, seine eigenen Lichter einzuschalten, wenn dies für einen eigenen Druckknopf erforderlich ist. Zum Beispiel:
    • Wenn WPB1 L2 und L3 steuert, muss es auch dann funktionieren, wenn der BUS defekt oder der RPI2 ausgeschaltet ist.
    • Wenn WPB3 L4 und L9 steuert, wird bei Problemen mit Bus / RPI2 nur L4 eingeschaltet, wenn WPB3 gedrückt wird.

Nach all dem sind hier meine Fragen:

  1. Ist ein zweidrahtiger RS485-Multidrop-Bus eine geeignete Wahl für mein Szenario?

  2. Wenn nicht, welche (möglicherweise billigen und einfachen) anderen Lösungen kann ich untersuchen?

  3. Wenn ja, welche Logik muss ich auf MEGAs implementieren:

    • a) Sie müssen in Bezug auf RPI2 als "Slave" fungieren, wenn sie Lichter einschalten, die über einen mit anderen MEGAs verbundenen Druckknopf gesteuert werden, oder wenn das RP2 beschließt, einige Lichter einzuschalten (z. B. aufgrund von Fern- / Internetzugang). ;;
    • b) Sie müssen als "Master" fungieren, wenn einer ihrer Drucktasten gedrückt wird und ... dies muss RPI2 mitgeteilt werden, um Befehle an andere MEGAs zu senden und "gehostete" Lichter einzuschalten.
  4. Wie für 3b) kann ich eine "Frequent-Polling" -Logik auf dem RPI2 implementieren, wenn MEGAs beim Drücken eines WPB als Master fungieren? Wenn ja, welcher Wert ist für eine solche Umfrage angemessen (1 Umfrage pro Sekunde? 5 Umfragen pro Sekunde? Zu viel? Zu niedrig?)

Ich verstehe, dass dies eine zu weit gefasste Frage ist, aber ich habe wirklich viele Nachforschungen angestellt und trotz vieler, vieler und vieler Online-Dokumentationen konnte ich keine Antwort auf diese Fragen finden.


Update 1

  • In Bezug auf die Gesamtzahl habe ich 31 Lichter, die über 46 Wandtasten gesteuert werden können, die mehr oder weniger gleichmäßig auf 4 verschiedene Schalttafeln verteilt sind.
  • Bei der Auswahl von MEGA (vs. UNO) habe ich mich aufgrund der größeren Anzahl von E / A-PINs für MEGA entschieden. Ich habe einfach das Board mit der maximalen Anzahl von PINs ausgewählt.
  • Für das RPI2 habe ich mich für einen geeigneten "Computer" (im Vergleich zu einem zusätzlichen Mikrocontroller) entschieden, da ich eine Art "Entkopplung" zwischen dem "physischen Steuerungsnetzwerk" und der "Benutzerverwaltungsoberfläche" möchte. Mit anderen Worten, ich möchte, dass die Verwaltung von physischen Tasten / Relais von einem SPS-ähnlichen Gerät übernommen wird (Arduino: sehr schnelles Einschalten; Art von Echtzeitleistung; sehr zuverlässig; geringfügige / keine externen "Rechen" -Faktoren, die Verzögerungen verursachen / Probleme); Gleichzeitig wird die gesamte Benutzeroberfläche von einem realen Computer verwaltet, auf dem ich mithilfe von Technologien und Sprachen, die nicht gut zu Arduino passen, problemlos leistungsstarke HTTP-Webdienste und / oder wirklich komplexe "Logiken" schreiben kann (später - viel später - habe ich vor, mehrere 150 $ Full-HD 10.1 hinzuzufügen " Rund um das Haus gibt es Android-Tablets, die als drahtlose "Clients" für einen "leistungsstarken" Webserver (in Bezug auf die Rechenkapazität) fungieren. Außerdem habe ich vor, verschiedene Sensoren hinzuzufügen [Temperatur; Feuchtigkeit; Kontakte; Leistungsmesser; usw.], deren Daten aus Trend- / Archivierungsgründen gespeichert werden müssen). Daher habe ich über RPi2 unterrichtet, das leicht durch etwas Stärkeres ersetzt werden kann, falls es benötigt wird.
Damiano Verzulli
quelle
Sie möchten wahrscheinlich etwas mit dem RPi verbunden haben, das 9-Bit-Kommunikation auf RS-485 ermöglicht. Linux unterstützt keine 9-Bit-UARTs, daher müssten Sie ohne das Paritätsbit hässliche Streiche spielen.
Ignacio Vazquez-Abrams
Was ist, wenn ich über RS485 nur ARDUINOs verbinde? Ich meine, das Problem, das Sie erwähnen, wird ausschließlich durch die Verbindung von RPI mit RS485 eingeführt, oder? Ich frage, weil ich, basierend auch auf der @ nick-gammon-Antwort unten, wahrscheinlich den RS485-Bus auf einem zusätzlichen Arduino beenden und die Verbindung zwischen RPI und Arduino über Ethernet (und nicht RS485) herstellen werde. In einem solchen Fall muss RPI nicht RS485 sprechen und das Problem sollte verschwinden, oder?
Damiano Verzulli
1
Ich wollte nur beachten, dass die Wahrscheinlichkeit von Paketkollisionen eher gering ist. Sie senden Pakete nur, wenn ein Schalter gedrückt wird oder wenn ein Licht eingeschaltet werden muss. Wenn Sie nicht in einer Disco wohnen, geschieht dies nur einige Male am Tag pro Licht / Schalter.
Gerben
1
@Gerben: Mit meinen beiden jungen Söhnen (2,5 und 5,5) kann ich Ihnen versichern, dass eine solche Wahrscheinlichkeit NICHT so gering ist, wie Sie es unter "normalen" Bedingungen erwarten könnten :-). Spaß beiseite, bedenken Sie, dass ich, sollte alles reibungslos läuft, VIELE zusätzliche Sensoren / Geräte hinzufügen möchte, die über den "Busverkehr" abgefragt werden sollen (Temperatur- / Feuchtigkeitssensoren [einer pro Raum]; Kontakte für Türen / Fenster öffnen / schließen [zumindest] zwei für jedes Fenster]; Strom- und Wasserverbrauchsmesser erzeugen wahrscheinlich "Impulse", die auf irgendeine Weise gesammelt werden müssen. Wenn also wieder "Kollisionen" auftreten, werden meine MEGAs "überleben"? Oder sie ' ll brennen?
Damiano Verzulli

Antworten:

8

Ich habe einen langen Beitrag über RS485 geschrieben .


Erstens scheint Ihre Verwendung von Megas ein Overkill zu sein, es sei denn, Sie haben sie bereits zur Hand. Ein Uno oder eine der kleineren Formfaktor-Karten scheint vollkommen ausreichend zu sein, um einige Schalter zu überwachen und ein paar Lichter einzuschalten.

Sogar das RPI scheint unnötig. Ein anderer Uno kann problemlos Ihre RS485-Leitungen überwachen und über Ethernet (eine Ethernet-Abschirmung) eine Verbindung zum Rest Ihres Hauses herstellen oder was auch immer Sie tun.


... es gibt keine Bestimmung, um "Kollisionen" bei der Kommunikation zu vermeiden ...

Nun, Sie bauen das ein. Sie geben jedem Mega eine Adresse (z. B. im EEPROM gespeichert) und "adressieren" dann die gewünschte Adresse und warten dann auf eine Antwort. Zum Beispiel im Code von meiner obigen Seite:

Meister

#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

Sie konfigurieren den RS485-Transceiver so, dass er entweder sendet oder empfängt. Normalerweise befindet es sich im Empfangsmodus und Sie wechseln in den Sendemodus, um ein "Datenpaket" zu senden. (Siehe "Senden aktivieren" oben).


Jetzt schaltet das adressierte Gerät seinen Transceiver in den "Send" -Modus und antwortet. Der Code in der Bibliothek, den ich geschrieben habe, hat eine Zeitüberschreitung. Wenn also dieser bestimmte Slave tot ist, läuft der Empfang ab. Vielleicht erinnern Sie sich am Master-Ende daran und versuchen, seltener damit zu kommunizieren. Oder es ist Ihnen egal, ob die Auszeit kurz ist.

Sklave

#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

Hinweis : Der Beispielcode von meiner verlinkten Seite liest die Adresse nicht aus dem EEPROM, dies ist jedoch trivial zu implementieren.


Ich kann keinen bestimmten Grund erkennen, warum Sie die Sklaven nicht häufig befragen konnten. Was würde der Meister sonst noch tun? Sie können den Master auch als HTTP-Server einrichten, um von Ihrem Laptop oder einem anderen Teil des Hauses aus mit ihm zu kommunizieren.


Meine Verkabelung:

RS485-Verkabelung


Um die Idee zu demonstrieren, habe ich zwei Unos aufgestellt, die über ca. 8 m Glockendraht verbunden sind (nicht einmal Twisted Pair und schon gar nicht abgeschirmt).

RS485-Demo

Ein Uno führte die Standard-ASCII-Tabellenskizze (bei 9600 Baud) aus, sein Tx-Pin ging in den LTC1480 und dann gingen die A / B-Pins zum Glockendraht.

Das andere Uno war als USB-Schnittstelle angeschlossen (Reset mit Masse verbunden) und gab nur das wieder, was auf dem Tx-Pin des USB-Anschlusses angekommen war.

Soweit ich sehen konnte, hat es perfekt funktioniert.


Ich brauche überhaupt keine Umfrage, da ich mit Ihrem Ansatz einen "Single-Master / Multiple-Slave" -Kontext implementieren kann ... aber mit einem "Moving" -Master. Ist das richtig?

Meine obige Antwort ging davon aus, dass die Slaves eher versagen als der Master (ich kann mir nicht vorstellen, warum dies passieren würde, aber vielleicht aufgrund der Tatsache, dass es mehr Slaves als Master gibt und der Master Dinge wie Lichter nicht kontrolliert ).

Ich finde meine Arduinos äußerst zuverlässig, wenn ich einfache Dinge tue (wie das Aufschließen einer Tür, wenn eine RFID-Karte vorgelegt wird).

Es ist denkbar, dass Sie eine Rückfallposition in die Sklaven einbauen. Wenn sie jede Sekunde abgefragt werden und dann keine Umfrage eintrifft, könnten sie möglicherweise versuchen, als Master zu fungieren, möglicherweise in aufsteigender Reihenfolge der Gerätenummer, um Konflikte zu vermeiden. Ein Teil dieser Abfrage durch den "neuen Master" könnte darin bestehen, den "ursprünglichen Master" zu überprüfen, um festzustellen, ob er bereit ist, seine Aufgaben wieder aufzunehmen.

In die Bibliothek, die ich auf meiner verlinkten Seite beschrieben habe, ist eine Fehlerprüfung integriert. Die Idee ist, dass Sie durch Überprüfen einer CRC für das Paket sicherstellen, dass Sie nicht auf halbem Weg durch ein Paket kommen und die darin enthaltenen Daten falsch interpretieren.

Sie können auch zufällige Zufallszeiten einbauen, um einen Deadlock zu beheben, wenn zwei Slaves gleichzeitig versuchen, Master zu werden. Wenn ein Slave ausfällt, kann er eine zufällige (und zunehmende) Zeit warten, bevor er erneut versucht, einem anderen Slave die Möglichkeit zu geben, dies zu tun.


Ich wollte nur beachten, dass die Wahrscheinlichkeit von Paketkollisionen eher gering ist. Sie senden Pakete nur, wenn ein Schalter gedrückt wird oder wenn ein Licht eingeschaltet werden muss.

Gerben hat recht, aber ich würde mir Sorgen machen, dass eine Benachrichtigung über einen Wechsel unbemerkt blieb. Eine mögliche Problemumgehung besteht darin, dass die Slaves nicht mit Statusänderungen auf eine Abfrage antworten, sondern mit dem aktuellen Status antworten. So könnte es gehen:

Master: Slave 3, what is your status?
Slave 3: Lights 1 and 4 on, lights 2 and 3 off.

Ich brauche überhaupt keine Umfrage, da ich mit Ihrem Ansatz einen "Single-Master / Multiple-Slave" -Kontext implementieren kann ... aber mit einem "Moving" -Master. Ist das richtig?

Ich habe ein bisschen darüber nachgedacht, und ich denke, jetzt könnten Sie ein System erstellen, das im Grunde genommen masterfrei ist. Es könnte so funktionieren:

  • Jedes Gerät hat eine eigene Adresse, die es vom EEPROM (oder den DIP-Schaltern) erhält. z.B. 1, 2, 3, 4, 5 ...

  • Sie wählen einen Adressbereich aus, den Sie verwenden möchten (z. B. maximal 10).

  • Wenn das Gerät eingeschaltet wird, lauscht es zuerst auf andere Geräte, die auf dem Bus "sprechen". Hoffentlich hört es mindestens einen anderen (wenn nicht, siehe unten).

  • Wir entscheiden uns für ein festes "Nachrichtenpaket", beispielsweise von 50 Bytes, einschließlich Adresse, CRC usw. Bei 9600 Baud würde das Senden 52 ms dauern.

  • Jedes Gerät erhält einen "Zeitschlitz" und wartet, bis es an der Reihe ist, mit dem Bus zu sprechen.

  • Wenn sein Zeitschlitz eintrifft, wechselt er in den Ausgabemodus und sendet sein Paket, das seine eigene Adresse enthält. Daher können jetzt alle anderen Geräte ihren Status lesen (und gegebenenfalls darauf reagieren). Z.B. Gerät 1 meldet möglicherweise, dass Schalter 3 geschlossen ist, was bedeutet, dass Gerät 2 ein Licht einschalten muss.

  • Im Idealfall wissen Sie, dass Ihr Zeitfenster angekommen ist, da Ihre Geräteadresse um eins größer ist als das Paket, das Sie gerade abgehört haben. Z.B. Sie sind Gerät 3. Sie haben gerade gehört, dass Gerät 2 seinen Status ankündigt. Jetzt bist du dran. Natürlich wickeln Sie sich mit der maximalen Anzahl um, also kehren Sie nach Gerät 10 zu Gerät 1 zurück.

  • Wenn ein Gerät fehlt und nicht antwortet, geben Sie ihm (sagen wir) einen halben Zeitschlitz, um zu antworten, und nehmen dann an, dass es tot ist, und jedes Gerät im Bus geht jetzt davon aus, dass der nächste Zeitschlitz gestartet wurde. (z. B. Sie haben gehört, dass Gerät 2, Gerät 3 antworten sollte. Nach 25 ms Inaktivität kann Gerät 4 jetzt antworten.) Diese Regel gibt einem Gerät 25 ms Zeit, um zu antworten. Dies sollte ausreichend sein, selbst wenn es einen Interrupt oder ähnliches bedient.

  • Wenn mehrere Geräte nacheinander fehlen, zählen Sie für jedes fehlende Gerät eine Lücke von 25 ms, bis Sie an der Reihe sind.

  • Sobald Sie mindestens eine Antwort erhalten, wird das Timing neu synchronisiert, sodass jede Drift in den Uhren aufgehoben wird.

Die einzige Schwierigkeit besteht darin, dass beim ersten Einschalten (was gleichzeitig passieren kann, wenn die Stromversorgung des Gebäudes unterbrochen und dann wiederhergestellt wird) derzeit kein Gerät seinen Status sendet und daher nichts synchronisiert werden kann.

In diesem Fall:

  • Wenn das Gerät lange genug gehört hat, um alle Geräte zu hören (z. B. 250 ms) und nichts zu hören, geht es vorläufig davon aus, dass es das erste ist, und sendet eine Sendung. Möglicherweise tun dies jedoch zwei Geräte gleichzeitig und hören sich daher nie.

  • Wenn ein Gerät nichts von einem anderen Gerät gehört hat, wird die Zeit zwischen den Sendungen zufällig verschoben (möglicherweise wird der Zufallszahlengenerator aus seiner Gerätenummer entfernt, um zu vermeiden, dass alle Geräte die Sendungen "zufällig" um denselben Betrag versetzen).

  • Diese zufällige Staffelung um zusätzliche Zeit spielt keine Rolle, da sowieso niemand zuhört.

  • Früher oder später wird ein Gerät den Bus exklusiv nutzen und die anderen können dann auf die übliche Weise mit ihm synchronisieren.

Diese zufällige Lücke zwischen Kommunikationsversuchen ähnelt der von Ethernet, wenn mehrere Geräte ein Koaxialkabel gemeinsam nutzen.


Demo des masterfreien Systems

Dies war eine interessante Herausforderung, daher habe ich eine Demo zusammengestellt, in der dies ohne einen bestimmten Master durchgeführt wurde, wie oben beschrieben.

Zuerst müssen Sie die aktuelle Geräteadresse und die Anzahl der Geräte im EEPROM einrichten. Führen Sie diese Skizze aus und ändern Sie sie myAddressfür jedes 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 () { }

Laden Sie dies nun auf jedes Gerät hoch:

/*
 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

Wenn Sie derzeit einen Schalter an A0 (Kurzschluss nach Masse) schließen, wird nacheinander eine LED (Pin 13) am nächsthöheren Gerät ausgeschaltet. Dies beweist, dass die Geräte miteinander sprechen. In der Praxis hätten Sie natürlich etwas Anspruchsvolleres in dem Paket, das gesendet wird.

Beim Testen stellte ich fest, dass die LED sofort ein- und ausgeschaltet zu sein schien.

Wenn alle Geräte getrennt sind, "sucht" das erste angeschlossene nach anderen Geräten. Wenn Sie Debugging-LEDs wie ich angeschlossen haben, können Sie sehen, dass die "Such" -LED in zufälligen Intervallen aufleuchtet, wenn sie ihr Paket mit zufällig variierenden Lücken sendet. Sobald Sie eine zweite angeschlossen haben, beruhigen sie sich und tauschen einfach Informationen aus. Ich habe mit drei gleichzeitig verbundenen getestet.

Mit HardwareSerial wäre es wahrscheinlich zuverlässiger - ich habe SoftwareSerial verwendet, um beim Debuggen zu helfen. Ein paar kleine Änderungen würden das erreichen.


Geänderter Schaltplan

RS485 Moving Master-Schaltplan


Screenshots des Codes in Aktion

Diese Bilder zeigen, wie der Code funktioniert. Erstens, wenn nur ein Gerät angeschlossen ist:

RS485 - ein Gerät

An den dortigen Impulsen können Sie erkennen, dass das Gerät seine Daten in zufällig variierenden Intervallen sendet, um zu vermeiden, dass es weiterhin zu Konflikten mit einem anderen Gerät kommt, das im selben Moment eingeschaltet wurde.


RS485 - zwei Geräte

Jetzt sehen wir Datenblöcke von zwei Geräten mit ungefähr gleich großen Lücken in der Mitte. Ich habe es für vier Geräte konfiguriert, aber nur zwei sind vorhanden, sodass wir zwei Datenblöcke und zwei Lücken sehen.


RS485 - drei Geräte

Jetzt, da drei Geräte online sind, sehen wir drei Datenblöcke und eine Lücke, da das fehlende Gerät umgangen wird.


Wenn Sie die Zahlen überprüfen, wurden diese mit der Baudrate genommen, die als Test auf 19200 Baud verdoppelt wurde.


Kabelführungstest

Für einen ordnungsgemäßen Hardwaretest habe ich die Geräte an meine interne UTP-Verkabelung angeschlossen. Ich habe ein Cat-5-Kabel, das von verschiedenen Räumen zu einer zentralen Steckdose führt. Wenn Sie von einem Ende des Hauses zum anderen gehen (eine angemessene Länge), funktioniert es immer noch einwandfrei. Zunächst befindet sich zwischen dem Arduino und der Wandsteckdose ein 5 m langes Kabel. Plus ein weiteres 5 m Kabel am anderen Ende. Dann gibt es ungefähr 2 x 15 m Läufe von den Räumen zum Schaltraum, und darin befindet sich ein kurzes Überbrückungskabel, um sie miteinander zu verbinden.

Dies war mit den Boards noch programmiert, um mit 19200 Baud zu laufen.

Nick Gammon
quelle
1.) Vielen Dank für Ihre Antwort und das RS-485-Tutorial. Ich werde es sicherlich benutzen; 2.) Was die Beteiligung von MEGA und RPIs betrifft, habe ich das OP aktualisiert. Bitte beziehen Sie sich für Details darauf; 3.) Was " Ich kann keinen bestimmten Grund sehen, warum Sie die Sklaven nicht häufig befragen konnten ", ist eine 10-Umfragen pro Sekunde bei einer Baudrate von 9600 eine vernünftige Wahl? Und schließlich: Wenn ich richtig verstanden hat , werde ich nicht jede Umfrage überhaupt benötigen, wie mit Ihrem Ansatz kann ich einen „Single-Master / Multiple-Slave“ Kontext implementieren ... aber mit einem „moving“ Master. Ist das richtig?
Damiano Verzulli
Siehe aktualisierte Antwort.
Nick Gammon
Siehe geänderte Antwort, die ein bewegendes Master-Konzept demonstriert.
Nick Gammon
is a 10-polls per second, on a 9600 baud rate, a reasonable choice?- In meinem aktuellen Test (siehe Screenshots) teste ich 4 Geräte wiederholt in 75 ms bei 19200 Baud, das sind 13 Statusberichte pro Sekunde. Grundsätzlich bedeutet dies, dass ein Gerät in 1/10 Sekunde auf eine Statusänderung (z. B. ein Schließen des Schalters) eines anderen Geräts reagieren sollte. In Ihrer ersten Frage wurden 4 Geräte erwähnt, sodass Sie ähnliche Ergebnisse erzielen sollten.
Nick Gammon
Könnten Sie mir bitte sagen, woher Sie den weißen Drahtclip haben, der auf dem Bild mit dem roten Hintergrund zu sehen ist (der Clip ist am
Drahtbündel
2

Denken Sie daran, RS485 ist kein Protokoll, sondern eine Definition einer physischen Transportschicht. Mit dem Mega, wie Sie es ausgewählt haben, können Sie die serielle 1,2,3 verwenden und sie im Vollduplex-Modus über das RS485-Netzwerk ausführen. Sie können also empfangen, was Sie senden.

Ich habe es in einer Multi-Master-Konfiguration betrieben. Jedes Mega empfängt beim Senden auch das, was es gerade gesendet hat, und kann byteweise feststellen, ob es sich zurückziehen muss. Die Verzögerung vor der Rückkehr in die Leitung wird durch die Knotenadresse und den Zeitpunkt bestimmt, zu dem der Bus verfügbar wird.

Der Code verwendet die Schreibfunktion anstelle der Druckfunktion, mit der jedes Byte überprüft werden kann. (Mit der Zeit werde ich es wahrscheinlich per Software machen und CAN emulieren oder einfach einen Dosencontroller verwenden). Ja, 8 Bits funktionieren einwandfrei. Das neunte Bit ist ein Paritätsbit oder eine andere Verwendung, je nachdem, wer es definiert.

Mein Testsystem arbeitet mit Paketen und sendet in diesem Format:

| Länge | Ziel | Quelle | Sequenz | Befehl | Flags | Daten | Prüfen

und erwartet eine ACK als Antwort. Dies ist ein relativ einfacher erster Durchgang und hat keine illegalen Codes. Alle Bytes sind Hex, aber Sie können verwenden, was Sie wollen.

Dies gibt mir Carrier Sense, Mehrfachzugriff, aber mit destruktiver Schiedsgerichtsbarkeit. Wenn Sie möchten, dass CSMANDA nur eine CAN-physische Schicht verwendet oder die von Ihnen gesendeten Daten bitknallt, können Sie bitweise vermitteln. In der Software wird es nicht viel anders sein als im Sendeteil.

Ich plane, das RPi mit Linux als Hauptknoten zu verwenden. Mit 8-Bit-Daten funktioniert es einwandfrei. Ich habe vor ungefähr zwei Wochen mit dieser Konfiguration begonnen und bis jetzt läuft sie gut. Ich arbeite mit 9600 Baud und habe viel Freizeit.

Gil

Gil
quelle
Wie haben Sie Dinge eingerichtet, um Kollisionen zu erfassen? Gibt der bestimmte Chip, den Sie für die RS-485-Schnittstelle verwenden, die erfassten Daten auf der Verbindung auf der RX-Leitung zurück, während Sie ihm Daten auf der TX-Leitung senden und diese übertragen?
cjs
0

Wenn Sie das Protokoll von Nick Gammon verwenden möchten, ist dies möglicherweise hilfreich: https://github.com/Sthing/Nick-Gammon-RS485

Es ist meine Implementierung des Protokolls in Python (zur Verwendung auf einem RaspberryPi). Ich habe bisher nur Python-to-Python getestet. Das Testen mit Nick Gammons Code steht als nächstes auf meiner To-Do-Liste.

/Ding

Sthing
quelle
Ich habe in letzter Zeit erfolgreich das nicht blockierende RS485-Protokoll von Nick Gammon zwischen drei MEGAs in einem Szenario "Ein Master, zwei Slaves" getestet. Ich habe mehrere Probleme festgestellt, die hauptsächlich durch die Verwendung physischer Serien (und nicht von SoftwareSerials) verursacht wurden. Zum Glück konnte ich alles reparieren. Ich habe den laufenden Code (mit einigen anderen Details) hier veröffentlicht: github.com/verzulli/arduino-smart-home - Ich arbeite derzeit aktiv an einem solchen Projekt und plane, es in den kommenden Wochen zu aktualisieren.
Damiano Verzulli
0

Ich denke, das CDBUS-Protokoll für RS485 ist genau das, was Sie wollen. Es führt einen Arbitrierungsmechanismus ein, der Konflikte wie den CAN-Bus automatisch vermeidet. Ich kann sogar Videostreams übertragen:

Geben Sie hier die Bildbeschreibung ein Vollständiges Video: https://youtu.be/qX5dh4wcfSk

Der Raspberry Pi kann gleichzeitig ein Vorschau-Video und einen Steuerbefehl ausgeben. Wir können den Erkennungsprozess auf dem PC überwachen. Wenn Probleme auftreten, ist es zweckmäßig, den Grund zu kennen und die Parameter anzupassen. Das Trennen des PCs hat keine Auswirkungen auf den Demo-Betrieb.

Geben Sie hier die Bildbeschreibung ein Darüber hinaus kann der Raspberry Pi jederzeit über den PC auf das Internet zugreifen und die Software einfach aktualisieren und fernsteuern.

Details zum CDBUS:

Update: Verbinden Sie den CDCTL-Bx-Controller mit einem Arduino: arduino und cdctl

user2024827
quelle
Die Erwähnung von Videos ist für die gestellte Frage und das Arduino-Thema dieser Site irrelevant und lenkt daher etwas davon ab. Ein solches Datenvolumen kann bei Systemen mit eingeschränkten Ressourcen durchaus Probleme verursachen, wenn es nicht elegant behandelt wird. Wenn Sie dieses Schema vorschlagen möchten, sollten Sie erklären, wie es für das Problem der Frage und für ein Arduino geeignet ist ... wenn es tatsächlich ist.
Chris Stratton
Es ist dasselbe für Arduino, ersetzen Sie den Raspberry Pi Zero in meinem Diagramm durch einen Arduino, mit anderen Worten, fügen Sie eine externe Abschirmungsplatine des RS485-Controllers für Arduino hinzu und kommunizieren Sie über die I2c- oder SPI-Schnittstelle.
user2024827