Одной из отличительных особенностей NXT является наличие 4-х портов для связи с цифровыми устройствами по протоколу I2C, по двухпроводной линии CLK, SDA.
Недавно мне понадобилось изготовить универсальный адаптер для NXT, и пришлось досканально разобраться с особенностями I2C- NXT. Лично для меня там было несколько «белых пятен», поэтому решил поделиться своими «открытиями», может кому пригодится.
Сам протокол подробно описан в различной литературе, в том числе и Википедии. Кстати, в одном из солидных источников, описывающих этот протокол именно для NXC, нашел информацию, которая меня смутила. Там говорится: «NXT посылает адресс устройства, младший бит в котором определяет направление передачи чтение/запись, затем посылает номер регистра, в который будет производится запись или из которого будет считываться информация, а затем идет передача данных в зависимости от направления.». Это, конечно, не верно. Действительно, устройства типа часов реального времени, предполагают, что NXT сообщит им, из какого именно внутреннего регистра будет считано его значение. Но это делается двумя последовательными сеансами связи. Т.е. сначала NXT записывает в устройство номер регистра, а следующим сеансом считывает из него данные.
Отмечу конструктивные особенности NXT:
- В I2C сети, NXT может быть только мастером-ведущим;
- NXT владеет шиной синхронизации монопольно, не взирая на попытки медленных устройств удержать CLK на время выполнения своих операций;
- Скорость передачи/приема фиксирована 9600 бод;
- За один сеанс можно принять/получить максимум 16 байт;
- Линии, CLK, SDA имеют последовательно включенные защитные резисторы 4,7К, внешние подтягивающие резисторы обязательны (рекомендовано 82К).
Теперь о соответствующий функциях NXC, в особенности которых я въехал не сразу.
Надо сказать, что родитель языка Джон Хансен, нормальный мужик. Он даже пару раз отвечал на мои вопросы и терпеливо делает это чуть ли не каждый день с сотнями других вопрошающих. Язык развивается, в том числе улучшается документация. NXC обростает новыми функциями, большинство из которых, впрочем, работают только со своей версией Firmware.
Остановлюсь только на основных функциях обмена с I2C устройствами, точно работающих с леговской прошивкой.
NXT Firmware имеет для каждого из 4-х портов по 16-байтному буферу для приема и такому-же буферу для передачи данных, а также регистры управления и состояния, с которыми и работают функции на высоком уровне.
Первая из них:
I2CWrite(Port, ReturnLen, OutBuffer)
С Port все понятно, один из 4-х возможных.
OutBuffer это, собственно массив данных, которые будут переданы от NXT I2C устройству. Первым байтом массива д.б. адресс устройства (младший бит адреса устройства зарезерворован для обозначения направления передача/прием и устанавливается автоматически). Количество байт для передачи равно длине массива и не должно превышать 16-ти.
Если параметр ReturnLen= 0, то происходит только передача данных устройству. Иначе, после сеанса передачи, NXT открывает новый сеанс связи с темже адресом, но уже для чтения (при этом NXT формирует состояние «повторный старт» минуя состояние «стоп»). И считывает количество байт, определенное параметром ReturnLen, загоняя данные в буфер приема. Все это происходит в фоновом режиме, т.к. функция только переписывает массив данных в буфер передачи и дает соответствующие указания системе.
Функция возвращает 0 в случае отсутствия ошибки, что еще не говорит об успехе, т.к. сам обмен данными с устройством еще только начался. Ошибка возникает в случае, если порт в этот момент занят или неправильно сконфигурирован, либо массив данных превышает размер буфера передачи.
Следующая функция используется как раз для определения результатов работы предыдущей функции.
I2CStatus(Port, nByteReady)
while(I2CStatus(port, nByteReady) == STAT_COMM_PENDING); такой цикл обычно используют, для ожидания, пока NXT закончит приемо/передачу данных. Переменная nByteReady будет содержать количество байт реально принятых в буфер приема, которое может отличаться от ReturnLen в случае, если I2C устройство оборвало связь во время сеанса. Сама функция вернет 0, если все прошло удачно и ERR_COMM_BUS_ERR, в случае, если передать или принять данные так и не удалось, либо коды ошибок, характерных для предыдущей функции.
Следущая функция:
I2CRead(Port, ReturnLen, InBuffer)
не устанавливает связи с устройством, а лишь считывает из буфера приема в InBuffer (массив) количество байт, определенное в ReturnLen. Буфер приема можно считать, как полностью, так и частями. Оставшееся в нем количество байт всегда можно проверить с помощью предыдущей функции. В документации написано , что ошибка возникает, если в буфере на данный момент не содержится запрашиваемое количество байт. В реальности, функция всегда выдает 0. Даже, если буфер был пуст. Если запрашивается больше байт, чем содержится в буфере, то считывается сколько есть. Буфер приема автоматически очищается лишь при его переполнении, что по-моему не есть хорошо. Может, например, возникнуть такая ситуация: В буфере оставалось 10 байт от предыдущего сеанса и мы считали еще 10. После получения первых 6-ти байт, буфер очистится и примет оставшиеся 4. Но, первые 6 пропадут безвозвратно, и ошибки при этом не возникнет. Впрочем, такая ситуация скорее надуманная и может возникнуть лишь с попустительства программиста.
И, наконец, последняя функция, объединяющая в себе действие 3-х предыдущих:
I2CBytes(Port, OutBuffer, OutInCount, InBuffer)
Т.е. сначала отправляются данные из OutBuffer,затем принимается количество данных определенное в OutInCount, функция дожидается завершения трансакции и переписывает содержимое буфера приема в InBuffer, заодно сохраняя количество принятых байт в ту же OutInCount. В отличии от предыдущих, эта функция возвращает true в случае удачного выполнения или false в случае ошибки, не вдаваясь в детали ошибок.
Для считывания информации с I2C устройства, эта функция наиболее удобна. Например, внешняя память поддерживает автоматический инкремент адреса, а значит однократным применением этой функции, мы сразу считаем 16-разрядное слово из памяти.
Помятуя о том, что режим удержания шины CLK пользовательским устройством, не поддерживается, оно должно успеть подготовить данные для NXT, в соответствии с его запросом, в небольшой промежуток времени между сеансами передачи и приема (в нашем случае, около 100мкС). Если устройство достаточно медленное, то лучше использовать функцию дважды – для передачи (OutInCount=0), и, с интервалом, достаточным для переваривания устройством, для приема (OutBuffer в этом случае должен содержать только адресс устройства).
Кроме этого NXC содержит еще 26 так называемых низкоуровневых функций на эту-же тему. Лично мне они пока ни разу не пригодились, так что и говорить про них не буду.
И так разошелся на целую статью. Ну, на то и форум, чтобы опытом делиться.