24.03.2009

REPEATED START в Arduino

На досуге разбирался с библиотекой Wire на предмет возможности работать по I2C в режиме REPEATED START. Вроде бы, разобрался ;)

I2C привлекательна тем, что в ней всего два сигнальных провода, на которые вешается произвольное количество устройств. Операцию обмена начинает и заканчивает ведущее устройство (Master). Ведомое (Slave) устройство выдает данные на шину только тогда, когда этого хочет Master.

Пример записи:



Пример чтения:



В начале любой операции Master выставляет START CONDITION, передает 7-битный адрес устройства (на одной шине их может быть несколько, и все с разными адресами), а также бит операции (чтение или запись).

В режиме Master Transfer следом передается серия байт согласно протоколу общения с устройством. Например, если Slave - это AT24C512 (EEPROM 512Кбит), то следующие два байта трактуются как адрес ячейки, куда дальше пойдет чтение/запись. Но это не догма: DS1307 (часы реального времени) нужен всего один байт (внутри 64 ячейки, и одного байта с запасом хватает для их адресации). Для некоторых устройств может идти код команды, и так далее.

Все передаваемые байты подтверждаются встречной стороной через ACK (может быть как позитивный ACK, так и негативный NACK). По завершении приема или передачи Master выставляет STOP CONDITION и освобождает шину.

В Arduino запись со сторны Master программируется так:

Wire.beginTransmission(address)
Wire.send(data);
Wire.send(data);
...
Wire.endTransmission();

Пусть вас не вводят в заблуждение "интуитивные" названия функций! ;)

beginTransmission не начинает передачу, а просто запоминает адрес устройства и инициализирует буфер передачи, send ничего в линию не передает, а добавляет в этот буфер байтики (следите, чтобы не было больше 32 байт, именно такого размера буфер в Arduino), и только endTransmission() дает команду аппаратной части TWI (так ATMEL назвал поддержку I2C в своих МК) передать скопившийся буфер на шину. Учтите, что передача будет "блокирующей", т.е. пока она не закончится, управление не перейдет на следующую строку вашего sketch-а.

В режиме приема со стороны Master достаточно одного вызова (также блокирующего):

Wire.requestFrom(address,quantity);

Функция очистит буфер приема, даст команду МК в режиме Master выставить START CONDITION, передать адрес и бит-флаг "чтение", затем будет ждать получения указанного числа байт; Получив их, МК "успокоится", выставит шину в STOP CONDITION и освободит ее.

В sketch-е можно получить данные побайтно Wire.receive(), на всякий случай осведомившись методом Wire.available() о размере приемного буфера.

Вроде бы все логично, но, как я говорил - Slave-устройства бывают разные. Иногда они используют режим REPEATED START:



Как видите, все начинается как обычная операция записи, но вместо того, чтобы перевести шину в состояние STOP CONDITION, Master повторно командует START (это называется REPEATED START) и продолжает чтением.

Получается, что сначала передается адрес или команда устройству, а затем устройство отвечает, так сказать "не отходя от кассы". Проблема Arduino заключается в том, что стандартная библиотека этого не умеет, и по завершению передачи данных она переведет шину в состояние STOP CONDITION, а затем и вообще освободит её. Дальше все зависит от устройства: оно может как запомнить полученную информацию (так часто и бывает - EEPROM заносит обновляет внутренний счетчик адреса, который использует в последующих операциях чтения), так и сброситься, оставив вас "с носом". Я уже не говорю о ситуациях, когда на шине несколько Master-устройств, и "коллега" может успеть захватить шину межу записью и чтением.

Что же делать?

Выхода два: отказаться напрочь от использования стандартной библиотеки Wire, например, в пользу либы Peter Fleury, либо модифицировать стандартную.

В результате изучения кода и медитаций над общей схемой построения библиотеки Wire, родилась дополнительная функция:

uint8_t TwoWire::transmitAndRequest(uint8_t quantity)

ее надо использовать вместо endTransmission():

Wire.beginTransmission(address)
Wire.send(data);
Wire.send(data);
...
Wire.transmitAndRequest(quantity);

Эта комбинация передаст накопившееся в буфере передачи, а затем будет запущен прием через REPEATED START.

Вернется число прочитанных байт. Об ошибке будет сигнализировать значение > 127, тогда надо сбросить старший бит (0x80) и трактовать код ошибки идентично коду возврата endTransmission().

Предлагаю заценить результат (модификация библиотеки Wire из Arduino IDE 0014), его надо распаковать в Arduino/hardware/libraries (я исключил examples): Wire.zip.

К сожалению, под рукой нет устройства, которое бы строго требовало REPEATED START, буду благодарен добровольцам, которые не поленятся проверить ;) Для микросхемы часов DS1307 - работает.

2 комментария:

  1. Вот бы еще пример кода, где общаются две ардуины в два конца.
    Не как в стандартных примерах - один пишет, второй читает, а так, чтоб один написал - второй обработал и передал ответ.
    не совсем понятно, как начать писать со слейва - если у мастера нет номера.

    ОтветитьУдалить
    Ответы
    1. У мастера действительно нет номера, поскольку он ему не нужен. Слейв отвечает и передает данные только в том случае, когда мастер его об этом попросил и ожидает от него данные.

      Что касается примеров, то за почти четыре года интерфейс Wire сильно изменился, в 2012 Todd Krein добавил туда repeated start и моя статья какбэ потеряла актуальность. Но соответствующего примера в ArduinoIDE действительно нет... Надо вополнить ;)

      Удалить