Перейти к содержанию

Wire и SPI против регистров TWI и SPI

Библиотеки Wire и SPI устроены совсем по-разному. SPI.transfer — это почти голая запись в SPDR, а Wire — конечный автомат в прерывании, буферы на 32 байта и блокирующее ожидание без тайм-аута. Разберём обе по исходнику и напишем мастер I2C на регистрах.

Обновлено Редакция GAW

От Arduino к регистрамурок 8 из 840 мин

Цель урока: Понимать, какие регистры TWI и SPI настраивают Wire.begin, Wire.setClock, SPI.begin и SPISettings, знать ограничения Wire и писать передачу I2C и SPI в режиме мастера на регистрах.

Два последних интерфейса трека — I2C, который в даташите Microchip называется TWI, и SPI. Их библиотеки Wire и SPI лежат в ArduinoCore-avr 1.8.8 в папке libraries. Сами протоколы разобраны в статьях «I2C» и «SPI»; здесь — что делают библиотеки с регистрами ATmega328P.

Wire.begin и частота SCL

Wire.begin() вызывает twi_init() из utility/twi.c (строки 80–103):

// ArduinoCore-avr 1.8.8, libraries/Wire/src/utility/twi.c, lines 80-103 (abridged)
void twi_init(void)
{
  // activate internal pullups for twi.
  digitalWrite(SDA, 1);
  digitalWrite(SCL, 1);

  // initialize twi prescaler and bit rate
  cbi(TWSR, TWPS0);
  cbi(TWSR, TWPS1);
  TWBR = ((F_CPU / TWI_FREQ) - 16) / 2;

  // enable twi module, acks, and twi interrupt
  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);
}

Частоту SCL задаёт формула раздела 22.5.2:

fSCL=fCPU16+2⋅TWBR⋅4TWPSf_\text{SCL} = \frac{f_\text{CPU}}{16 + 2 \cdot \text{TWBR} \cdot 4^\text{TWPS}}

Библиотека Wire ставит делитель 1 (TWPS = 0) и для TWI_FREQ = 100 000 получает TWBR = (160 − 16) / 2 = 72: 16 000 000 / (16 + 144) = 100 кГц ровно. Wire.setClock(400000) пересчитывает по той же формуле: TWBR = (40 − 16) / 2 = 12, и 16 000 000 / (16 + 24) = 400 кГц (расчёт наш). 400 кГц — предел даташита для fSCL (табл. 29-14).

Подтяжки

digitalWrite(SDA, 1) при выводе-входе включает подтягивающий резистор — это то, что мы видели в уроке 2, а даташит прямо разрешает так подтягивать SDA и SCL (раздел 22.5.1). Но сопротивление внутренней подтяжки — 20–50 кОм (табл. 30-1), а по табл. 29-14 для шины до 100 кГц оно не должно превышать 1000 нс / Cb, где Cb — ёмкость линии. При Cb = 100 пФ это 10 кОм, при 400 пФ — 2,5 кОм (расчёт наш). Внутренние 20 кОм укладываются в требование только при ёмкости до 50 пФ. Нижняя граница — (VCC − 0,4 В) / 3 мА ≈ 1,5 кОм при 5 В. Внешние резисторы под длину проводов и число устройств подберёт калькулятор подтяжек I2C, а что бывает без них — ответ «Будет ли работать I2C без подтягивающих резисторов».

Что Wire делает при передаче

Wire.beginTransmission(0x3C);   // only stores the address
Wire.write(0x00);               // into a 32-byte buffer
Wire.write(0xAF);
uint8_t err = Wire.endTransmission();   // the bus transfer happens here

beginTransmission и write ничего не передают — они копируют байты в буфер на 32 байта (BUFFER_LENGTH в Wire.h). Если буфер полон, write() возвращает 0 и ставит флаг ошибки записи (строки 240–247 Wire.cpp): 33-й байт молча не попадёт в передачу. Вся работа с шиной — в endTransmission(), которая вызывает twi_writeTo(). Та копирует буфер ещё раз — в свой буфер на 32 байта, — запускает START и ждёт в цикле, пока обработчик ISR(TWI_vect) (строка 503 twi.c) не пройдёт весь конечный автомат передачи.

Коды возврата endTransmission — из комментария к twi_writeTo (строки 237–251):

КодЗначение
0успех
1данные не помещаются в буфер
2после адреса нет ACK — по адресу никто не ответил
3после байта данных нет ACK
4другая ошибка шины: потерян арбитраж, ошибка шины
5тайм-аут

