Почему Arduino зависает на Wire.endTransmission()?
Обновлено Редакция GAW
Обмен по I²C в ядре Arduino для AVR идёт через автомат TWI микроконтроллера, а библиотека ждёт его в циклах while. До версии ядра с таймаутами эти циклы были бесконечными, и в 1.8.8 таймаут всё ещё выключен по умолчанию: twi_timeout_us = 0. Если шина не освобождается, скетч висит внутри endTransmission() или requestFrom(), и даже сторожевой таймер не всегда спасает: он перезагрузит плату, но после старта всё повторится.
Почему шина не освобождается
| Причина | Что видно на линиях |
|---|---|
| Только внутренние подтяжки (их включает twi_init) или слишком высокоомные внешние для ёмкости шины | линии в единице, но фронты медленные, на длинной шине биты искажаются |
| Ведомый держит SDA после сброса Arduino посреди чтения | SDA постоянно в нуле, SCL в единице |
| Помеха на длинных проводах | автомат TWI считает, что шину занял другой ведущий |
| Ведомый растягивает такт дольше обычного | SCL в нуле десятки миллисекунд |
Включить таймаут
#include <Wire.h>
void setup() {
Wire.begin();
Wire.setWireTimeout(25000, true); // 25 ms, reset TWI on timeout
}
void loop() {
Wire.beginTransmission(0x76);
Wire.write(0xD0);
uint8_t err = Wire.endTransmission();
if (err == 5 || Wire.getWireTimeoutFlag()) {
Wire.clearWireTimeoutFlag(); // bus stuck: recover, then retry later
}
}
Коды endTransmission() по исходнику twi.c: 0 — успех, 1 — данные длиннее 32-байтного буфера, 2 — нет ответа на адрес, 3 — нет ответа на данные, 4 — прочая ошибка шины, 5 — таймаут. Что значит код 2 и как устроены регистры TWI, разобрано в уроке «Wire и SPI против регистров».
Таймаут не лечит причину, а только не даёт скетчу зависнуть. Подтяжки подбирают по ёмкости шины в калькуляторе подтяжек I²C, а если SDA держит ведомый, нужна процедура освобождения шины из ответа «I²C зависает после сброса микроконтроллера». Почему ведомый вправе держать SCL, объясняет термин «Растяжка такта».
Похожие вопросы
Какой таймаут выбрать?
Комментарий в Wire.cpp называет типичным 25 мс — наибольшая растяжка такта по SMBus — и замечает, что обычно годятся и меньшие значения. Если на шине есть ведомый, который подолгу растягивает такт, таймаут должен быть больше его наибольшей паузы, иначе Wire оборвёт исправную передачу.
Таймаут сработал, а следующие обмены тоже не проходят. Что делать?
Значит, линию держит ведомый. Сброс модуля TWI в Arduino это не исправит: нужно освободить шину, например девятью тактами SCL, как описано в спецификации I²C, или снять питание с датчика. Порядок действий — в ответе о зависании I²C после сброса микроконтроллера.
Есть ли таймаут у requestFrom()?
Тот же самый: setWireTimeout() действует на все ожидания в twi.c. При таймауте requestFrom() вернёт 0 прочитанных байт, а флаг getWireTimeoutFlag() покажет, что причина — таймаут, а не NACK.
Источники
- Arduino. ArduinoCore-avr 1.8.8: libraries/Wire/src/Wire.h, Wire.cpp, utility/twi.c — twi.c: twi_init() включает внутренние подтяжки SDA и SCL (digitalWrite(SDA, 1), digitalWrite(SCL, 1)), twi_disable() выключает; twi_timeout_us = 0 — проверка таймаута выключена (прежнее поведение), в комментарии — в будущем по умолчанию может стать 25 000 мкс со сбросом; циклы ожидания прерываются, только если twi_timeout_us > 0 и micros() − start больше таймаута; twi_writeTo возвращает 0 — успех, 1 — данные длиннее буфера, 2 — NACK на адрес, 3 — NACK на данные, 4 — другая ошибка (потеря арбитража, ошибка шины), 5 — таймаут; Wire.h: setWireTimeout(timeout = 25000, reset_with_timeout = false), BUFFER_LENGTH 32; Wire.cpp: при таймауте выставляется флаг getWireTimeoutFlag(), сбрасываемый clearWireTimeoutFlag(); таймаут может сработать и при растяжке такта, типичное значение 25 мс — наибольшая растяжка по SMBus
- NXP. UM10204 I2C-bus specification and user manual (Rev. 7.0, 1 October 2021) — разд. 3.1.16 Bus clear: если SDA залипла в низком уровне, ведущий выдаёт девять тактов SCL, и устройство должно отпустить линию, иначе — аппаратный сброс или выключение питания; если залипла SCL — аппаратный сброс устройств, а без него — выключение питания, чтобы сработал внутренний сброс по включению
Нашли ошибку?
Смотрите также
- Wire, SPI и регистрыTWBR, буфер 32 байта, коды ошибок, SPCR и SPSR, мастер на регистрах
- Почему шина I2C зависает после перезагрузки микроконтроллера?Ведомый не знает о сбросе ведущего и держит SDA посреди байта
- Подтягивающие резисторы I²CДиапазон Rp по напряжению, скорости и ёмкости шины
- I²CSDA и SCL, START/STOP, ACK, адреса, скорости, расчёт подтяжек
- Растягивание такта I²CВедомое держит SCL в нуле: зачем, ограничения SMBus, совместимость