Einleitung
Kommunikationsprotokolle sind das Rückgrat eingebetteter Systeme. Ob Sie einen Sensor auslesen, mit einer SD-Karte kommunizieren oder Debug-Nachrichten ausgeben – Sie verwenden eines der drei wichtigsten Protokolle: Serial (UART), I2C (TWI) oder SPI. In diesem Tutorial werden wir alle drei gleichzeitig auf einem Arduino Uno im browserbasierten Velxio-Simulator ausführen. Sie werden sehen, wie man den I2C-Bus scannt, ein EEPROM liest/schreibt, SPI-Übertragungen durchführt und alles über die serielle Schnittstelle protokolliert.

Schaltungsdurchlauf
Dieses Beispiel verwendet nur das Arduino Uno-Board selbst – es werden keine externen Komponenten benötigt. Die integrierten Peripheriegeräte des Arduino bieten alles, was wir brauchen:
- Serial (UART): Verwendet die Pins 0 (RX) und 1 (TX) für die Kommunikation mit dem seriellen Monitor.
- I2C (TWI): Verwendet die Pins A4 (SDA) und A5 (SCL). Die internen Pull-Up-Widerstände werden von der Wire-Bibliothek aktiviert.
- SPI: Verwendet die Pins 10 (SS), 11 (MOSI), 12 (MISO) und 13 (SCK). Pin 10 ist als Slave-Select-Ausgang konfiguriert.
Es ist keine Verkabelung erforderlich, da alle Verbindungen intern im Arduino erfolgen. Die Velxio-Leinwand zeigt den Uno mit seinen Pin-Beschriftungen, und Sie können die virtuellen Komponenten durch Öffnen der Komponentenauswahl inspizieren.

