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:
Библиотека 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 МГц | SPI2X | SPR1:0 |
|---|---|---|---|
| 10 000 000 | 8 МГц (fosc/2) | 1 | 00 |
| 4 000 000 (по умолчанию) | 4 МГц (fosc/4) | 0 | 00 |
| 1 000 000 | 1 МГц (fosc/16) | 0 | 01 |
| 100 000 | 125 кГц (fosc/128) | 0 | 11 |
Последняя строка — исключение из правила: медленнее 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 В.
Задание
- Какие значения SPCR и SPSR получит
SPISettings(2000000, MSBFIRST, SPI_MODE0)и какая будет частота SCK? - Какое значение TWBR запишет
Wire.setClock(50000)и какая получится частота? - Скетч передаёт по I2C 40 байт одной транзакцией:
beginTransmission, 40 вызововwrite,endTransmission. Что уйдёт на шину и как это исправить?
Ответ и решение
-
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 МГц ровно.
-
TWBR = (16 000 000 / 50 000 − 16) / 2 = (320 − 16) / 2 = 152; 16 000 000 / (16 + 2 · 152) = 50 кГц (расчёт наш).
-
В буфер помещаются 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 требует оставлять его выходом, даже если чип выбора подключён к другому выводу.
Источники
- 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
- 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
- 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)
Нашли ошибку?
Смотрите также
- I²CSDA и SCL, START/STOP, ACK, адреса, скорости, расчёт подтяжек
- SPISCLK, MOSI, MISO, CS, режимы 0–3, daisy chain, частота, SPI в AVR
- Подтягивающие резисторы I²CДиапазон Rp по напряжению, скорости и ёмкости шины
- TWIМодуль I²C в AVR: регистры, формула частоты SCL, фильтр и подтяжки
- Режимы SPI (CPOL, CPHA)Режимы 0–3: полярность и фаза такта SPI
- Почему Arduino зависает на Wire.endTransmission()?Таймаут Wire выключен по умолчанию; setWireTimeout(25000, true) и код возврата 5