Kostengünstiger/komplexer Bus für erweiterbare Module?

Ich versuche, einen Bus für ein erweiterbares System auszuwählen. Zu den Anforderungen gehören idealerweise:

  • Unbekannte Anzahl von Modulen (möglicherweise bis zu ein paar Dutzend).
  • Mehrere identische Module sind erlaubt (es kann schwierig sein, Modulen im Voraus eindeutige IDs zuzuweisen)
  • Klein und billig zu implementieren (jedes Modul kann eine billige MCU enthalten)
  • Muss nicht lang sein, höchstens ein paar Zentimeter, aber sie müssen nicht unbedingt auf demselben Brett sein.
  • > 1Mbps wäre schön.
  • Geteilter Bus. Keine Auswahlleitungen pro Modul usw. Muss idealerweise Verzweigungen und Zyklen im Bus handhaben.

Der führende Kandidat scheint I2C zu sein, aber es ist schwierig, mehrere identische Module zu haben.

Ich dachte, ich könnte mir ein Schema einfallen lassen, das auf I2C geschichtet ist. Zum Beispiel: Der Master fragt Slaves ab, denen keine Adresse zugewiesen wurde, indem er eine Nachricht an eine vorgegebene „Broadcast“-Adresse sendet, und Module, die keine Adresse haben, antworten zufällig nach einer bestimmten Anzahl von Abfragen, um Buskonflikte zu vermeiden. Das Modul antwortet mit einer Zufallszahl + CRC, sodass der Master ihnen bei Konflikten sagen kann, dass sie es später erneut versuchen sollen. Nach einer gewissen Anzahl von Abfragen ohne Antworten geht der Master davon aus, dass alle Slaves geantwortet haben und kann mit der Initialisierung fortfahren. Ich denke, dieses Schema könnte mit Standard-I2C-Geräten kompatibel sein. Ich weiß jedoch nicht, ob die in MCUs integrierte I2C-Unterstützung kompatibel ist.

Ist das unnötig komplex? Gibt es andere einfache Busprotokolle, die dieses Szenario bewältigen können?

Wenn ein Konflikt auftritt, wie würde Ihre Wiederholungsnachricht aussehen? Noch eine spezielle Broadcast-Adresse?
Warum nicht einen DIP-Schalter an jedem Modul als Adresse für den I2C-Bus verwenden?
@JonL: Der Master sendete weiter und der Slave versuchte es nach einer zufälligen Anzahl von Sendungen erneut
@Saad: Das ist mein Fallback-Plan, aber es gibt zwei mögliche Probleme: 1) Die Module werden von Benutzern hinzugefügt, ich möchte sie lieber nicht bitten, sie zu konfigurieren, 2) Die Module müssen klein und billig sein, und ich würde wahrscheinlich brauchen mindestens 4 Bits konfigurierbar.
Gibt es eine Garantie für eine 'werkseitig konfigurierte' eindeutige Adresse (nicht unbedingt dieselbe Adresse, die für die Kommunikation zwischen den Modulen verwendet wird) in diesen Modulen?

Antworten (4)

Ich habe so etwas einmal mit I2C entworfen[1]. (Da ich es für die Arbeit gemacht habe, kann ich den Code nicht posten.) Solange Sie die Kontrolle über alle Knoten haben (es sind alles von Ihnen programmierte MCUs), sollte dies funktionieren.

Grundsätzlich sind die Geräte wie gewohnt in einer Daisy-Chain mit I2C angeordnet. Zusätzlich zum I2C haben Sie eine Punkt-zu-Punkt-Logikleitung, die zwei PIO-Pins pro Knoten verwendet. Ein Pin ("Upstream Sense") ist nur Eingang und hochgezogen, während der andere Pin ("Downstream Sense") nur Ausgang ist, aber anfänglich drei Zustände hat (High-Z out) und optional hochgezogen ist. Der stromaufwärtige Lesestift jedes Knotens ist mit dem stromabwärtigen Lesestift des nächsten stromaufwärts gelegenen Chips verbunden. Die am weitesten stromaufwärts und am weitesten stromabwärts gelegenen Pins bleiben unverbunden. Optional kann jeder Knoten einen externen FET haben, der Pull-up-Widerstände mit dem I2C-Bus verbindet.

Beim Einschalten haben alle Knoten ihre I2C-Ports als Slaves mit der Adresse 0 oder so (spielt keine Rolle), treiben ihre Downstream-Sense-Pins auf 0 und warten eine feste Zeit (abhängig davon, wie lange es für alle Ihre dauert Knoten zum Einschalten und Initialisieren). Was sie empfangen möchten, ist eine "Rundruf"-Nachricht (Broadcast).

