Ich versuche, einen Bus für ein erweiterbares System auszuwählen. Zu den Anforderungen gehören idealerweise:
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?
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.
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;
}
Jon L
Saad
tlrobinson
tlrobinson
bdutta74