Код 5 появляется, только если тайм-аут включить: в версии 1.8.8 twi_timeout_us = 0 (строка 54), и проверка выключена. Если ведомый завис и держит SDA в нуле, endTransmission будет ждать вечно. Защита — Wire.setWireTimeout(25000, true): 25 мс и сброс модуля TWI при срабатывании, такое значение предлагает комментарий в Wire.cpp. Как вывести шину из зависания, рассказывает термин «Сброс зависшей шины I²C».

Мастер I2C на регистрах

Без прерываний мастер-передатчик TWI — это четыре шага по табл. 22-2 даташита: START, адрес с битом записи, байты данных, STOP. После каждого шага модуль ставит флаг TWINT и кладёт код состояния в TWSR; два младших бита TWSR — делитель, их маскируют (раздел 22.9.3).

// Polling TWI master. Do not use together with the Wire library:
// Wire brings its own ISR(TWI_vect).

static uint8_t twiWait(void)
{
  while (!(TWCR & _BV(TWINT))) {}   // no timeout here: add one for real use
  return TWSR & 0xF8;               // status without the prescaler bits
}

static bool twiSend(uint8_t data, uint8_t expected)
{
  TWDR = data;
  TWCR = _BV(TWINT) | _BV(TWEN);    // writing 1 to TWINT starts the next step
  return twiWait() == expected;
}

static void twiStop(void)
{
  TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN);
  while (TWCR & _BV(TWSTO)) {}      // TWSTO clears itself after the STOP
}

bool twiWriteRegister(uint8_t address7, uint8_t reg, uint8_t value)
{
  TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);       // START
  bool ok = twiWait() == 0x08                        // START sent
         && twiSend(address7 << 1, 0x18)             // SLA+W, ACK (0x20: no ACK)
         && twiSend(reg, 0x28)                       // data, ACK (0x30: no ACK)
         && twiSend(value, 0x28);
  twiStop();
  return ok;
}

void setup() {
  TWSR = 0;                 // prescaler 1
  TWBR = 72;                // 16 MHz / (16 + 2 * 72) = 100 kHz
  // SDA (A4) and SCL (A5) need pull-up resistors to VCC: external ones
}

void loop() {}

Коды 0x08, 0x18, 0x20, 0x28 и 0x30 — из табл. 22-2. Флаг TWINT сбрасывают записью единицы, и пока он стоит, модуль держит SCL в нуле (разделы 22.5.5 и 22.9.2) — поэтому каждый шаг начинается записью TWCR с битом TWINT. Такой код короче библиотеки и не копирует данные дважды, но ждёт без тайм-аута: перед применением в устройстве добавьте счётчик или проверку по micros().

SPI.begin и SPISettings

// ArduinoCore-avr 1.8.8, libraries/SPI/src/SPI.cpp, lines 26-64 (abridged)
if (!(*reg & bit)) {
  digitalWrite(SS, HIGH);   // SS still an input: pull-up, the slave is not selected
}
pinMode(SS, OUTPUT);
SPCR |= _BV(MSTR);
SPCR |= _BV(SPE);
pinMode(SCK, OUTPUT);       // after SPE, so no stray bit is clocked
pinMode(MOSI, OUTPUT);

SPI.begin() делает вывод 10 (SS, PB2) выходом с высоким уровнем, включает SPI в режиме мастера и только потом переводит SCK и MOSI в выходы. MISO переключать не нужно: в режиме мастера он вход автоматически (табл. 19-1). Вывод SS обязан оставаться выходом: если в режиме мастера он станет входом и на нём появится ноль, SPI сбросит бит MSTR и станет ведомым (раздел 19.3.2).

SPISettings(clock, bitOrder, dataMode) подбирает делитель из табл. 19-5 — самую высокую частоту SCK, не превышающую заданную, — и упаковывает её в значения SPCR и SPSR (строки 72–152 SPI.h). beginTransaction() просто записывает эти два байта, а SPI.transfer() — это несколько строк:

// ArduinoCore-avr 1.8.8, libraries/SPI/src/SPI.h, lines 208-219 (abridged)
inline static uint8_t transfer(uint8_t data) {
  SPDR = data;
  asm volatile("nop");
  while (!(SPSR & _BV(SPIF))) ; // wait
  return SPDR;
}

Функция объявлена inline, и регистровый код делает ровно то же самое. Здесь библиотека почти ничего не стоит — в отличие от Wire с её буферами и конечным автоматом.

Запрошено в SPISettingsПолучится при 16 МГцSPI2XSPR1:0
10 000 0008 МГц (fosc/2)100
4 000 000 (по умолчанию)4 МГц (fosc/4)000
1 000 0001 МГц (fosc/16)001
100 000125 кГц (fosc/128)011