Welcher Knoten auch immer am weitesten stromaufwärts ist, wird in dieser Zeit nicht sehen, dass sein stromaufwärts gerichteter Sinn nach unten gezogen wird. Also geht er zuerst (wenn Pull-Ups FET-gesteuert sind, schaltet er seinen Pull-Up ein), stellt seinen Port als Master ein und sendet eine All-Call-Nachricht, die sich selbst an die anderen Knoten identifiziert, einschließlich seiner Adresse (was auch immer Sie möchten für den ersten verwenden möchten) und alle anderen Informationen, die identifizieren, was es für die anderen Knoten ist. Dann wartet es für eine festgelegte Zeitspanne auf einen anderen Knoten (sollte keiner sein, aber wer weiß), um eine All-Call-Nachricht zu senden, die besagt, dass er sich tatsächlich an der ersten Adresse befindet. Erhält er eine solche Nachricht, wiederholt er seine Identifizierung, jedoch mit der nächsten Adresse. Dieser Zyklus wiederholt sich, bis er eine verfügbare Adresse findet. (Dieses Muster ermöglicht es einem Knoten, sich zurückzusetzen und seine Adresse zurückzubekommen, ohne den Bus zu verwirren.)

Sobald er sich seiner Adresse sicher ist, stellt er sie im I2C-Peripheriegerät ein und geht in den Slave-Modus, um nach anderen Knoten zu lauschen, und treibt seine nachgeschaltete Abtastleitung hoch, was dem nächsten nachgeschalteten Knoten mitteilt, denselben Prozess zu durchlaufen, um seine Adresse zu erhalten Adresse. An diesem Punkt hört er nur auf Personen, die versuchen, seine Adresse zu beanspruchen, und zeichnet die Identifikationsinformationen der anderen Knoten auf. (Knoten hören auch auf die Identifikation anderer Knoten, bevor sie eine steigende Flanke auf Upstream-Sense erhalten, und erstellen eine Netzwerktabelle, aber sie haben noch keine beanspruchte Adresse, also prüfen sie nicht auf Kollisionen. Wenn es an der Zeit ist, Ansprüche zu erheben eine Adresse, kann es die Tabellendaten verwenden, um eine wahrscheinlich nicht beanspruchte Adresse auszuwählen.)

Nach all dem sollte jeder eindeutige I2C-Adressen haben und startklar sein. Dann verwenden Sie einfach I2C wie gewohnt. (Unnötig zu erwähnen, dass die Anfangsadresse aller Knoten nach der Konfiguration nicht verwendet werden konnte.) In unserem Setup wurde All-Call nur zur Konfiguration verwendet, und die direkte Adressierung wurde nur für die eigentliche Arbeit verwendet. Wenn Sie All-Call nach der Konfiguration verwenden möchten, müssen Sie Ihre All-Call-Nachricht so gestalten, dass sie anzeigt, in welchem ​​Modus sie sich befindet.

Es gibt wahrscheinlich viel, was hier optimiert werden kann, aber es sollte Ihnen einen Anfang geben. Wir haben dies auf einer Piggyback-Platine für ein Half-Brick-Netzteil verwendet, sodass Sie einfach alle benötigten Bricks zusammenstecken konnten (wir haben unseren Platinen Kantenstecker hinzugefügt, um I2C und die anderen Leitungen zu übertragen) und dann an einen seriellen Anschluss anschließen auf einen der Bausteine, um Informationen zu Spannung, Strom und Temperatur für alle zu erhalten. Es war ziemlich süß und brachte unserem Studenten (der das schwere Heben erledigte) eine Eins im Senior Lab ein. (Dann rannte er so schnell er konnte, um die Schule im ganzen Land zu absolvieren ...)

[1] Mit "entworfen" meine ich, dass ich etwas Ähnliches wie den obigen Text geschrieben habe, die 1% Inspiration pro Edison. Die 99% Schweiß wurde von meinem Bachelor-Studenten zur Verfügung gestellt.

