Protocoles de Communication

I2C - SPI - UART - CAN - RS-485

Communications Serie pour Systemes Embarques

I²C SPI UART CAN Bus
📡

Chapitre 1 : Introduction aux Communications Serie

Concepts fondamentaux des protocoles de communication

🎯 Objectifs du chapitre

  • Differencier communication serie et parallele
  • Comprendre les modes synchrone et asynchrone
  • Connaitre les topologies de communication
  • Identifier les protocoles adaptes a chaque usage

1.1 Communication Serie vs Parallele

    COMMUNICATION PARALLELE           COMMUNICATION SERIE
    
    Emetteur    Recepteur             Emetteur    Recepteur
    ┌─────┐     ┌─────┐               ┌─────┐     ┌─────┐
    │  D0 │─────│ D0  │               │     │     │     │
    │  D1 │─────│ D1  │               │ TX  │─────│ RX  │
    │  D2 │─────│ D2  │               │     │     │     │
    │  D3 │─────│ D3  │               └─────┘     └─────┘
    │  D4 │─────│ D4  │               
    │  D5 │─────│ D5  │               1 bit a la fois
    │  D6 │─────│ D6  │               Moins de fils
    │  D7 │─────│ D7  │               Longues distances
    └─────┘     └─────┘               
                                      
    8 bits simultanes                 
    Beaucoup de fils                  
    Courtes distances                 
                    
CritereParalleleSerie
VitesseRapide (theorique)Plus lente par bit
Nombre de fils8+ fils donnees1-4 fils
DistanceCourte (<1m)Longue (km possible)
CoutEleve (cablage)Faible
InterferencesProbleme de diaphonieMeilleure immunite
ExemplePort parallele, bus memoireUART, I2C, SPI, USB

1.2 Synchrone vs Asynchrone

Communication Asynchrone

  • Pas de signal d'horloge partage
  • Utilise des bits de START et STOP
  • Emetteur et recepteur doivent avoir la meme vitesse (baud rate)
  • Exemple : UART

Communication Synchrone

  • Signal d'horloge (CLK) partage entre emetteur et recepteur
  • Les donnees sont echantillonnees sur front montant ou descendant
  • Plus rapide et plus fiable
  • Exemples : I2C, SPI
    ASYNCHRONE (UART)                 SYNCHRONE (SPI/I2C)
    
    TX ──┬─┬─┬─┬─┬─┬─┬─┬─┬──         CLK ─┬─┬─┬─┬─┬─┬─┬─┬─
         │S│0│1│0│1│1│0│1│P│              │ │ │ │ │ │ │ │ │
                                     
    S = Start bit                    DATA ─┬───┬───┬───┬───
    P = Stop bit                          │ 0 │ 1 │ 0 │ 1 │
    Pas d'horloge commune                 
                                     Donnees synchronisees 
                                     avec l'horloge
                    

1.3 Topologies de Communication

↔️

Point a Point

2 dispositifs seulement
UART classique

🌐

Multi-maitre

Plusieurs maitres
I2C, CAN

Maitre-Esclave

1 maitre, N esclaves
SPI

Modes de transmission

  • Simplex : Communication dans un seul sens
  • Half-duplex : Communication bidirectionnelle, mais pas simultanee
  • Full-duplex : Communication bidirectionnelle simultanee (TX et RX)

1.4 Vue d'ensemble des Protocoles

ProtocoleTypeFilsVitesseDistanceUsage
UARTAsynchrone2 (TX/RX)~1 Mbps~15 mDebug, GPS, Bluetooth
I2CSynchrone2 (SDA/SCL)100-400 kbps~1 mCapteurs, EEPROM, RTC
SPISynchrone4 (MOSI/MISO/SCK/SS)~50 Mbps~1 mFlash, ecrans, ADC
CANAsynchrone2 (CAN_H/CAN_L)1 Mbps~1 kmAutomobile, industrie
RS-485Asynchrone2 (A/B)10 Mbps~1.2 kmIndustrie, Modbus
USBSynchrone4480 Mbps+5 mPeripheriques PC
📤