Последняя строка — исключение из правила: медленнее fosc/128 делителя нет, и тогда SPISettings берёт самый медленный, как и написано в комментарии исходника.

При fosc/2 один байт — это 8 бит по 2 такта, 16 тактов, 1 мкс (расчёт наш). Режим SPI_MODE0…3 — это биты CPOL и CPHA в SPCR, о них термин «Режимы SPI».

SPI на регистрах

void spiInit(void)
{
  PORTB |= _BV(PORTB2);                     // SS high first: the slave stays deselected
  DDRB |= _BV(DDB2);                        // SS (pin 10) output: keeps the master mode
  SPCR = _BV(SPE) | _BV(MSTR);              // master, mode 0, MSB first, fosc/4
  SPSR = _BV(SPI2X);                        // double speed: fosc/2 = 8 MHz
  DDRB |= _BV(DDB3) | _BV(DDB5);            // MOSI (pin 11) and SCK (pin 13) outputs
}

uint8_t spiTransfer(uint8_t data)
{
  SPDR = data;
  while (!(SPSR & _BV(SPIF))) {}
  return SPDR;                              // reading SPDR after SPIF clears the flag
}

Порядок взят из SPI.begin() (строки 38–45 и далее SPI.cpp): сначала высокий уровень на SS, пока вывод ещё вход, — это подтяжка, — затем SS выходом, так что на линии выбора не бывает даже короткого нуля; потом включение SPI и только затем SCK и MOSI. Флаг SPIF сбрасывается чтением SPSR при установленном SPIF и последующим обращением к SPDR (раздел 19.5.2) — функция делает это сама. Поля кадра и временную диаграмму для любого режима покажет конструктор кадра SPI.

Уровни и питание датчиков

Uno работает на 5 В, и подтяжки Wire тянут SDA и SCL к 5 В. Многие датчики питаются от 3,3 В, и напряжение на их выводах не должно превышать их абсолютного максимума — смотрите даташит датчика. Для таких модулей нужен преобразователь уровней или модуль, на котором он уже стоит. То же для SPI: выход MOSI и SCK Uno даёт почти 5 В.

Задание

  1. Какие значения SPCR и SPSR получит SPISettings(2000000, MSBFIRST, SPI_MODE0) и какая будет частота SCK?
  2. Какое значение TWBR запишет Wire.setClock(50000) и какая получится частота?
  3. Скетч передаёт по I2C 40 байт одной транзакцией: beginTransmission, 40 вызовов write, endTransmission. Что уйдёт на шину и как это исправить?
Ответ и решение
  1. 2 МГц = F_CPU / 8: это fosc/8, в табл. 19-5 — SPI2X = 1, SPR1:0 = 01. SPCR = SPE | MSTR | SPR0 = 0x40 | 0x10 | 0x01 = 0x51, SPSR = 0x01, частота SCK — 2 МГц ровно.

  2. TWBR = (16 000 000 / 50 000 − 16) / 2 = (320 − 16) / 2 = 152; 16 000 000 / (16 + 2 · 152) = 50 кГц (расчёт наш).

  3. В буфер помещаются 32 байта, остальные 8 вызовов write() вернут 0, и на шину уйдут только первые 32 байта — endTransmission() при этом вернёт 0, как будто всё в порядке. Передавайте данные частями до 32 байт (с учётом байта адреса регистра, если датчик его ждёт) или пишите регистровый мастер без буфера, как в уроке.

Что дальше

Это последний урок трека. Следующий шаг — убрать ядро совсем и писать программу с main(): трек «AVR с нуля» показывает, как собрать и прошить её без Arduino IDE. Регистры TWI и SPI с битами — на страницах TWI и SPI атласа ATmega328P, последовательность байтов транзакции I2C нарисует конструктор транзакций I2C. Что такое подтверждение в I2C и почему его может не быть, объясняет термин «ACK и NACK в I²C».

Частые вопросы

Почему Wire.endTransmission возвращает 2?

Код 2 в библиотеке Wire означает, что после адреса ведомого не пришло подтверждение ACK: по этому адресу никто не ответил. Частые причины — неверный адрес (7-битный вместо 8-битного или наоборот), нет подтягивающих резисторов, перепутаны SDA и SCL, у датчика нет питания. Код 3 — подтверждения не было после байта данных, 4 — другая ошибка шины, 5 — сработал тайм-аут, если его включили функцией setWireTimeout.

Хватит ли внутренних подтяжек для I2C?

Wire.begin включает внутренние подтягивающие резисторы 20–50 кОм на SDA и SCL. По табл. 29-14 даташита сопротивление подтяжки для 100 кГц не должно превышать 1000 нс / Cb — 10 кОм при ёмкости шины 100 пФ. Внутренние подтяжки годятся разве что для одного датчика на очень коротких проводах; для надёжной работы ставят внешние резисторы, обычно в единицы килоом, но не меньше (VCC − 0,4 В) / 3 мА ≈ 1,5 кОм при 5 В.