Ich habe so etwas einmal für einen Mann entworfen, der Messungen an einem IIRC 30 x 30-Raster in einem Gewächshaus durchführen musste. Ich habe eine Kurzimpuls-/Langimpuls-Codierung verwendet, um die Ungenauigkeit der eingebauten PIC-Uhr zu berücksichtigen. Jede Kette von 30 Knoten verwendete einen 74HCT-Chip als Puffer/Regenerator. Ohne den PIC-Eingriff des Knotens erhielt jeder Knoten das Signal vom „Hauptmeister“. Aber jeder Knoten könnte das Signal zum nächsten Knoten blockieren, der während des Hochfahrens (Aufzählungsphase) verwendet wurde. Die PICs waren 16F819, damals die billigsten PICs, die sich selbst schreiben konnten. (Die ganze Kette könnte parallel Firmware-aktualisiert werden). Die Köpfe jeder Kette wurden wiederum auf ähnliche Weise verkettet, sodass von einem PC aus auf das gesamte Raster von ~ 1.000 Knoten zugegriffen werden konnte. IIRC, der Kunde, hatte dieses Setup einmal irgendwo in Spanien, ein paar 1000 km von seinem Wohnort entfernt.

IIRC die Baudrate war nicht so hoch, mach 19k2 oder so. Aber die Knotenkosten waren sehr gering: PIC, 74HCT-Chip, ein paar Widerstände und die immer vorhandenen 100 nF und 22 uF.

Ich benutze SPI viel und es scheint sehr gut zu funktionieren. Es gibt viele Geräte, die SPI unterstützen, und ich habe es in einer Reihe von Mikrocontroller-zu-Mikrocontroller-Steuerungsanwendungen verwendet.

SPI ist aufgrund seiner Anforderung "Keine Auswahlleitungen pro Modul" ausgefallen.
@Mike DeSimone - Sie können eine gemeinsame Auswahlleitung und weiche Adressierung mit demselben in der Frage vorgeschlagenen Zuordnungsschema verwenden, vorausgesetzt, es werden Mittel verwendet, um Buskollisionen erkennbar zu machen (Open-Collector-MISO-Treiber mit Pullup?). Es stellt sich wirklich die Frage, welches elektrische Schema vorzuziehen ist.
@Chris Stratton: Was Sie mit "weicher Adressierung" meinen, ist unklar. Die Kollisionserkennung erfordert, dass MISO tri-statefähig ist. Wenn Sie fertig sind, sind Sie mit mehr Drähten auf halbem Weg zu I2C.
@MikeDeSimone - Ich meine, in jedes Datenpaket eine Adresse einfügen. Gewöhnlicher Tri-State würde eigentlich nicht ganz für die Kollisionserkennung funktionieren, es würde etwas überschreibbares erfordern, wie z. B. Open Collector & Pullup. Natürlich können Sie Open-Collector mit Tristate simulieren, indem Sie die Freigabe basierend auf den Daten steuern oder nicht. Jeder von I2C und SPI hat seine Vorteile und Anhänger.

Ich weiß, Sie wollten > 1 MB, aber wenn Sie bereit sind, sich genau mit 1 MB zufrieden zu geben, dann ist der CAN-Bus ein ziemlich anständiger, kostengünstiger Bus. Wenn es sich um einen kurzen Bus handelt, kommen Sie möglicherweise sogar ohne Transceiver oder nur mit einfachen Transistoren davon. So würden Sie automatisch alle identischen Knoten auf dem Bus nummerieren. Jeder Knoten würde eine zufällige Anzahl von Millisekunden warten und dann eine einfache CAN-Nachricht senden. Die Anzahl der Can-Nachrichten, die es sieht, bevor es seine eigenen sendet, sagt ihm, welche ID es sein sollte. Wenn mehr als ein Knoten gleichzeitig eine Nachricht sendet, werden beide Nachrichten ignoriert und jeder Knoten wartet eine andere zufällige Zeit.

Jeder hätte den folgenden Code:

int negotiate_my_id(void)
{
    int pause_time = random number between 2 .. 100
    int my_id = 0;
    int num_messages_this_ms = 0;

    initialise_100kHz_timer();


    while(pause_time)
    {
        if (pause_time == 1)
            Send_CAN_message ( ID = 1, length = 0);


        timer_value = 100;                 // timer_value is decremented by the timer
        while(timer_value)
        {
            if (CAN_message_seen())        // Count num CAN messages seen
            {                              // during this millisecond.
                num_messages_this_ms++;    
                timer_value = 100;         // All nodes synchronise on message.
            }
        }

        if (num_messages_this_ms == 1)
        {
            my_id++;
        }

        if (num_messages_this_ms > 1)      // Saw a collision?
        {
            if (pause_time == 1)           // with my message?
                pause_time = random number between 2 .. 100
        }

        num_messages_this_ms = 0;
        pause_time--;
    }

   return my_id;
}