Chapitre 2 : UART - Universal Asynchronous Receiver Transmitter

Communication serie asynchrone

🎯 Objectifs du chapitre

  • Comprendre le fonctionnement de l'UART
  • Configurer les parametres (baud rate, parite)
  • Analyser une trame UART
  • Programmer la communication UART

2.1 Principe de l'UART

📖
UART (Universal Asynchronous Receiver Transmitter) Protocole de communication serie asynchrone point-a-point. Pas d'horloge partagee : emetteur et recepteur doivent etre configures avec le meme baud rate.

Connexion UART

    CONNEXION UART CROISEE
    
    Dispositif A              Dispositif B
    ┌──────────┐              ┌──────────┐
    │          │    TX ───────│ RX       │
    │   MCU    │              │   MCU    │
    │          │    RX ───────│ TX       │
    │          │              │          │
    │          │   GND ───────│ GND      │
    └──────────┘              └──────────┘
    
    TX (Transmit) de A → RX (Receive) de B
    RX (Receive) de A ← TX (Transmit) de B
    GND commun obligatoire
                    

2.2 Format de la Trame UART

    TRAME UART - Format 8N1 (le plus courant)
    
    Ligne au repos (IDLE) = niveau HAUT (1)
    
         ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
    1 ───┤   │   │   │   │   │   │   │   │   │   │   │─── 1
         │   │ D │ D │ D │ D │ D │ D │ D │ D │   │   │
    0 ───┤ S │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ P │ T │
         │ T │   │   │   │   │   │   │   │   │   │ P │
         │ A │   │   │ DONNEES (LSB first) │   │   │
         │ R │   │   │                     │ A │ S │
         │ T │   │   │                     │ R │ T │
         └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
    
    START bit : Toujours 0 (passage de 1 a 0)
    DATA bits : 5 a 9 bits (generalement 8)
    PARITY    : Optionnel (Even, Odd, None)
    STOP bit  : 1 ou 2 bits a 1
                    

Notation courante

  • 8N1 : 8 bits de donnees, No parity, 1 stop bit
  • 8E1 : 8 bits de donnees, Even parity, 1 stop bit
  • 8O1 : 8 bits de donnees, Odd parity, 1 stop bit

2.3 Baud Rate

📖
Baud Rate Nombre de symboles par seconde. En UART (2 niveaux), 1 baud = 1 bit/s. Les deux dispositifs DOIVENT avoir le meme baud rate (tolerance ±2-3%).

Baud rates standards

Baud RateDuree bitUsage
9600104.17 µsCapteurs, GPS (defaut courant)
1920052.08 µsModems
3840026.04 µsCommunication rapide
5760017.36 µsCommunication rapide
1152008.68 µsDebug, Bluetooth, WiFi modules
9216001.09 µsHaute vitesse
Duree d'un bit = 1 / Baud Rate

2.4 Programmation UART (Arduino)

// UART sur Arduino

void setup() {
    // Initialiser UART a 115200 baud
    Serial.begin(115200);
    
    // Attendre l'ouverture du port
    while (!Serial) {
        ; // Necessaire pour certaines cartes
    }
    
    Serial.println("UART initialise a 115200 baud");
}

void loop() {
    // Verifier si donnees disponibles
    if (Serial.available() > 0) {
        // Lire un octet
        char received = Serial.read();
        
        // Echo : renvoyer le caractere recu
        Serial.print("Recu: ");
        Serial.println(received);
    }
}

// Envoyer des donnees
Serial.print("Hello");        // Texte sans retour ligne
Serial.println("World");      // Texte avec retour ligne
Serial.write(0x41);          // Envoyer un octet (ASCII 'A')
Serial.write(buffer, len);   // Envoyer un tableau

2.5 UART sur STM32

// UART sur STM32 (HAL)

// Configuration dans CubeMX : USART2, 115200, 8N1