Code-Durchlauf
Lassen Sie uns den Sketch Schritt für Schritt aufschlüsseln.
Bibliotheken und Definitionen
#include <Wire.h>
#include <SPI.h>
#define DS1307_ADDR 0x68
#define EEPROM_ADDR 0x50
#define SS_PIN 10
Wir binden die Wire-Bibliothek für I2C und die SPI-Bibliothek für SPI ein. Zwei I2C-Adressen werden definiert: 0x68 für eine DS1307 RTC (obwohl nicht physisch vorhanden, werden wir danach scannen) und 0x50 für ein 24Cxx-EEPROM. Der SPI-Slave-Select-Pin wird auf digitalen Pin 10 gesetzt.
Hilfsfunktionen
byte bcdToDec(byte val) {
return ((val >> 4) * 10) + (val & 0x0F);
}
void readRTC(byte &hr, byte &min, byte &sec) {
Wire.beginTransmission(DS1307_ADDR);
Wire.write(0x00);
Wire.endTransmission();
Wire.requestFrom(DS1307_ADDR, 3);
sec = bcdToDec(Wire.read() & 0x7F);
min = bcdToDec(Wire.read());
hr = bcdToDec(Wire.read() & 0x3F);
}
Die Funktion bcdToDec konvertiert binär codierte Dezimalzahlen (BCD) in Dezimalzahlen. readRTC liest die Sekunden, Minuten und Stunden von einer DS1307 RTC. Die Masken & 0x7F und & 0x3F löschen ungenutzte Bits.
void writeEEPROM(byte reg, byte value) {
Wire.beginTransmission(EEPROM_ADDR);
Wire.write(reg);
Wire.write(value);
Wire.endTransmission();
delay(5);
}
byte readEEPROM(byte reg) {
Wire.beginTransmission(EEPROM_ADDR);
Wire.write(reg);
Wire.endTransmission();
Wire.requestFrom(EEPROM_ADDR, 1);
return Wire.available() ? Wire.read() : 0xFF;
}
Diese Funktionen schreiben und lesen ein einzelnes Byte von einem I2C-EEPROM an Adresse 0x50. Die delay(5) stellt sicher, dass das EEPROM Zeit hat, den Schreibzyklus abzuschließen.
byte spiTransfer(byte data) {
digitalWrite(SS_PIN, LOW);
byte result = SPI.transfer(data);
digitalWrite(SS_PIN, HIGH);
return result;
}
Diese Funktion führt eine Vollduplex-SPI-Übertragung durch: Sie zieht SS auf LOW, sendet ein Byte, empfängt die Antwort und zieht SS wieder auf HIGH.
Setup
void setup() {
Serial.begin(9600);
Wire.begin();
pinMode(SS_PIN, OUTPUT);
digitalWrite(SS_PIN, HIGH);
SPI.begin();
Serial.println("===================================");
Serial.println(" Multi-Protocol Demo");
Serial.println(" Serial (USART) + I2C (TWI) + SPI");
Serial.println("===================================");
Serial.println();
// ── I2C: Scan bus ──
Serial.println("[I2C] Scanning bus...");
int found = 0;
for (byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) {
Serial.print(" Found device at 0x");
if (addr < 16) Serial.print("0");
Serial.println(addr, HEX);
found++;
}
}
Serial.print(" ");
Serial.print(found);
Serial.println(" device(s) on I2C bus.");
Serial.println();
// ── I2C: Write/read EEPROM ──
Serial.println("[I2C] EEPROM write/read test:");
writeEEPROM(0, 42);
writeEEPROM(1, 99);
byte v0 = readEEPROM(0);
byte v1 = readEEPROM(1);
Serial.print(" Wrote 42, read ");
Serial.print(v0);
Serial.println(v0 == 42 ? " [OK]" : " [FAIL]");
Serial.print(" Wrote 99, read ");
Serial.print(v1);
Serial.println(v1 == 99 ? " [OK]" : " [FAIL]");
Serial.println();
// ── SPI: Transfer test ──
Serial.println("[SPI] Transfer test:");
byte spiData[] = {0xAA, 0x55, 0x42};
for (int i = 0; i < 3; i++) {
byte rx = spiTransfer(spiData[i]);
Serial.print(" TX=0x");
if (spiData[i] < 16) Serial.print("0");
Serial.print(spiData[i], HEX);
Serial.print(" RX=0x");
if (rx < 16) Serial.print("0");
Serial.println(rx, HEX);
}
Serial.println();
Serial.println("Setup complete. Reading RTC...");
Serial.println();
}
In setup() initialisieren wir alle drei Protokolle. Dann führen wir folgende Schritte durch:
- Den I2C-Bus scannen nach Geräten von Adresse 1 bis 126.
- EEPROM testen, indem wir die Werte 42 und 99 an die Adressen 0 und 1 schreiben, sie dann zurücklesen und überprüfen.
- SPI testen, indem wir drei Bytes (
0xAA,0x55,0x42) senden und die empfangenen Bytes ausgeben.
Loop
void loop() {
// ── Serial: Print RTC time every 2 seconds ──
byte hr, min, sec;
readRTC(hr, min, sec);
Serial.print("[RTC] ");
if (hr < 10) Serial.print("0");
Serial.print(hr);
Serial.print(":");
if (min < 10) Serial.print("0");
Serial.print(min);
Serial.print(":");
if (sec < 10) Serial.print("0");
Serial.print(sec);
Serial.print(" | Uptime: ");
Serial.print(millis() / 1000);
Serial.println("s");
delay(2000);
}
Alle zwei Sekunden liest die Schleife die RTC-Zeit und gibt sie zusammen mit der Systemlaufzeit aus. Dies demonstriert die kontinuierliche serielle Ausgabe, während I2C und SPI im Leerlauf sind.
Schlüsselkonzepte
- Serial (UART): Asynchron, Punkt-zu-Punkt. Wird für Debug-Ausgaben und die Kommunikation mit PCs oder Modulen wie GPS verwendet.
- I2C: Synchron, Multi-Master, verwendet zwei Leitungen (SDA, SCL). Jedes Gerät hat eine eindeutige Adresse. Ideal für Sensoren und EEPROMs.
- SPI: Synchron, Vollduplex, verwendet vier Leitungen (MOSI, MISO, SCK, SS). Schneller als I2C, benötigt aber mehr Pins.
- Bus-Scanning: Durchlaufen von I2C-Adressen, um angeschlossene Geräte zu entdecken.
- EEPROM Lesen/Schreiben: Nichtflüchtiger Speicher, der Daten nach einem Stromausfall behält.
- SPI-Transfer: Gleichzeitiges Senden und Empfangen von Daten.
Häufige Fallstricke & Debugging-Tipps
- I2C-Pull-Up-Widerstände: Die Wire-Bibliothek aktiviert interne Pull-Ups, aber für längere Busse werden externe Widerstände (4,7 kΩ) empfohlen.
- SPI-SS-Pin: Setzen Sie SS immer als Ausgang und halten Sie ihn auf HIGH, wenn er nicht verwendet wird, um ein versehentliches Umschalten in den Slave-Modus zu vermeiden.
- EEPROM-Schreib-Timing: Warten Sie nach einem Schreibvorgang mindestens 5 ms vor dem nächsten Vorgang.
- Serielle Monitor-Baudrate: Stellen Sie sicher, dass der serielle Monitor auf 9600 Baud eingestellt ist.
- Keine externen Geräte: In dieser Simulation findet der I2C-Scan keine Geräte (außer möglicherweise das interne TWI des Arduino). Der EEPROM-Test verwendet ein virtuelles EEPROM, das in den Simulator integriert ist.
Vorgeschlagene Erweiterungen
- Echte Sensoren hinzufügen: Schließen Sie eine DS1307 RTC und ein 24Cxx-EEPROM an den I2C-Bus sowie ein SD-Kartenmodul über SPI an.
- Daten im EEPROM protokollieren: Speichern Sie Sensorwerte und rufen Sie sie nach einem Reset ab.
- Interrupts verwenden: Lösen Sie eine SPI-Übertragung durch einen Tastendruck aus.
- Eine Befehlsschnittstelle implementieren: Parsen Sie serielle Befehle, um I2C- und SPI-Operationen zu steuern.
Probieren Sie es selbst aus
Bereit, alle drei Protokolle in Aktion zu sehen? Öffnen Sie das Live-Beispiel in Velxio, kompilieren Sie es und führen Sie es aus. Beobachten Sie die Ausgabe des seriellen Monitors, während der Arduino den I2C-Bus scannt, das EEPROM testet, SPI-Übertragungen durchführt und dann kontinuierlich die RTC-Zeit ausgibt.
Starten Sie die Multi-Protokoll-Demo auf Velxio

Viel Spaß beim Hacken!