Библиотека Wire для Arduino для работы с шиной I2C. Копаем глубже...
Введение
Документация на библиотеку Wire довольно скромна. Нигде нет объяснения её связи с библиотекой twi из avr-lib, и, что еще хуже, с аппаратным модулем TWI в микроконтроллерах ATmega. Эта статья попытается заполнить пробелы и предоставить необходимую документацию по библиотеке Wire.
Для более подробной информации смотрите техническое описание на микроконтроллер, используемый в вашей плате Arduino. Это бесценная справочная информация для понимания аппаратного обеспечения TWI и способов связи контроллеров ATmega через I2C.
Внутри ATmega328
В основе данного подраздела лежит техническое описание на ATmega328 версии Rev. 8271C – 08/10. Приводимые мной страницы могут немного отличаться от текущей версии.
Для полного понимания кода библиотеке Wire
, нам необходимо понимать, что ей нужно сделать с микроконтроллером, чтобы его настроить.
Просмотр технического описания на микроконтроллер показывает нам, как работает двухпроводная (Two Wire) система. Стоит отметить:
- Микроконтроллер включает в себя аппаратный модуль TWI, который обрабатывает связь через шину I2C. ... Интересно, что это означает, что связь не обрабатывается библиотекой исключительно программно, как вы могли бы подумать! Другими словами, библиотека сама в программе не создает битовый поток. Библиотека взаимодействует с аппаратным компонентом, который выполняет тяжелую работу. Смотрите страницу 222 технического описания.
- "AVR TWI работает с байтами и основывается на прерываниях..." (раздел 21.6 на странице 224). Это ключевой момент; это означает, что
- вы настраиваете регистры;
- вы позволяете TWI модулю осуществлять связь;
- вы можете делать в это время что-то еще; ваш микроконтроллер с тактовой частотой 16 МГц не занят управлением последовательной связью на 100 кГц;
- TWI модуль вызывает прерывание, когда заканчивает работу, чтобы уведомить процессор об изменениях состояния (включая успешность операций и/или ошибки).
- Однако обратите внимание, что библиотека
Wire
блокирует ввод/вывод. Это означает, что он переходит в цикл ожидания и ждет завершения связи I2C. Ваше приложение не может ничего делать, пока модуль TWI общается по шине I2C. Обратите внимание, что это может быть не то, чего бы вы хотели: если ваша программа критична ко времени, то ваш 16-мегагерцовый процессор, застрявший в цикле ожидания и ждущий 100-килогерцового потока связи, будет не эффективен. Возможно, вам лучше написать собственный код I2C. В исходном коде avr-libc есть пример в ./doc/examples/twitest/twitest.c (смотрите http://www.nongnu.org/avr-libc/). Вы можете найти версию avr-libc, используемую вашей конкретной IDE Arduino, посмотрев файл versions.txt в каталоге установки Arduino IDE. Это будет где-то в Java/hardware/tools/avr. На Mac полный путь будет следующим /Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/versions.txt; путь у вас будет другим, но схожим.
Регистры
Вот несколько регистров, задействованных аппаратным модулем TWI (Two Wire Interface, двухпроводный интерфейс):
Регистр | Название | Назначение |
---|---|---|
TWCR | Регистр управления двухпроводным интерфейсом | Управляет действиями модуля TWI |
TWSR | Регистр состояния двухпроводного интерфейса | Сообщает статус действий TWI |
TWDR | Регистр данных/адреса двухпроводного интерфейса | Содержит данные, которые вы хотите передать или приняли |
TWBR | Регистр битовой скорости двухпроводного интерфейса | Управляет частотой тактового сигнала (SCL) |
Аппаратный модуль TWI
Смотрите следующую схему потока для записи на шину I2C:
Ссылаясь на таблицу регистров, приведенную выше, операция записи I2C с использованием аппаратного модуля TWI вкратце будет выглядеть следующим образом:
- Библиотека
Wire
конфигурирует микросхему ATmega так, что внутренний аппаратный модуль TWI использует свои выводы, которые жестко привязаны к двум аналоговым выводам (4 и 5 на Duemilanove). - Библиотека
Wire
устанавливает значение регистра TWCR для создания состоянияSTART
.- Сначала она проверяет шину I2C, чтобы убедиться, что она свободна. Шина свободна, когда на обеих линиях и SDA, и SCL установлен высокий логический уровень (потому что устройство по умолчанию устанавливает выводы на шине в третье состояние «не подключено». Общий резистор подтягивает сигнал на шине к высокому уровню).
- Что такое состояние
START
? Линия тактового сигнала SCL остается в состоянии логической 1, а мастер в это время меняет состояние на линии SDA на логический 0. Это уникальный момент, поскольку во время обычной передачи данных линии SDA изменяет состояние только тогда, когда на линии SCL установлен низкий логический уровень. Момент, когда сигнал на линии данных изменяется на логический 0, а на SCL логическая 1, является сигналом для всех устройств на шине I2C, с которыми мастер собирается начать взаимодействовать.
- Аппаратный модуль TWI при завершении выполнения действия вызывает прерывание процессора.
- Библиотека
Wir
e проверяет состояние по региструTWSR
. Предположим, что всё хорошо. - Библиотека
Wire
загружает в регистрTWDR
адрес ведомого устройства плюс бит «запись». Вместе это значение известно как "SLA+W" ("SLave Address plus Write"). - Библиотека
Wire
устанавливает значение регистраTWCR
для передачиSLA+W
. - В случае успеха, библиотека
Wire
загружает данные в регистрTWDR
, устанавливаетTWCR
, и данные передаются. - Библиотека
Wire
проверяет состояние по региструTWSR
. Предположим, что всё хорошо. - Библиотека
Wire
устанавливает значение регистраTWCR
для передачи состоянияSTOP
. В состоянииSTOP
линия SCL освобождается (переходит в высокий логический уровень), и затем линия SDA переходит в состояние логической 1. Обычно линия SDA должна оставаться неизменной, когда на линии SCL установлен высокий логический уровень.
Операция чтения выполняется похожим образом.
Проблемы при использовании с ATtiny
В библиотеке Wire
для буферов используется большой объем памяти. Он включает в себя 3 разных байтовых буфера, каждый размером 32 байта. Это не большая проблема для ATmega, но ATtiny (например, ATtiny48) обладает меньшим количеством RAM (512/1K у ATmega, и 256/512 у ATtiny). Когда я попытался использовать библиотеку Wire на ATtiny, у меня появлялись случайные сбои, зависания и т.д. Как только я изменил размер буфера (в .\libraries\Wire\utility\twi.h) на 6, всё отлично заработало.
Не делайте ошибку, думая, что вы можете просто указать в своем скетче #define TWI_BUFFER_LENGTH 6
потому, что это не работает. Способ, которым Arduino IDE компилирует ваш код, не очевиден. Библиотека компилируется отдельно от вашего скетча, а затем линкуется с ним. Поэтому вам придется изменить размер буфера в заголовочном файле библиотеки, который также включается в исходном файле библиотеки (twi.c).
Библиотека Wire
Подробное описание библиотеки.
Обратите внимание, что библиотека Wire
написана на C++. Таким образом, все перечисленные здесь вызовы являются настоящими методами класса C++. Если вы не знакомы с программированием на C++, вы можете думать о методе как о просто функции или подпрограмме, и будете правы.
Библиотека Wire
блокирует ввод/вывод. Это означает, что при отправке или приеме по шине I2C ваше приложение не запускается до завершения связи. По правде говоря, работа оборудования TWI в вашем процессоре управляется прерываниями, поэтому приложение может работать на полной скорости, во время связи TWI, но эта возможность библиотекой Wire
не используется.
В следующих подразделах в секциях «Под капотом» описывается действие, которое выполняет каждый метод с модулем TWI на ATmega и, следовательно, с шиной I2C.
begin()
- Описание
Wire.begin()
илиWire.begin(address)
должен вызываться первой для запуска любой связи по I2C, использующей библиотекуWire
. МетодWire.begin()
устанавливает индексы внутренних буферов библиотеки для чтения и записи в значение 0, затем готовит аппаратный модуль TWI с помощью вызова функцииtwi.init()
.twi.init()
– это внутренний вызов библиотекиWire
. Он выполняет следующие задачи:- включает доступ аппаратного модуля TWI доступ к соответствующим выводам процессора ATmega. Если это ATmega328, то это аналоговые выводы: 4 для линии SDA, и 5 для линии SCL шины I2C;
- устанавливает тактовую частоту, которую аппаратный модуль TWI будет использовать, если/когда он будет являться мастером на шине I2C. В исходниках она устанавливается на 100 кГц, но теоретически вы можете сбросить эту частоту, вызвав
Wire.setClock()
. Переданное значение – это битовая скорость от 400000L до 31000L.Wire.setClock(400000L);
установит частоту на 400 кГц.Wire.setClock()
должен вызываться послеWire.begin()
.Wire.begin()
устанавливает битрейт на 100 кГц.
- Аргументы
- Нет
- Под капотом
- Инициализирует внутренние переменные библиотеки Wire.
- Вызывает
twi.init()
, который настраивает два порта ATmega на использование аппаратным модулем TWI:- если это ATmega168, ATmega8 или ATmega328P: PC4 для SDA, PC5 для SCL;
- иначе PD0 для SCL, PD1 для SDA. Для более подробной информации смотрите техническое описание на свой процессор.
- Устанавливает скорость передачи TWI на 100 кГц.
- Включает аппаратный модуль TWI
begin(address)
- Описание
- Записывает TWAR (регистр адреса TWI) младшие 7 бит аргумента
address
. 8 бит аргумента сдвигаются влево, и добавляется бит 0. Этот 0 – это бит TWGCE. - Запускает функции
twi_attachSlaveTxEvent(onRequestService);
иtwi_attachSlaveRxEvent(onReceiveService);
. Эти функции назначают определенные пользователем функции, которые вызываются во время передачи и/или приема по I2C, когда процессор работает в качестве ведомого устройства I2C. Таким образом,onRequest()
иonReceive()
должны быть вызваны до вызова этого метода. Смотрите описание методовonRequest()
иonReceive()
ниже. - Запускает метод
begin()
, как и метод приведенный выше.
- Записывает TWAR (регистр адреса TWI) младшие 7 бит аргумента
- Аргументы
address
: 8-битное целое число или значениеint
(по умолчанию размером 16 бит). Если ATmega будет в режиме ведомого, то это его адрес. Он преобразуется в 8-битное целое число. Если задано значение типа int (16 бит), у числа будет отсечено 8 старших бит.- Примечания
- Если вы хотите, чтобы какая-либо функция автоматически вызывалась после каждой передачи по I2C, то для начала связи по I2C вы должны использовать эту функцию.
- Вы должны использовать эту функцию, если хотите использовать Arduino как ведомое устройство на шине I2C.
Этот метод устанавливает значение регистра
TWAR
в аппаратном модуле TWI, который используется, когда вы хотите, чтобы Arduino работал как ведомое устройство I2C. Блок соответствия адресов TWI проверяет входящие сообщения I2C с содержимымTWAR
. Когда есть совпадение, блок управления TWI оповещается, и модуль TWI будет выполнять действие в зависимости от настроекTWCR
. Обратите внимание, что в библиотеке Wire битTWGCE
не устанавливается в 1. Это означает, что Arduino не будет отвечать на адрес общего вызова (адрес 0x00, который в основном используется для широковещательной рассылки по I2C).twi_attachSlaveTxEvent()
назначает функцию обратного вызова, которая будет использоваться в передаче I2C, если ATmega используется как ведомый I2C передатчик. Эта функция должна быть предварительно установлена с помощью методаonRequest(handler)
. Она вызывается после полученияSLA+R
, и она должна вызыватьtwi_transmit()
, чтобы заполнить буфер передачи данными. Она также должна вызыватьtwi_reply(1)
, чтобы реально начать передачу по шине I2C (аргументtwi_reply()
указывает, что ATmega отправитACK
после своего ответа). После вызоваtwi_reply()
обработчик прерывания библиотеки Wire и аппаратный модуль TWI будут обрабатывать передачу данных.twi_attachSlaveRxEvent()
назначает функцию обратного вызова, которая будет использоваться при приеме по I2C, если ATmega используется как ведомое I2C устройство. Эта функция должна быть предварительно установлена с помощью методаonReceive(handler)
; она должна вызываться с двумя аргументами: приемный буфер и индекс к последним данным в буфере. Эта функция выполняется после принятия состоянияSTOP
, сигнализирующего о завершении сеанса I2C. ATmega подтягивает линию тактового сигнала к нулю, тем самым заставляя ведущее устройство I2C дождаться завершения обработчика.- Под капотом
- Устанавливает значение регистра
TWAR
в аппаратном модуле. - Назначает функцию обратного вызова, используемую при передаче по I2C. Эта функция вызывается. Эта функция после получения
SLA+R
, и она должна вызыватьtwi_transmit()
иtwi_reply(1)
.
- Устанавливает значение регистра
requestFrom(address, count)
- Описание
Используется, когда ATmega работает в качестве ведущего устройства I2C. Запрашивает
count
байт от ведомого устройства I2C, по заданному адресу. Каждый вызов этого метода сбрасывает индекс буфера чтения; то есть внутренний буфер считывается с начала, с нулевой ячейки массива. Обратите внимание, что адрес должен быть 7-битным; если передается 8-битный адрес, старший бит просто отсекается.Метод
requestFrom()
вызывает функциюtwi_readFrom()
, которая фактически выполняет чтение I2C, отправляя состояниеSTART
и ожидая заполнения буфера в соответствии с запросом. Ведомое устройство I2C ответитNACK
на запрос на чтение, если вы запросите больше байтов, чем доступно, и методrequestFrom()
завершится.Этот метод возвращает количество байтов, прочитанных с ведомого устройства.
- Аргументы
: 8-битное целое число, или значениеaddress
int
(обычно размером 16 бит). Адрес ведомого I2C устройства, с которым ATmega будет связываться. Он приводится к 8-разрядному беззнаковому целому числу. Если задано значение типаint
(16 бит), у числа будет отсечено 8 старших бит. Затемaddress
сдвигается влево на один бит, поэтому используются только младшие 7 бит. Если у вас 8-битный адрес, чтобы использовать его на Arduino, вы должны сдвинутьaddress
вправо (потеряв самый младший бит). : целое число или 8-битное значение (типcount
uint8_t
), задает количество байтов для чтения. Значениеcount
ограничено значениемBUFFER_LENGTH
, которое определено в заголовочном файле Wire.h; на время написания оно было рано 32. Еслиcount
больше 32, то он всё равно будет установлен в значение 32.
- Под капотом
- Включает модуль TWI, который передает состояние
START
, таким образом, начиная сеанс связи по I2C. - Поэтому вызов функции
SIGNAL
будет использоваться при срабатывании прерывания процессора от модуля TWI, чтобы продолжить I2C сеанс и заполнить буфер чтения.
- Включает модуль TWI, который передает состояние
beginTransmission(address)
- Описание
Используется, когда ATmega действует как мастер I2C. Устанавливает внутренние переменные библиотеки Wire для подготовки к передаче на указанный адрес.
Следующий вызов библиотеки Wire должен быть методом
send()
.- Аргументы
: адрес ведомого устройства I2C. Это 7-битный адрес; если передаются 8 бит, самый старший бит просто отсекается.address
- Под капотом
- Никакой активности в TWI. Этот метод просто изменяет некоторые внутренние переменные библиотеки Wire.
endTransmission()
- Описание
Вызывает функцию twi, которая выполняет реальную передачу TWI в режиме ведущего передатчика: он пытается стать мастером на шине I2C и записывает последовательность байтов в устройство на шине. Байты должны быть предварительно загружены в буфер передачи библиотеки Wire с помощью вызова
send()
.Результаты возвращаются в виде значения размером байт. Возможны следующие варианты:
- 0: передача успешна;
- 1: буфер передачи больше буфера twi. Этого не должно случиться, так как размер буфера TWI, установленный в twi.h, равен размеру буферу передачи, установленному в Wire.h;
- 2: адрес был передан, но был получен
NACK
. Это проблема, и мастер должен отправить состояниеSTOP
; - 3: данные были переданы, но был получен
NACK
. Это означает, что ведомому устройству нечего передавать. Мастер может послать состояниеSTOP
или повторитьSTART
; - 4: произошла другая ошибка TWI (например, мастер потерял арбитраж шины).
- Под капотом
- Включает модуль TWI, который отправляет I2C условие
START
, что запускает сеанс связи I2C. - Поэтому вызов функции
SIGNAL
будет использоваться при срабатывании прерывания процессора от модуля TWI, чтобы продолжить I2C сеанс и передать содержимое выходного буфера данных по шине I2C.
- Включает модуль TWI, который отправляет I2C условие
send()
- Описание
Заполняет буфер передачи данными, полученными из аргументов. Список возможных аргументов приведен ниже.
Термин "send" (отправить) для этого метода является чем-то неправильным. Этот метод на самом деле ничего не отправляет. Буфер данных (или массив) просто заполняется аргументом данных.
В режиме ведущего передатчика: в случае
send(value)
в буфер данных добавляется один байт. В случаеsend(string)
илиsend(data, quantity)
представленные данные добавляются в буфер данных с ограничениемBUFFER_LENGTH
. Любые данные, переданные для отправки, и которые могут переполнить буфер после превышенияBUFFER_LENGTH
(который на момент написания равен 32), просто игнорируются.При работе в качестве ведомого передатчика I2C данные вставляются в буфер передачи ведомого I2C для доставки оборудованием TWI. Смотрите
Wire.begin(address)
иWire.onRequest(handler)
.- Аргументы
Есть три варианта аргументов:
send(value)
: отправить байт, где значение типаuint8_t
;send(string)
: отправить строку символовchar
. Вызываетsend(data, quantity)
, гдеquantity
вычисляется как длина строки;send(data, quantity)
: отправить массив байтов данных. Этот массив ограничен значениемBUFFER_LENGTH
, которое задано равным 32 в заголовочных файлах. Еслиquantity
больше, чемBUFFER_LENGTH
, дополнительные данные просто игнорируются.
- Под капотом
- Никакой активности в TWI. Этот метод просто изменяет некоторые внутренние переменные библиотеки Wire.
available()
- Описание
Возвращает количество байтов доступных в приемном буфере библиотеки Wire. Этот буфер заполняется:
- в методе
requestFrom()
; - в методе
onReceiveService()
, который вызывается во время сессии ведомого I2C приемника после того, как мастер послал состояниеSTOP
или повторилSTART
, по сути, завершая передачу данных.
- в методе
- Аргументы
Нет.
- Под капотом
- Никакой активности в TWI. Этот метод просто изменяет некоторые внутренние переменные библиотеки Wire.
receive()
- Описание
Возвращает следующий байт из приемного буфера библиотеки Wire. Смотрите метод
available()
выше для более подробной информации о приемном буфере библиотеки Wire.- Аргументы
Нет.
- Под капотом
- Никакой активности в TWI. Этот метод просто изменяет некоторые внутренние переменные библиотеки Wire.
onReceive(handler)
Устанавливает функцию обработчик handler
, которая должна вызываться после того, как ATmega получит данные, работая в качестве ведомого устройства I2C. Обработчик вызывается с единственным аргументом, целым числом, которое является количеством полученных байтов. Он не должен возвращать каких-либо значений. Поэтому он должен выглядеть так:
void handler(int byteCount)
Шина I2C не освобождается до тех пор, пока не завершится выполнение обработчика, поэтому, вероятно, стоит убедиться, что обработчик завершается относительно быстро. Обратите внимание, что «относительно быстро» означает, что для ATmega это здесь предоставляется довольно много времени, так как на большинстве Arduino контроллер работает на частоте 16 МГц, а связь по I2C – лишь на частоте 100 кГц.
Обработчик может использовать метод receive()
для чтения следующего байта из приемного буфера библиотеки Wire.
onRequest(handler)
Устанавливает функцию обработчик handler
, которая должна вызываться, когда ATmega работает в качестве ведомого передатчика. Обработчик не принимает никаких аргументов и не возвращает каких-либо значений. Поэтому он должен выглядеть так:
void handler(void)
Обработчик вызывается после того, как ATmega принял SLA+R
(SLave Address plus the Read bit, адрес ведомого плюс бит чтения). Он должен вызвать twi_transmit()
, чтобы заполнить данными буфер передачи. Либо он должен вызвать метод send()
из библиотеки Wire (смотрите выше), чтобы заполнить буфер передачи. Затем он должен вызвать twi_reply(1)
, чтобы на самом деле начать передачу по шине I2C (аргумент "1" в twi_reply()
указывает ATmega, что после отклика необходимо послать ACK
). Как только будет вызвана функция twi_reply()
, будет вызвано прерывание, работа обработчика будет прервана, и модуль TWI обработает передачу данных.