UART_HandleTypeDef huart2;
uint8_t rxBuffer[100];
uint8_t txData[] = "Hello STM32!\r\n";

// Envoi bloquant
HAL_UART_Transmit(&huart2, txData, strlen((char*)txData), 100);

// Reception bloquante (attend 10 octets)
HAL_UART_Receive(&huart2, rxBuffer, 10, 1000);

// Envoi non-bloquant (interruption)
HAL_UART_Transmit_IT(&huart2, txData, strlen((char*)txData));

// Reception par interruption
HAL_UART_Receive_IT(&huart2, rxBuffer, 1);

// Callback de reception
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart == &huart2) {
        // Traiter rxBuffer[0]
        HAL_UART_Receive_IT(&huart2, rxBuffer, 1); // Relancer
    }
}
🔗

Chapitre 3 : I2C - Principes

Inter-Integrated Circuit - Fondamentaux

🎯 Objectifs du chapitre

  • Comprendre l'architecture I2C
  • Connaitre le format des trames I2C
  • Maitriser l'adressage 7 bits
  • Analyser les conditions START et STOP

3.1 Presentation de l'I2C

📖
I2C (Inter-Integrated Circuit) Bus serie synchrone developpe par Philips (maintenant NXP). Utilise seulement 2 fils (SDA et SCL) pour connecter plusieurs peripheriques. Architecture maitre-esclave avec possibilite multi-maitre.

Caracteristiques

2️⃣

2 fils

SDA (Data)
SCL (Clock)

📍

Adressage

7 ou 10 bits
128 adresses max

Vitesses

100 kHz (Standard)
400 kHz (Fast)

3.2 Architecture du Bus I2C

    BUS I2C - ARCHITECTURE
    
    VCC (3.3V ou 5V)
     │           │
    ┌┴┐         ┌┴┐          Resistances de pull-up
    │R│ 4.7kΩ   │R│ 4.7kΩ    (obligatoires car open-drain)
    └┬┘         └┬┘
     │           │
     ├───────────┼─────────────────────────── SDA (Data)
     │           │
     │           ├─────────────────────────── SCL (Clock)
     │           │
    ┌┴──────────┴┐   ┌──────────┐   ┌──────────┐   ┌──────────┐
    │   MAÎTRE   │   │ ESCLAVE  │   │ ESCLAVE  │   │ ESCLAVE  │
    │   (MCU)    │   │  0x48    │   │  0x68    │   │  0x27    │
    │            │   │ (Temp)   │   │  (RTC)   │   │  (LCD)   │
    └────────────┘   └──────────┘   └──────────┘   └──────────┘
    
    - Maitre genere l'horloge SCL
    - Chaque esclave a une adresse unique
    - Jusqu'a 128 dispositifs (adressage 7 bits)
                    
⚠️
Resistances de Pull-up Les lignes SDA et SCL sont en open-drain : elles ne peuvent que tirer vers le bas. Les resistances de pull-up (typiquement 4.7kΩ a 10kΩ) ramenent les lignes a VCC quand personne ne tire.

3.3 Conditions START et STOP

    CONDITIONS START ET STOP
    
    SCL ────────┐   ┌─────────────┐   ┌──────────────
                │   │             │   │
                └───┘             └───┘
    
    SDA ──────┐       ┌─────────────┐       ┌────────
              │       │             │       │
              └───────┘             └───────┘
    
           │START│              │ STOP │
           └─────┘              └──────┘
    
    START: SDA passe de HIGH a LOW pendant que SCL est HIGH
    STOP:  SDA passe de LOW a HIGH pendant que SCL est HIGH
    
    (Normalement SDA ne change que quand SCL est LOW)
                    

