Protocoles — UART, I²C, SPI#
← Multitâche · Programme · OTA & NVS →
Grâce à la matrice GPIO de l'ESP32, UART, I²C et SPI peuvent être placés sur presque n'importe quelles broches : on les choisit dans le code, pas sur le schéma de la carte.
| Fils | Débit typique | Adressage | Usage | |
|---|---|---|---|---|
| UART (série) | TX, RX (+ GND) | 9 600 à 1 Mbit/s | point à point | GPS, MIDI, autre microcontrôleur, module DMX |
| I²C | SDA, SCL (+ pull-up) | 100 / 400 kHz | adresse 7 bits, jusqu'à ~100 périphériques | capteurs, petits écrans OLED, extensions GPIO |
| SPI | SCK, MOSI, MISO, CS par périphérique | 1 à 80 MHz | une broche CS par périphérique | écrans TFT, cartes SD, ADC rapides, mémoires |
UART#
Ouvrir ce montage dans Plot Circuits ↗
#include <Arduino.h>
// Serial = port série USB (moniteur)
// Serial1 = UART matériel, sur les broches de notre choix
#define RX1 5
#define TX1 6 // test sans matériel : reliez GPIO 5 et 6 par un fil (boucle)
void setup() {
Serial.begin(115200);
Serial1.begin(115200, SERIAL_8N1, RX1, TX1); // vitesse, format, RX, TX
// MIDI : Serial1.begin(31250, SERIAL_8N1, RX1, TX1);
}
void loop() {
Serial1.printf("ping %lu\n", millis()); // envoyé sur TX1…
delay(100);
while (Serial1.available()) { // …et relu sur RX1 grâce au fil
String line = Serial1.readStringUntil('\n');
Serial.printf("reçu sur l'UART : %s\n", line.c_str());
}
delay(900);
}
Serialest le port série USB ;Serial1etSerial2sont des UART matériels, sur les broches de votre choix.- Les niveaux sont en 3,3 V : un module en 5 V (Arduino UNO, certains GPS) a besoin d'un adaptateur de niveau sur la ligne qui arrive vers l'ESP32.
- Entre deux ESP32 : TX de l'une sur RX de l'autre (croisés) et GND commun.
- MIDI (DIN 5 broches) : 31 250 bauds ; l'entrée nécessite un optocoupleur (6N138…). Pour du MIDI par USB (environnement
usb-otg) : classeUSBMIDId'Arduino-ESP32 3.x, ou librairie Adafruit TinyUSB en 2.0.x.
Deux cartes reliées (TX de l'une sur RX de l'autre, GND commun) :
Ouvrir ce montage dans Plot Circuits ↗
I²C#
Scanner : qui est sur le bus ?#
Ouvrir ce montage dans Plot Circuits ↗
#include <Arduino.h>
#include <Wire.h>
#define SDA_PIN 8
#define SCL_PIN 9
void setup() {
Serial.begin(115200);
Wire.begin(SDA_PIN, SCL_PIN, 400000); // broches au choix, 400 kHz
// Un 2e bus indépendant : Wire1.begin(10, 11);
}
void loop() {
Serial.println("Scan I²C…");
int found = 0;
for (uint8_t addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) { // 0 = un périphérique a répondu
Serial.printf(" périphérique à 0x%02X\n", addr);
found++;
}
}
Serial.printf("%d trouvé(s)\n\n", found);
delay(3000);
}
Premier réflexe quand un capteur ne répond pas : s'il n'apparaît pas, c'est le câblage ou l'alimentation. Les modules du commerce ont presque toujours leurs résistances de pull-up ; sinon, 4,7 kΩ vers 3,3 V sur SDA et SCL.
Lire un registre, sans librairie#
Toute librairie de capteur I²C se résume à des écritures et lectures de registres :
#include <Arduino.h>
#include <Wire.h>
// Lecture d'un registre sans librairie : ici le VL53L0X (adresse 0x29)
// dont le registre 0xC0 contient toujours 0xEE (identifiant du modèle)
const uint8_t ADDR = 0x29;
uint8_t readRegister(uint8_t reg) {
Wire.beginTransmission(ADDR);
Wire.write(reg); // 1. on indique le registre…
Wire.endTransmission(false); // (false : sans relâcher le bus, « repeated start »)
Wire.requestFrom(ADDR, (uint8_t) 1); // 2. …puis on lit 1 octet
return Wire.available() ? Wire.read() : 0;
}
void setup() {
Serial.begin(115200);
Wire.begin(8, 9);
}
void loop() {
Serial.printf("ID modèle : 0x%02X (attendu 0xEE)\n", readRegister(0xC0));
delay(1000);
}
- Deux capteurs à la même adresse (deux VL53L0X par exemple) : soit on change l'adresse de l'un au démarrage (
setAddressdans la librairie Pololu, en gardant l'autre éteint via sa broche XSHUT), soit on utilise le 2e busWire1, soit un multiplexeur TCA9548A. - Exemple complet avec la librairie du VL53L0X : Jour 2.
Accéléromètre et gyroscope : MPU6050#
Le MPU6050 (ou MPU6500, MPU9250, compatibles) mesure l'accélération et la rotation sur 3 axes : inclinaison d'un objet, secousses, chocs. Toujours sans librairie :
Ouvrir ce montage dans Plot Circuits ↗
#include <Arduino.h>
#include <Wire.h>
// MPU6050 (accéléromètre + gyroscope 3 axes), en I²C à l'adresse 0x68 — sans librairie
const uint8_t MPU = 0x68;
void writeRegister(uint8_t reg, uint8_t value) {
Wire.beginTransmission(MPU);
Wire.write(reg);
Wire.write(value);
Wire.endTransmission();
}
struct Motion {
float ax, ay, az; // accélération en g
float gx, gy, gz; // vitesse de rotation en °/s
};
Motion readMotion() {
Wire.beginTransmission(MPU);
Wire.write(0x3B); // premier registre de mesure (ACCEL_XOUT_H)
Wire.endTransmission(false);
Wire.requestFrom(MPU, (uint8_t) 14); // 14 octets : accel (6), température (2), gyro (6)
int16_t raw[7];
for (int i = 0; i < 7; i++) raw[i] = (Wire.read() << 8) | Wire.read(); // valeurs 16 bits signées, octet fort d'abord
return {
raw[0] / 16384.0f, raw[1] / 16384.0f, raw[2] / 16384.0f, // échelle ±2 g
raw[4] / 131.0f, raw[5] / 131.0f, raw[6] / 131.0f // échelle ±250 °/s
};
}
float pitch = 0, roll = 0; // inclinaison en degrés
uint32_t lastMicros;
void setup() {
Serial.begin(115200);
Wire.begin(8, 9, 400000);
writeRegister(0x6B, 0x00); // PWR_MGMT_1 : sort le capteur du mode veille
writeRegister(0x1A, 0x03); // filtre passe-bas interne (~44 Hz)
lastMicros = micros();
}
void loop() {
Motion m = readMotion();
float dt = (micros() - lastMicros) / 1e6;
lastMicros = micros();
// Inclinaison d'après la gravité (juste mais bruitée)…
float accPitch = atan2f(-m.ax, sqrtf(m.ay * m.ay + m.az * m.az)) * RAD_TO_DEG;
float accRoll = atan2f(m.ay, m.az) * RAD_TO_DEG;
// …et d'après le gyroscope (lisse mais qui dérive) : filtre complémentaire = le meilleur des deux
pitch = 0.98f * (pitch + m.gy * dt) + 0.02f * accPitch;
roll = 0.98f * (roll + m.gx * dt) + 0.02f * accRoll;
Serial.printf("pitch:%.1f,roll:%.1f,accel:%.2f\n", pitch, roll, sqrtf(m.ax * m.ax + m.ay * m.ay + m.az * m.az));
delay(10);
}
- L'accéléromètre donne l'inclinaison (la gravité indique le bas) mais réagit aux secousses ; le gyroscope est précis sur les mouvements rapides mais dérive avec le temps. Le filtre complémentaire (98 % gyro + 2 % accéléro) combine les deux.
- La norme de l'accélération (
accel) vaut ~1 g au repos : un pic au-dessus de 2 g = un choc, une secousse. - Le cap (rotation autour de la verticale) n'est pas mesurable sans magnétomètre. Pour une orientation 3D complète et sans dérive : BNO055 ou BNO085 (fusion de capteurs intégrée), avec leurs librairies Adafruit.
SPI#
Ouvrir ce montage dans Plot Circuits ↗
#include <Arduino.h>
#include <SPI.h>
// Le bus SPI peut utiliser n'importe quelles broches
#define SCK_PIN 12
#define MISO_PIN 13
#define MOSI_PIN 11
#define CS_PIN 10
// Test sans matériel : reliez MOSI (11) et MISO (13) par un fil
SPIClass spi(FSPI); // un des 2 contrôleurs SPI disponibles (FSPI, HSPI)
void setup() {
Serial.begin(115200);
spi.begin(SCK_PIN, MISO_PIN, MOSI_PIN, CS_PIN);
pinMode(CS_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH);
}
void loop() {
uint8_t tx[4] = {0xDE, 0xAD, 0xBE, 0xEF};
uint8_t rx[4];
// Une « transaction » : vitesse, ordre des bits et mode propres à ce périphérique
spi.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
digitalWrite(CS_PIN, LOW); // sélection du périphérique
for (int i = 0; i < 4; i++) {
rx[i] = spi.transfer(tx[i]); // le SPI envoie ET reçoit un octet en même temps
}
digitalWrite(CS_PIN, HIGH);
spi.endTransaction();
Serial.printf("envoyé DE AD BE EF, reçu %02X %02X %02X %02X\n", rx[0], rx[1], rx[2], rx[3]);
delay(1000);
}
- Les réglages (
SPISettings) changent d'un périphérique à l'autre : vitesse max,MSBFIRST, mode 0 à 3. Ils sont donnés dans la fiche technique. - Plusieurs périphériques sur le même bus : SCK / MOSI / MISO partagés, une broche CS par périphérique.
- En pratique, on passe par une librairie : TFT_eSPI ou LovyanGFX (écrans),
SD(carte SD), Adafruit MCP3008 (ADC)… en lui donnant les broches choisies. - Les dalles LED HUB75 ressemblent à du SPI mais en parallèle sur 6 lignes de données : voir Matrices LED.
Lecteur RFID / NFC : RC522#
Des objets reconnus quand on les pose : chaque badge, carte ou autocollant NFC a un identifiant unique. Librairie : lib_deps = miguelbalboa/MFRC522.
Ouvrir ce montage dans Plot Circuits ↗
#include <Arduino.h>
#include <SPI.h>
#include <MFRC522.h>
// Lecteur RFID RC522 (13,56 MHz : badges et cartes MIFARE, autocollants NTAG…), en SPI, alimenté en 3,3 V
#define RFID_SCK 12
#define RFID_MISO 13
#define RFID_MOSI 11
#define RFID_SS 10
#define RFID_RST 9
MFRC522 rfid(RFID_SS, RFID_RST);
String uidToString(const MFRC522::Uid& uid) {
String s;
for (byte i = 0; i < uid.size; i++) {
if (uid.uidByte[i] < 0x10) s += "0";
s += String(uid.uidByte[i], HEX);
}
s.toUpperCase();
return s;
}
void setup() {
Serial.begin(115200);
SPI.begin(RFID_SCK, RFID_MISO, RFID_MOSI, RFID_SS); // broches SPI au choix
rfid.PCD_Init();
rfid.PCD_DumpVersionToSerial(); // vérifie que le lecteur répond
}
void loop() {
// Une nouvelle carte est-elle posée ?
if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) {
delay(50);
return;
}
String uid = uidToString(rfid.uid); // identifiant unique de la carte
Serial.printf("Carte %s (%s)\n", uid.c_str(),
rfid.PICC_GetTypeName(rfid.PICC_GetType(rfid.uid.sak)));
// Associer des objets à des actions
if (uid == "04A2B3C4D5E680") { // ← remplacez par l'UID de vos cartes
Serial.println("→ objet « clé »");
}
rfid.PICC_HaltA(); // on attend que la carte soit retirée puis reposée
rfid.PCD_StopCrypto1();
}
- Lisez d'abord les UID de vos cartes dans le moniteur série, puis associez-les à des actions.
- Portée : quelques centimètres ; le métal à proximité la réduit fortement.
- Le RC522 est en 3,3 V (jamais en 5 V). Son SPI peut partager le bus avec d'autres périphériques (une broche SS chacun).
- Plusieurs lecteurs : un objet
MFRC522par lecteur, chacun avec sa broche SS. - Le PN532 (I²C ou SPI) sait aussi lire les téléphones en NFC et les tags NTAG plus récents.