Embedded HandbookApps
All sections › III. Peripherals
STM32F407 · level advanced · 11 min read
Open in the interactive handbook

17а. The microcontroller as a slave device: I²C and SPI

Usually the microcontroller is the master: it polls sensors. But it often becomes a "sensor" for someone else itself: an expansion board, a module in a rack, a coprocessor next to a Linux board. Then the clock is set by someone else's master, and the main question changes — not "how to send", but "will I manage to answer in time".

This section covers an I²C slave device with a register map (like real sensors) and the calculation of the response time for SPI. The register map logic (regmap.c) is one and the same in the firmware and in the bus model on a computer; the interrupt handler for the F407 is built with the CMSIS headers. The block rules follow the F4 reference manual. The files are in _verify/slave17a/, checked by _verify/slave17a_check.py.

Terms used in the documentation

Term Meaning
slave transmitter / slave receiver a slave that sends / that receives
clock stretching the slave holding the clock low
NOSTRETCH (clock stretching disable) forbidding clock stretching
ADDR, STOPF, AF (slave events) address matched, STOP, the master answered NACK
register map, auto-increment pointer a set of addressable registers, a pointer that increments by itself
overrun / underrun (OVR, NOSTRETCH = 1) a lost / a repeated byte without stretching
dummy byte an empty byte that gives the slave time

17а.1 What a slave looks like from outside: the register map

It is most convenient for the master when the slave looks like an ordinary sensor: it has registers, the first byte after the address is the register number, then the pointer increments by itself. Our map of 16 bytes:

Address Register Access
0x00 WHO_AM_I = 0x5A read
0x01 STATUS: the number of writes received read
0x02 CTRL read and write
0x04…0x05 SETPOINT, 16 bits, low byte first read and write
0x08…0x0B COUNTER, 32 bits read

The master's transactions are the same as for any sensor:

Two rules without which the slave returns "torn" values:

A check of the logic in the model: the write 0x0F, 0xAA, 0xBB, 0xCC puts 0xAA into 0x0F, then the pointer wraps around to 0x00 and 0x01 — both "read only", two bytes are discarded (WHO_AM_I stayed 0x5A); before STOP register 0x0F is still old (0x00), after it 0xAA.

A map of sixteen slave registers at addresses from 0x00 to 0x0F: WHO_AM_I equal to 0x5A and STATUS read only, CTRL read and write, SETPOINT of two bytes low first, COUNTER of four bytes read only, the rest free; below, the write and read transactions and two rules — write into the shadow until STOP, read from a snapshot, otherwise the counter reads as 0x0001FFFF instead of 0x0000FFFF


17а.2 Five events of the I²C block

The I²C block catches its address by itself, sets ACK by itself and holds the line by itself if the program has not managed in time. What is left to the program is events (flags in SR1/SR2):

Event When How to clear
ADDR the address matched; TRA in SR2 is the direction read SR1, then SR2
RXNE a byte has been received read DR
TXE the next byte to transmit is needed write DR
AF the master answered NACK — it has finished reading write 0 into the AF bit
STOPF the master finished the transaction read SR1, then write CR1

The handler for the F407 translates the flags into calls of the logic:

void I2C1_EV_IRQHandler(void)
{
    uint32_t sr1 = I2C1->SR1;
    if (sr1 & I2C_SR1_ADDR) {
        uint32_t sr2 = I2C1->SR2;                                          /* SR1, затем SR2 — ADDR сброшен */
        int read = (sr2 & I2C_SR2_TRA) != 0;
        rm_on_addr(&rm, read);
        if (read)
            I2C1->DR = rm_on_tx(&rm);                                      /* первый байт — сразу, пока SCL держится */
        return;
    }
    if (sr1 & I2C_SR1_RXNE)
        rm_on_rx(&rm, (uint8_t)I2C1->DR);
    if ((sr1 & I2C_SR1_TXE) && !(sr1 & I2C_SR1_AF))
        I2C1->DR = rm_on_tx(&rm);
    if (sr1 & I2C_SR1_STOPF) {
        I2C1->CR1 |= I2C_CR1_ACK;                                          /* SR1 уже прочитан; запись CR1 сбрасывает STOPF */
        rm_on_stop(&rm);
    }
}

The slave initialisation and the error handler — from the same file:

void i2c_slave_init(void)
{
    rm_init(&rm);
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
    GPIOB->MODER = (GPIOB->MODER & ~(0xFu << 12)) | (0xAu << 12);        /* PB6, PB7 — альтернативная функция */
    GPIOB->OTYPER |= (1u << 6) | (1u << 7);                                /* открытый сток */
    GPIOB->AFR[0] = (GPIOB->AFR[0] & ~(0xFFu << 24)) | (0x44u << 24);      /* AF4 = I2C1 */
    I2C1->CR2 = 42;                                                        /* частота APB1, МГц */
    I2C1->OAR1 = 0x42u << 1;                                               /* 7-битный адрес 0x42 */
    I2C1->CR2 |= I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN | I2C_CR2_ITERREN;
    I2C1->CR1 = I2C_CR1_PE;
    I2C1->CR1 |= I2C_CR1_ACK;                                              /* ACK ставится только при PE = 1 */
    NVIC_EnableIRQ(I2C1_EV_IRQn);
    NVIC_EnableIRQ(I2C1_ER_IRQn);
}

void I2C1_ER_IRQHandler(void)
{
    if (I2C1->SR1 & I2C_SR1_AF) {                                          /* ведущий ответил NACK — чтение закончено */
        I2C1->SR1 = ~I2C_SR1_AF;
        rm_on_nack(&rm);
    }
}

Built with arm-none-eabi-gcc -Os: the initialisation and both handlers are 268 bytes, the register map logic 260.

An extra byte when reading. The block asks for the next byte (TXE) when the previous one has only begun to go out — the slave does not know that the master will stop on it. So there is always one byte more in DR than has been read, and the pointer runs ahead. On AF it is moved back by one — otherwise the next "continued read" will skip a register.


17а.3 Clock stretching: the bus waits for the program

While ADDR is not cleared, the block holds SCL low — the master waits. Likewise, if by the end of the next byte the program has not read DR (or has not written it when transmitting), the block sets BTF and holds SCL. So the margin is one byte with ACK: 9 bits, 90 µs at 100 kHz and 22.5 µs at 400 kHz.

The model: the master writes the setpoint 2500 (address, pointer 0x04, two bytes) and reads the counter 0x12345678 (pointer 0x08, repeated START, 4 bytes). The processor responds to each event after L µs:

Bus L Write of which held by the slave Read held
100 kHz 2 µs 382 µs 2 664 µs 4
100 kHz 100 µs 500 µs 120 890 µs 230
400 kHz 2 µs 97 µs 2 169 µs 4
400 kHz 30 µs 140 µs 45 252.5 µs 87.5
400 kHz 100 µs 350 µs 255 672.5 µs 507.5

The data is correct in all rows: the setpoint 2500, the counter 0x12345678. The price of a slow slave is bus time: at 400 kHz with L = 100 µs a write takes 350 µs instead of 95. As long as L is less than 22.5 µs, only the ADDR event holds the bus (L per address).


17а.4 NOSTRETCH: the bus does not wait — the data gets corrupted

Not every master supports a stretched clock. Then the slave is given NOSTRETCH = 1, and being late turns into an OVR error:

The same model without stretching:

Bus L Setpoint after the write Counter read as
100 kHz 2…30 µs 2500 — correct 0x345678FF
100 kHz 100 µs 777 (0x0309) 0x7878FFFF
400 kHz 2…10 µs 2500 — correct 0x345678FF
400 kHz 30 µs 777 (0x0309) 0x7878FFFF
400 kHz 100 µs 1000 — the old one 0xFFFFFFFF

Writing the setpoint at 400 kHz with a 30 µs response: with stretching the bus waits for the handler, without stretching the byte 0xC4 is lost

How 777 came about: the byte 0xC4 was lost, 0x09 was written at pointer 0x04 — into the low byte; the high one stayed 0x03. 0x0309 = 777 instead of 2500 — without a single error signal to the master.

The counter gets corrupted even with L = 2 µs: the first byte of a read must go out right after the address ACK, but the ADDR handler has not yet managed to write DR — whatever was there (0xFF) goes out, and all the bytes shift. Without stretching the first byte is put into DR in advance, before the address arrives, and the rest via DMA.


17а.5 An SPI slave: the answer is needed before the question

SPI has no stretching at all: the clock runs as long as the master provides it, and the reply bits are shifted out simultaneously with the command bits. The manual requires the slave's DR to be ready before the first clock edge; the clock frequency for a slave is no higher than fPCLK/2.

If the master sends a command and expects the answer immediately, the slave has only half a period of SCK between the last bit of the command and the first bit of the answer. With one empty byte — eight more periods. In clock cycles of a 168 MHz core:

SCK Byte Without an empty byte With one empty byte
1 MHz 8000 ns 84 cycles 1428 cycles
4 MHz 2000 ns 21 cycles 357 cycles
10 MHz 800 ns 8.4 cycles 142.8 cycles
21 MHz 381 ns 4 cycles 68 cycles
42 MHz 190 ns 2 cycles 34 cycles