3.4 Format de la Trame I2C

    TRAME I2C - ECRITURE
    
    ┌───────┬─────────────────────┬─────┬─────────────────┬─────┬──────┐
    │ START │ ADRESSE (7 bits)    │ R/W │ ACK             │DATA │ STOP │
    │       │ A6 A5 A4 A3 A2 A1 A0│  0  │ (esclave)       │ 8b  │      │
    └───────┴─────────────────────┴─────┴─────────────────┴─────┴──────┘
    
    R/W = 0 : Ecriture (Maitre → Esclave)
    R/W = 1 : Lecture (Esclave → Maitre)
    
    ACK (Acknowledge) : L'esclave tire SDA a LOW pour confirmer
    NACK : SDA reste HIGH (pas de reponse)
    
    
    TRAME I2C - LECTURE
    
    ┌───────┬──────────────┬─────┬─────┬──────────────┬─────┬─────┬──────┐
    │ START │ ADRESSE 7b   │ R/W │ ACK │ REGISTRE     │ ACK │     │      │
    │       │              │  0  │     │              │     │     │      │
    ├───────┼──────────────┼─────┼─────┼──────────────┼─────┼─────┼──────┤
    │ START │ ADRESSE 7b   │ R/W │ ACK │ DATA (escl.) │NACK │STOP │      │
    │ REP.  │              │  1  │     │              │     │     │      │
    └───────┴──────────────┴─────┴─────┴──────────────┴─────┴─────┴──────┘
                    

3.5 Adresses I2C Courantes

PeripheriqueAdresse (7 bits)Usage
EEPROM 24C020x50 - 0x57Memoire non volatile
DS1307 / DS32310x68Horloge temps reel (RTC)
BMP280 / BME2800x76 ou 0x77Capteur pression/temperature
SHT310x44 ou 0x45Capteur humidite/temperature
MPU60500x68 ou 0x69Accelerometre/Gyroscope
OLED SSD13060x3C ou 0x3DEcran OLED
PCF85740x20 - 0x27Expanseur GPIO
LCD I2C (PCF8574)0x27 ou 0x3FEcran LCD 16x2
⚙️

Chapitre 4 : I2C - Pratique

Programmation I2C sur Arduino et STM32

🎯 Objectifs du chapitre

  • Scanner le bus I2C
  • Lire et ecrire des registres
  • Interfacer des capteurs I2C courants
  • Deboguer les problemes I2C

4.1 I2C sur Arduino

// I2C sur Arduino avec Wire.h
#include <Wire.h>

void setup() {
    Wire.begin();          // Initialiser I2C (maitre)
    Serial.begin(115200);
}

// Scanner I2C - Trouver tous les peripheriques
void scanI2C() {
    Serial.println("Scan I2C...");
    
    for (byte addr = 1; addr < 127; addr++) {
        Wire.beginTransmission(addr);
        byte error = Wire.endTransmission();
        
        if (error == 0) {
            Serial.print("Trouve a 0x");
            Serial.println(addr, HEX);
        }
    }
}

// Ecriture d'un registre
void writeRegister(uint8_t deviceAddr, uint8_t reg, uint8_t value) {
    Wire.beginTransmission(deviceAddr);
    Wire.write(reg);
    Wire.write(value);
    Wire.endTransmission();
}

// Lecture d'un registre
uint8_t readRegister(uint8_t deviceAddr, uint8_t reg) {
    Wire.beginTransmission(deviceAddr);
    Wire.write(reg);
    Wire.endTransmission();
    
    Wire.requestFrom(deviceAddr, (uint8_t)1);
    return Wire.read();
}

4.2 Exemple : Capteur BME280

// Lecture temperature avec BME280
#include <Wire.h>
#include <Adafruit_BME280.h>

Adafruit_BME280 bme;
#define BME280_ADDR 0x76

void setup() {
    Serial.begin(115200);
    Wire.begin();
    
    if (!bme.begin(BME280_ADDR)) {
        Serial.println("BME280 non trouve!");
        while(1);
    }
    Serial.println("BME280 OK");
}

