Программная реализация шины I2C
Дата: 27 Апреля 2015. Автор: Алексей
В библиотеке AXLIB есть набор функций для работы с данной шиной.
Сразу хочу ответить на вопрос, почему не TWI? Да, у Атмела есть реализация аппаратной шины I2C под названием TWI. И вроде как оно лучше, есть прерывания, вся обработка ошибок шины и ногодрыг ложится на плечи МК, а мы просто подтаскиваем данные или забираем полученные. Все красиво да есть одно но. Нет у AVR ремапа как у STM32. И если нужен TWI, то прощай порт. Думал, я думал и решил, а ну его этот TWI, пусть будет программный. Теперь можно реализовать шину на любом порту. Но. Что при этом теряется. Во первых, теперь вся проблема ногодрыга лежит на плечах программы. Так же перестал обрабатывать ошибки. Конечно занятость линии можно и понюхать и к тому же обработать АКи-НАКи, но полный аспект не жди. Так же я не стал забивать голову с многомастеровыми вариантами, так как больше одного я все равно не буду использовать. Я честно и не представляю где требуется два мастера. Ведь эта шина для межблочного обмена на уровне одной платы. Нафига два МК. Ну есть, значит надо, а для любительских технологий максимум часы, ЕЕПРОМка, да еще какая-нибудь микросхема. В общем давайте как от лирики к делу.
Шина I2C была разработана фирмой Philips и как всегда бла-бла-бла. Никто это читать не будет все равно. Сразу к делу. Как устроена физическая часть.
Что мы тут имеем. Для реализации линии, требуется всего два провода. Один SCL, другой SDA. Что по ним бегает. По проводу SCL бегают тактовые синхроимпульсы, а по проводу SDA данные. Те два резистора справа должны подтягивать линии к плюсу питания. Значение может лежать от 4,7К до 10К. Теперь давайте разберемся зачем нужны эти резисторы. Дело в том что данные по линиям передаются следующим образом. Если мне нужно передать ноль, то я прижимаю линию к нулю. Если мне надо передать единицу, то мне нужно прижать линию к плюсу. Но вот не задача. Ведущий прижимает линию к нуля, а в это время ведомый прожевал данные и выкатил единицу на линию. Что будет? Верно, короткое замыкание. Вероятнее всего порты погорят. Как быть? Вот для этого и установлены резисторы. Когда кому-то приспичит выставить единицу, то он просто отпускает линию, переведя свою ногу в Z состояние, а резисторы подтянут к питанию и на линии появится единица. В этом случае даже если кто-то прижмет линию к нулю, короткого не будет. Все довольны и все счастливы. Таким образом решается арбитраж если на линии больше одного ведущего. Тот кто первый выставил ноль, тот и главнее, а другие ведущие увидев ноль переходят в режим ведомых. Но как я уже говорил мы такой вариант рассматривать не будем . Едем дальше. Передача данных. Данные по шине передаются пакетом. Пакет имеет строгий формат. Сначала передается условие старта. Затем передается семибитный адрес ведомого устройства с которым хотим поговорить. Восьмым битом идет режим записи или чтения. В ответ ведущий должен получить ACK если ведомый отозвался на свой адрес, либо NACK если на линии тишина. Далее в зависимости от условия записи или чтения идут байты данных завершающиеся в конце обязательными АКами или НАКом. Самым последним идет условие стопа. Так же есть еще одно не обязательное условие это повторный старт. Его нужно пихать если мы например хотим обратиться к ведомому не отпуская линии. Боле подробно это обсудим при работе с микросхемой часов реального времени, а пока чисто теория.
Вот как выглядит посылка адреса. Верхний рисунок. Сначала подается условие старта, при высоком уровне SCL переводим линию SDA с единицы в ноль. Далее опускаем линию SCL в ноль и выставляем старший бит. В нашем случае это единица. После поднимаем линию SCL в единицу и держим ее. В это время ведомый считывает уровень который присутствует в данный момент на линии SDA. У нас это единица. Далее ведущий снова опускает линию SCL в ноль, а на линию SDA выставляет следующий бит из байта. И так далее. После восьмого бита ведомый должен отпустить линию SDA и прижать SCL к нулю. В это время ведомый должен ответить либо ACK, прижав линию SDA к нулю, либо ничего, то есть NACK. Далее ведущий должен поднять линию SCL к единице и прочитать сто пришло на линию SDA. Если ACK, то что-то делаем дальше, а если его послали NACK, то принимаем решение что делать в этом случае. В любом случае если больше шина нам не нужна, то мы ее отпускаем подав условие стопа. Для этого нужно поднять линию SCL, а потом поднять линию SDA. На рисунках сверху это хорошо видно. Сверху ответ ACK, снизу NACK. Теперь давайте рассмотрим запись байта в ведомое устройство.
Сначала нам нужно подать условия старта. Затем толкаем семибитный адрес ведомого, старшим битом вперед. Восьмым битом едет бит записи, тобиш ноль. Потом слушая шину получаем ACK, ведомый жив здоров и нас слушает. Теперь когда мы наладили диалог с ведомым кидаем в шину адрес регистра в который будем писать. Если мы хотим писать несколько байт, то кидаем адрес первого регистра, а уже после записи первого байта данных, ведомый увеличит счетчик адреса на единицу и будет ждать следующий байт. После адреса регистра нужно получить ACK о том что адрес регистра получен. Теперь по надобности кидаем в шину байты и после каждого получаем ACK. Когда байты писать больше не надо, то просто кидаем условие стоп и отпускаем шину. Теперь давайте рассмотрим чтение.
Тут немного мудрено. Сначала старт, за ним адрес и условие записи (ноль). Получаем ответ ACK что все в порядке. За ним шлем адрес регистра из которого будем читать. Потом повстарт (я о нем говорил, это когда мы вкатываем старт не отпуская шины). Снова адрес этого же ведомого и бит статуса но уже на чтение (единица). Получаем ACK. А теперь просто генерим синхроимпульсы на линии SCL и при высоком уровне смотрим что на линии SDA. Если мы будем читать еще один байт, то после чтения текущего байта обязательно отвечаем ведомому ACK, мол все прочел. Если это был прочитан последний байт, то сначала посылаем ведомый NACK, а затем кидаем в шину условие стоп. Вот и все. Это минимальные требования для того чтобы пообщаться с ведомыми микросхемами. Вот как выглядит запрос температуры из часов реального времени DS3231.
Зелененькая точка это старт. Затем адрес устройства и бит записи, в ответ ACK. Далее запись адреса регистра температуры и ответ ACK. Снова зелененькая точка это повстарт. Передача адреса устройства с битом чтения, в ответ ACK. Чтение температуры (27). В конце посылаем NACK и красная точка это стоп. В следующей статье разберемся как с помощью данной библиотеки получать и принимать данные из этих часов.
На закуску готовая библиотека для работы с шиной под Atmel Studio 6.2.
Все что необходимо, это выбрать порт и разряды на нем для SDA и SCL. Дальше использовать шесть функций.
- void I2c_init(void) // Инициализация шины.
- void i2c_start(void) // Подача условия старт
- void i2c_restart(void) // Подача условия повстарт
- BYTE i2c_stop(void) // Подача условия стоп. Возвращает 0, 1, 2 или 3. 0 все хорошо, 1 ошибка SDA, 2 ошибка SCL и 3 ошибка SDA и SCL вместе.
- BYTE i2c_send_byte(BYTE data) // Посылает на шину байт и возвращает ACK или NACK
- BYTE i2c_read_byte(BYTE ask) // Читает из шины байт и в конце отсылает ACK или NACK в зависимости что передали аргументом функции.
В библиотеке AXLIB есть набор функций для работы с данной шиной.
Все. Будут вопросы пишите.
Владимир 14.08.16 14:12
Здравствуйте, Алексей!
Пишу к Вам ,как мастеру,дабы выяснить, можно ли использовать
Вашу библиотеку в ATtiny45. Мне надо связать ATmega8535 (ведущий) посредством I2C с несколькими ATtiny45 (ведомый). И второй вопрос: как объяснить ведомому, что он ведомый при программной реализации I2C. С уважением amir@sao.ru
Алексей 14.08.16 15:01
Все команды начинает мастер. Слейв только отвечает. Напрямую привязать к тини я думаю можно реализовать. Программный и2ц платформонезависим.
Миха 25.12.16 18:14
а лучше не использовать дерьмо типа и2с а пользоваться простыми и удобными spi uart
Алексей 25.12.16 19:13
А так же все периферийные микросхемы с шиной I2C переделать на SPI и UART. Идеальное решение.
Владимир_2 24.01.17 11:17
Здравствуйте, Алексей
Спасибо за очень практикабельную статью.
Но и у Вас не нашел прямой ссылки - откуда берется адрес ведомого ? (Тот за который положен лицензионный платеж). Можно ли как, например, в 1-Wire, прочитать его из девайса или вообще проигнорировать, если на шине только одно устройство ?
Мне, кстати, попадались схемы автомагнитол,где разные I2C-девайсы управлялись по отдельным парам проводов.
Спасибо,
Спасибо за очень практикабельную статью.
Но и у Вас не нашел прямой ссылки - откуда берется адрес ведомого ? (Тот за который положен лицензионный платеж). Можно ли как, например, в 1-Wire, прочитать его из девайса или вообще проигнорировать, если на шине только одно устройство ?
Мне, кстати, попадались схемы автомагнитол,где разные I2C-девайсы управлялись по отдельным парам проводов.
Спасибо,
Алексей 24.01.17 14:39
Нет. Здесь сама концепция протокола такова, что первый байт передает адрес устройства и бит направления данных.
По поводу лицензии дело обстоит так. Вот ссылка на офсайт. Здесь больше нет ссылок на лицензию. Вероятно они смирились с таким наплывам и просто как 1W отпустили ее. Хотя в спецификации последняя строка четко напоминает о возможной мзде за использование названия шины. C-bus — logo is a trademark of NXP Semiconductors N.V Поэтому Атмел назвал ее по своему)))
По поводу лицензии дело обстоит так. Вот ссылка на офсайт. Здесь больше нет ссылок на лицензию. Вероятно они смирились с таким наплывам и просто как 1W отпустили ее. Хотя в спецификации последняя строка четко напоминает о возможной мзде за использование названия шины. C-bus — logo is a trademark of NXP Semiconductors N.V Поэтому Атмел назвал ее по своему)))
Владимир_2 25.01.17 00:10
Благодарю, Алексей, что нашли время на комментарий.
Но я все-таки не понял где взять адрес вызываемого устройства для первого байта посылки ведущего. Мне конкретно нужно AVR нацелить на TSA6057 и TDA7300.
Этот адрес индивидуален для конкретного экземпляра адресанта (что для 7 бит маловероятно) или его типа ?
Почему-то этот факт в описаниях протокола всегда остается за кадром. Или все настолько очевидно, что я не вижу бревно в глазу.
Не обессудьте за навязчивость,
Спасибо,
Но я все-таки не понял где взять адрес вызываемого устройства для первого байта посылки ведущего. Мне конкретно нужно AVR нацелить на TSA6057 и TDA7300.
Этот адрес индивидуален для конкретного экземпляра адресанта (что для 7 бит маловероятно) или его типа ?
Почему-то этот факт в описаниях протокола всегда остается за кадром. Или все настолько очевидно, что я не вижу бревно в глазу.
Не обессудьте за навязчивость,
Спасибо,
Алексей 25.01.17 08:43
Если микросхема заводского типа, то ее адрес указан в документации и он уникален. Если изготовитель микросхемы дает возможность устанавливать несколько штук на шину, то на микросхеме предусматривают несколько выводов для изменения адреса. Чаще всего три ножки, что дают возможность подключать до 8 микросхем. Если вы сами сочиняете ведомое устройство, то присваеааете адрес сами на свое усмотрение.
Владимир_2 26.01.17 23:37
Здравствуйте, Алексей
Действительно, в даташитах обнаружил адреса девайсов. Спасибо за нацеливание.
Попробую реализовать на ассемблере. Интересно будет посмотреть в сравнении с 1-Wire и SPI.
Спасибо еще раз,
Владимир
Действительно, в даташитах обнаружил адреса девайсов. Спасибо за нацеливание.
Попробую реализовать на ассемблере. Интересно будет посмотреть в сравнении с 1-Wire и SPI.
Спасибо еще раз,
Владимир
Руслан 30.07.17 21:34
Добрий вечер!
использую вашу библиотеку для работи с микросхемой мср23008.
но ничего не работает!
вот кусок кода:
...
i2c_start ();
i2c_send_byte (0x40); //адрес микросхеми
i2c_send_byte (0x09); //адрес регистра
i2c_send_byte (0x55); // данние
i2c_stop ();
_delay_ms(1);
можете чем нибудь помочь?
использую вашу библиотеку для работи с микросхемой мср23008.
но ничего не работает!
вот кусок кода:
...
_delay_ms(1);
можете чем нибудь помочь?
Руслан 31.07.17 15:38
вроде ничего сложного но не работает в чем же может бить загвоздка?
есть рабочий код написанний на CodeVision AVR
...
void write_mcp23008(byte addrerss_mcp23008,byte address_byte, byte data)
{
i2c_start ();
i2c_write(addrerss_mcp23008);
i2c_write(address_byte);
i2c_write(data);
i2c_stop ();
delay_ms(1);
}
...
так он ничем не отличается от моего
в моем коде функция и2с стоп возвращает 0 а ето ОК
k =i2c_stop ();
значит общение происходит успешно!
в чем же дело?
есть рабочий код написанний на CodeVision AVR
...
{
i2c_write(addrerss_mcp23008);
i2c_write(address_byte);
i2c_write(data);
delay_ms(1);
}
...
так он ничем не отличается от моего
в моем коде функция и2с стоп возвращает 0 а ето ОК
k =
значит общение происходит успешно!
в чем же дело?
Руслан 31.07.17 16:14
адрес 0 1 0 0 А2 А1 А0 R/W ето из даташита
А2,А1,А0 у меня на минусе
1 если читать 0 если писать в чип
значит виходит 01000000 а ето 0х40
регистр виводов 0х9 либо 0х0А пробовал оба варианта!
вроде все верно а не работает!
мозг уже закипел потому что вроде все просто вроде код рабочий
а не работает!
А2,А1,А0 у меня на минусе
1 если читать 0 если писать в чип
значит виходит 01000000 а ето 0х40
регистр виводов 0х9 либо 0х0А пробовал оба варианта!
вроде все верно а не работает!
мозг уже закипел потому что вроде все просто вроде код рабочий
а не работает!
Руслан 31.07.17 20:53
все уже разобрался!
оказивается ей MCP23008 нужно принудительно записать в первий регистр что она работает на виход (0х00)!
а я думал что тут как с микроконтроллерами по умолчанию все 0 и зачем ей записивать в регистр нули если они там и так должни бить а оказивается не тут то било!
оказивается ей MCP23008 нужно принудительно записать в первий регистр что она работает на виход (0х00)!
а я думал что тут как с микроконтроллерами по умолчанию все 0 и зачем ей записивать в регистр нули если они там и так должни бить а оказивается не тут то било!
Алексей 31.07.17 22:33
Я об этом как раз и хотел сказать, но целый день занят был.)))
Витя 06.01.18 11:33
Здравствуйте. А ни у кого нет проекта по подключению ds1307 таким способом, именно программно??? Я не силен в этом, помогите пожалуйста. Спасибо
Алексей 07.01.18 00:29
Библиотека axlib. Для ее использования можно скачать генератор кода. Я даже видео снял как с ней работать. Там как раз есть эти часы.
Александр 20.05.18 00:06
Доброго времени суток. Вы упомянули у следующей статье. Можно узнать когда примерно ее ждать ?
Алексей 20.05.18 08:51
Она давно уже написана. http://www.avrki.ru/articles/content/ds3231/
Максим 11.03.19 02:03
Доброго времени суток, Алексей. Не сталкивались случаем с OPT3001?