Without an empty byte what remains is from 84 cycles at 1 MHz to 2 at 42 MHz — and in this time you must enter the interrupt, read the command, choose the answer and write DR. Already from a few megahertz this is impossible. Hence how SPI slaves are designed:

SPI on the F4 has no "failed to write in time" flag (UDR exists only in I2S mode): a slave that is late with DR will simply send the wrong bits, and only the master can find out about it — by a checksum.


17а.6 Pitfalls

The setpoint got a strange value without any errors. NOSTRETCH = 1 and a slow handler: a byte was lost, the others were written to the wrong place. Allow stretching or receive via DMA.

The first byte read is 0xFF or "the previous one". Without stretching DR was not filled before the first byte. Put it there in advance.

Every second read skips a register. The pointer was not moved back on AF: the extra byte requested by TXE "ate" a register.

A multi-byte value is sometimes "torn". There is no snapshot on reading or shadow on writing.

The slave does not answer its address. ACK in CR1 is cleared by hardware when PE = 0: written before the block is enabled, it is lost. Set it after PE = 1.

ADDR does not clear. Only SR1 was read — SR2 is needed too. And the other way round: an extra read of SR2 without checking ADDR clears an ADDR that arrived between the two reads.

An SPI slave returns garbage at a high frequency. An answer in the same byte as the command is physically impossible — an empty byte or DMA with a ready answer is needed.


17а.7 Summary

Full sources

i2c_slave_f4.c — the interrupt handlers of an I²C slave on the STM32F4.

/* Раздел 17а: ведомый I²C на STM32F4 (I2C1, PB6 = SCL, PB7 = SDA, адрес 0x42) — обработчики прерываний.
 * Порядок сброса флагов — по RM0090: ADDR — чтение SR1, затем SR2; STOPF — чтение SR1, затем запись CR1;
 * AF — запись 0 в бит AF. Сама логика — в regmap.c, одинаковая для прошивки и модели на компьютере. */
#include "stm32f4xx.h"
#include "regmap.h"

static regmap_t rm;

void i2c_slave_init(void)
{
    rm_init(&rm);
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
    GPIOB->MODER = (GPIOB->MODER & ~(0xFu << 12)) | (0xAu << 12);        /* PB6, PB7 — альтернативная функция */
    GPIOB->OTYPER |= (1u << 6) | (1u << 7);                                /* открытый сток */
    GPIOB->AFR[0] = (GPIOB->AFR[0] & ~(0xFFu << 24)) | (0x44u << 24);      /* AF4 = I2C1 */
    I2C1->CR2 = 42;                                                        /* частота APB1, МГц */
    I2C1->OAR1 = 0x42u << 1;                                               /* 7-битный адрес 0x42 */
    I2C1->CR2 |= I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN | I2C_CR2_ITERREN;
    I2C1->CR1 = I2C_CR1_PE;
    I2C1->CR1 |= I2C_CR1_ACK;                                              /* ACK ставится только при PE = 1 */
    NVIC_EnableIRQ(I2C1_EV_IRQn);
    NVIC_EnableIRQ(I2C1_ER_IRQn);
}

void I2C1_EV_IRQHandler(void)
{
    uint32_t sr1 = I2C1->SR1;
    if (sr1 & I2C_SR1_ADDR) {
        uint32_t sr2 = I2C1->SR2;                                          /* SR1, затем SR2 — ADDR сброшен */
        int read = (sr2 & I2C_SR2_TRA) != 0;
        rm_on_addr(&rm, read);
        if (read)
            I2C1->DR = rm_on_tx(&rm);                                      /* первый байт — сразу, пока SCL держится */
        return;
    }
    if (sr1 & I2C_SR1_RXNE)
        rm_on_rx(&rm, (uint8_t)I2C1->DR);
    if ((sr1 & I2C_SR1_TXE) && !(sr1 & I2C_SR1_AF))
        I2C1->DR = rm_on_tx(&rm);
    if (sr1 & I2C_SR1_STOPF) {
        I2C1->CR1 |= I2C_CR1_ACK;                                          /* SR1 уже прочитан; запись CR1 сбрасывает STOPF */
        rm_on_stop(&rm);
    }
}

void I2C1_ER_IRQHandler(void)
{
    if (I2C1->SR1 & I2C_SR1_AF) {                                          /* ведущий ответил NACK — чтение закончено */
        I2C1->SR1 = ~I2C_SR1_AF;
        rm_on_nack(&rm);
    }
}

uint16_t app_setpoint(void)
{
    return rm_setpoint(&rm);
}

Next: section 18 "SPI" — an interface that is much simpler and works an order of magnitude faster.

Open in the interactive handbook