🔗 SPI & I2C (TWI) — ATmega328P

Bus de communication série en bare metal : registres SPI (SPCR, SPDR) et TWI (TWBR, TWCR, TWDR)

⚡ SPI sur ATmega328P

L'ATmega328P intègre un module SPI matériel complet. Les broches sont fixées par le matériel :

SignalAVR PortArduino Pin
SCK (horloge)PB5D13
MOSI (données sortantes)PB3D11
MISO (données entrantes)PB4D12
SS (chip select)PB2D10

Registres SPI

💻 Code : SPI Master bare metal

#include <avr/io.h>

#define CS_PIN   PB2
#define CS_LOW   PORTB &= ~(1 << CS_PIN)
#define CS_HIGH  PORTB |= (1 << CS_PIN)

void spi_init(void) {
    // MOSI, SCK, SS en sortie
    DDRB |= (1 << PB3) | (1 << PB5) | (1 << CS_PIN);
    // MISO en entrée (déjà par défaut)
    CS_HIGH;  // CS inactif

    // SPI Enable, Master, Mode 0, Fosc/16
    SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0);
}

uint8_t spi_transfer(uint8_t data) {
    SPDR = data;                      // Lancer le transfert
    while (!(SPSR & (1 << SPIF)));   // Attendre fin
    return SPDR;                      // Lire donnée reçue
}

// Utilisation :
// CS_LOW;
// uint8_t reponse = spi_transfer(0x42);
// CS_HIGH;

Vitesses SPI disponibles (Fosc = 16 MHz)

SPR1:SPR0SPI2XDiviseurFréq. SCK
001/28 MHz
000/44 MHz
010/161 MHz
100/64250 kHz
110/128125 kHz

🔗 I2C (TWI) sur ATmega328P

Sur les AVR, le module I2C s'appelle TWI (Two-Wire Interface). Il est fonctionnellement identique à l'I2C. Les broches sont :

N'oubliez pas les résistances pull-up de 4.7 kΩ sur SDA et SCL !

Registres TWI

RegistreRôle
TWBRBit Rate Register — définit la vitesse SCL
TWCRControl Register — Start, Stop, ACK, Enable, Interrupt
TWSRStatus Register — code d'état de la dernière opération
TWDRData Register — données à envoyer ou reçues

Calcul de la vitesse I2C

SCL_freq = F_CPU / (16 + 2 × TWBR × prescaler)
Pour 100 kHz à 16 MHz : TWBR = 72 (prescaler=1)

💻 Code : I2C (TWI) Master bare metal

#include <avr/io.h>

/* ---- Initialisation TWI à 100 kHz ---- */
void twi_init(void) {
    TWBR = 72;     // 100 kHz avec F_CPU=16MHz, prescaler=1
    TWSR = 0x00;   // Prescaler = 1
    TWCR = (1 << TWEN);  // Activer TWI
}

/* ---- Start ---- */
void twi_start(void) {
    TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);
    while (!(TWCR & (1<<TWINT)));
}

/* ---- Stop ---- */
void twi_stop(void) {
    TWCR = (1<<TWINT) | (1<<TWSTO) | (1<<TWEN);
}

/* ---- Envoyer un octet ---- */
void twi_write(uint8_t data) {
    TWDR = data;
    TWCR = (1<<TWINT) | (1<<TWEN);
    while (!(TWCR & (1<<TWINT)));
}

/* ---- Recevoir avec ACK ---- */
uint8_t twi_read_ack(void) {
    TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);
    while (!(TWCR & (1<<TWINT)));
    return TWDR;
}

/* ---- Recevoir avec NACK (dernier octet) ---- */
uint8_t twi_read_nack(void) {
    TWCR = (1<<TWINT) | (1<<TWEN);
    while (!(TWCR & (1<<TWINT)));
    return TWDR;
}

/* ---- Exemple : lire 1 octet d'un capteur ---- */
uint8_t i2c_read_register(uint8_t addr, uint8_t reg) {
    twi_start();
    twi_write((addr << 1) | 0);   // Write
    twi_write(reg);
    twi_start();                     // Repeated Start
    twi_write((addr << 1) | 1);   // Read
    uint8_t data = twi_read_nack();
    twi_stop();
    return data;
}

📌 Comparaison Arduino Wire/SPI vs bare metal

ArduinoBare metal AVR
Wire.begin()twi_init()
Wire.beginTransmission(addr)twi_start(); twi_write(addr<<1);
Wire.write(data)twi_write(data)
Wire.endTransmission()twi_stop()
SPI.begin()spi_init()
SPI.transfer(data)spi_transfer(data)

Le code bare metal est très proche de l'API Arduino mais vous donne le contrôle complet sur les vitesses, les modes, les codes d'état TWI et la gestion d'erreurs — ce que les bibliothèques Arduino masquent.