void loop() {
    float temp = bme.readTemperature();
    float hum = bme.readHumidity();
    float pres = bme.readPressure() / 100.0; // hPa
    
    Serial.print("Temp: "); Serial.print(temp); Serial.println(" °C");
    Serial.print("Hum: ");  Serial.print(hum);  Serial.println(" %");
    Serial.print("Pres: "); Serial.print(pres); Serial.println(" hPa");
    
    delay(2000);
}

4.3 I2C sur STM32 (HAL)

// I2C sur STM32 avec HAL

I2C_HandleTypeDef hi2c1;
#define DEVICE_ADDR  (0x76 << 1)  // Adresse 8 bits (decalee)

// Ecriture d'un registre
HAL_StatusTypeDef I2C_WriteReg(uint8_t reg, uint8_t value) {
    uint8_t data[2] = {reg, value};
    return HAL_I2C_Master_Transmit(&hi2c1, DEVICE_ADDR, data, 2, 100);
}

// Lecture d'un registre
uint8_t I2C_ReadReg(uint8_t reg) {
    uint8_t value;
    HAL_I2C_Master_Transmit(&hi2c1, DEVICE_ADDR, ®, 1, 100);
    HAL_I2C_Master_Receive(&hi2c1, DEVICE_ADDR, &value, 1, 100);
    return value;
}

// Avec HAL_I2C_Mem_Read (plus simple)
uint8_t I2C_ReadReg_Mem(uint8_t reg) {
    uint8_t value;
    HAL_I2C_Mem_Read(&hi2c1, DEVICE_ADDR, reg, 1, &value, 1, 100);
    return value;
}

Chapitre 5 : SPI - Principes

Serial Peripheral Interface - Fondamentaux

🎯 Objectifs du chapitre

  • Comprendre l'architecture SPI
  • Connaitre les 4 modes SPI (CPOL/CPHA)
  • Maitriser la selection des esclaves
  • Comparer SPI et I2C

5.1 Presentation du SPI

📖
SPI (Serial Peripheral Interface) Bus serie synchrone full-duplex developpe par Motorola. Utilise 4 fils et une architecture maitre-esclave strict. Plus rapide que I2C mais necessite plus de fils.

Signaux SPI

SignalAutres nomsDirectionFonction
SCKSCLK, CLKMaitre → EsclaveHorloge
MOSISDO, DO, DOUT, SIMaitre → EsclaveDonnees maitre vers esclave
MISOSDI, DI, DIN, SOEsclave → MaitreDonnees esclave vers maitre
SSCS, NSS, CEMaitre → EsclaveChip Select (actif bas)

5.2 Architecture SPI

    ARCHITECTURE SPI
    
                                        ┌──────────────┐
                                        │  ESCLAVE 1   │
                               ┌────────│  (SD Card)   │
                               │        └──────────────┘
                               │ SS1
    ┌──────────────┐           │        ┌──────────────┐
    │   MAÎTRE     │───────────┼────────│  ESCLAVE 2   │
    │    (MCU)     │  SCK      │  SS2   │  (Display)   │
    │              │───────────┼────────└──────────────┘
    │              │           │
    │              │───────────┼────────┌──────────────┐
    │              │  MOSI     │  SS3   │  ESCLAVE 3   │
    │              │───────────┼────────│  (Flash)     │
    │              │           │        └──────────────┘
    │              │  MISO     │
    │              │───────────┘
    └──────────────┘
    
    - SCK, MOSI, MISO : partages par tous les esclaves
    - SS : un fil dedie par esclave (actif LOW)
    - Un seul esclave actif a la fois
                    

5.3 Modes SPI (CPOL / CPHA)

Le SPI a 4 modes definis par CPOL (polarite horloge) et CPHA (phase donnees).

ModeCPOLCPHAHorloge reposEchantillonnage
Mode 000LOWFront montant
Mode 101LOWFront descendant
Mode 210HIGHFront descendant
Mode 311HIGHFront montant
    MODE 0 (CPOL=0, CPHA=0) - Le plus courant
    
    SS   ─────┐                               ┌─────
              └───────────────────────────────┘
    
    SCK  ─────────┬───┬───┬───┬───┬───┬───┬───┬─────
                  │   │   │   │   │   │   │   │
                  └───┘   └───┘   └───┘   └───┘
    
    MOSI ─────────┬───────┬───────┬───────┬───────
                  │  D7   │  D6   │  D5   │  ...
                  
    Echantillonnage sur front MONTANT (↑)
                    