Почему SPI перестаёт работать, если вывод 10 сделать входом?

Вывод 10 Uno — это SS аппаратного SPI. Если в режиме мастера он настроен входом и на нём появится низкий уровень, SPI решит, что его выбрал другой мастер, сбросит бит MSTR и станет ведомым (разд. 19.3.2 даташита). SPI.begin поэтому делает вывод 10 выходом, а комментарий в SPI.cpp требует оставлять его выходом, даже если чип выбора подключён к другому выводу.

Источники

  1. Arduino. ArduinoCore-avr 1.8.8: libraries/Wire (Wire.cpp, Wire.h, utility/twi.c, utility/twi.h) и libraries/SPI (SPI.cpp, SPI.h) — тег 1.8.8 — коммит 86df345 от 21 мая 2026 (GitHub API, tags и commits); twi.h, стр. 29–35: TWI_FREQ = 100 000, TWI_BUFFER_LENGTH = 32; twi.c, стр. 54: twi_timeout_us = 0 — тайм-аут выключен; стр. 80–103: twi_init — digitalWrite(SDA, 1) и digitalWrite(SCL, 1) включают внутренние подтяжки, TWPS1:0 = 0, TWBR = ((F_CPU / TWI_FREQ) − 16) / 2, TWCR = TWEN | TWIE | TWEA; стр. 139–147: twi_setFrequency — TWBR = ((F_CPU / frequency) − 16) / 2; стр. 237–330: twi_writeTo — коды 0–5, ожидание готовности и конца передачи; стр. 503: ISR(TWI_vect) — конечный автомат; Wire.h, стр. 29: BUFFER_LENGTH = 32; Wire.cpp, стр. 217–227: endTransmission возвращает код twi_writeTo; стр. 240–247: write при полном буфере возвращает 0 и ставит ошибку записи; SPI.cpp, стр. 26–64: begin — SS в высокий уровень и выход, MSTR и SPE, затем SCK и MOSI выходы; SPI.h, стр. 72–152: SPISettings выбирает ближайшую частоту не выше заданной и упаковывает SPCR и SPSR, по умолчанию 4 МГц, MSBFIRST, SPI_MODE0; стр. 178–205: beginTransaction пишет SPCR и SPSR; стр. 208–219: transfer — SPDR, nop, ожидание SPIF
  2. Microchip. ATmega48A/PA/88A/PA/168A/PA/328/P Data Sheet (DS40002061B) — разд. 19.3.2: в режиме мастера SS-вход с низким уровнем сбрасывает MSTR и переводит SPI в ведомый; разд. 19.5.1: SPCR — SPIE, SPE, DORD, MSTR, CPOL, CPHA, SPR1:0; разд. 19.5.2 и табл. 19-5: SPSR — SPIF, WCOL, SPI2X, частоты SCK от fosc/2 до fosc/128; табл. 19-1: MISO в режиме мастера — вход; разд. 22.5.1: внутренние подтяжки SDA и SCL включаются битами PORT, ограничение фронтов и фильтр помех короче 50 нс; разд. 22.5.2: SCL = fCPU / (16 + 2 · TWBR · 4^TWPS); разд. 22.5.5: пока установлен TWINT, SCL удерживается в нуле; разд. 22.9.2: TWCR, TWINT сбрасывается записью единицы, TWSTO сбрасывается сам после STOP; разд. 22.9.3: TWSR, при проверке статуса биты делителя маскируют; табл. 22-2: коды мастера-передатчика 0x08, 0x18, 0x20, 0x28, 0x30; табл. 22-7: делители 1, 4, 16, 64; табл. 29-14: Rp от (VCC − 0,4 В) / 3 мА до 1000 нс / Cb при fSCL ≤ 100 кГц и 300 нс / Cb выше, fSCL до 400 кГц; табл. 30-1: подтяжка выводов 20–50 кОм; разд. 14.3.1 и 14.3.2: SS, MOSI, MISO, SCK — PB2–PB5, SDA и SCL — PC4 и PC5
  3. AVR-LibC 2.3.2: avr/io.h, iom328p.h (github.com/avrdudes/avr-libc) — имена регистров и битов TWBR, TWSR, TWCR, TWDR, TWINT, TWSTA, TWSTO, TWEN, SPCR, SPSR, SPDR, SPE, MSTR, SPI2X, SPIF; векторы TWI_vect (24) и SPI_STC_vect (17)

Нашли ошибку?