5.4 Comparaison SPI vs I2C

CritereSPII2C
Nombre de fils4 + (N-1) pour N esclaves2 (bus partage)
Vitesse max~50 Mbps3.4 Mbps (High Speed)
ModeFull-duplexHalf-duplex
AdressagePar ligne SS dedieePar adresse 7/10 bits
Accuse receptionNon (aucun ACK)Oui (ACK/NACK)
Nombre max esclavesLimite par pins SS128 (7 bits)
ComplexiteSimplePlus complexe
Usage typiqueFlash, SD, ecrans, ADC rapidesCapteurs, EEPROM, RTC
💾

Chapitre 6 : SPI - Pratique

Programmation SPI sur Arduino et STM32

🎯 Objectifs du chapitre

  • Configurer le SPI sur Arduino et STM32
  • Interfacer une memoire Flash SPI
  • Communiquer avec un ecran SPI
  • Lire une carte SD en SPI

6.1 SPI sur Arduino

// SPI sur Arduino
#include <SPI.h>

#define CS_PIN 10  // Chip Select

void setup() {
    pinMode(CS_PIN, OUTPUT);
    digitalWrite(CS_PIN, HIGH);  // Desactiver esclave
    
    SPI.begin();  // Initialiser SPI
    
    // Configuration (optionnel, valeurs par defaut = Mode 0)
    SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
    // 1 MHz, MSB first, Mode 0
}

// Transfert d'un octet (full-duplex)
uint8_t spiTransfer(uint8_t data) {
    digitalWrite(CS_PIN, LOW);    // Activer esclave
    uint8_t received = SPI.transfer(data);
    digitalWrite(CS_PIN, HIGH);   // Desactiver esclave
    return received;
}

// Lire un registre
uint8_t readRegister(uint8_t reg) {
    digitalWrite(CS_PIN, LOW);
    SPI.transfer(reg | 0x80);    // Bit 7 = Read
    uint8_t value = SPI.transfer(0x00);  // Dummy byte
    digitalWrite(CS_PIN, HIGH);
    return value;
}

// Ecrire un registre
void writeRegister(uint8_t reg, uint8_t value) {
    digitalWrite(CS_PIN, LOW);
    SPI.transfer(reg & 0x7F);    // Bit 7 = 0 = Write
    SPI.transfer(value);
    digitalWrite(CS_PIN, HIGH);
}

6.2 SPI sur STM32 (HAL)

// SPI sur STM32 avec HAL

SPI_HandleTypeDef hspi1;

// Chip Select (GPIO)
#define CS_GPIO_Port  GPIOA
#define CS_Pin        GPIO_PIN_4

#define CS_LOW()   HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET)
#define CS_HIGH()  HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET)

// Transfert full-duplex
uint8_t SPI_Transfer(uint8_t data) {
    uint8_t received;
    HAL_SPI_TransmitReceive(&hspi1, &data, &received, 1, 100);
    return received;
}

// Lire un registre
uint8_t SPI_ReadReg(uint8_t reg) {
    uint8_t txData[2] = {reg | 0x80, 0x00};
    uint8_t rxData[2];
    
    CS_LOW();
    HAL_SPI_TransmitReceive(&hspi1, txData, rxData, 2, 100);
    CS_HIGH();
    
    return rxData[1];
}

// Ecrire un registre
void SPI_WriteReg(uint8_t reg, uint8_t value) {
    uint8_t txData[2] = {reg & 0x7F, value};
    
    CS_LOW();
    HAL_SPI_Transmit(&hspi1, txData, 2, 100);
    CS_HIGH();
}