Урок 16
Часть 3
Интерфейс TWI (I2C)
В предыдущей части занятия мы продолжили знакомство с шиной I2C и уже создали проект для того, чтобы занятия проработать на практике. Также мы познакомились с интересной микросхемой EEPROM AT24C32, работающей на данной шине.
Продолжаем изучение шины.
Шину мы инициализировали в проекте, выставив ей скрость с помощью регистра TWBR, теперь давайте поближе познакомимся с управляющим регистром — TWCR
Как мы видим, в данном регистре присутствует насколько битов.
TWINT — это бит прерываний. Можно его назвать битом не управляющим, а статусным, так как устанавливается он аппаратно в тот момент, когда определённое задание на шине завершится и будет ожидаться реакция программы. А вот сбрасывается данный флаг не аппаратно, а только программно — записью в него логической 1.
TWEA — бит или флаг, разрешающий подтверждение. Если мы его не установим, то мы не будем просить подтверждение от ведомого устройства, а в случае, если контроллер наоборот является ведомым устройством, то с очередной посылкой мы не отправим бит подтверждения в конце какой-то посылки.
TWSTA — бит установки или генерирования условия «Старт».
TWSTO — бит установки или генерирования условия «Стоп».
TWWC — бит ошибочной записи. Устанавливается при попытки записи в адресный буфер, когда флаг TWINT ещё не установился. Ещё называется он флагом коллизий. Данный бит сбросится, когда TWINT будет равен 1.
TWEN — бит, активирующий шину I2C. Если мы его устанавливаем, то шина I2C начинает пытаться выполнять задание в зависимости от условий.
TWIE — бит, который разрешает прерывания.
Теперь статусный регистр TWSR
Пять старших битов регистра содержат код статуса операции, которая выполнялась перед тем, как мы читаем регистр. Как правило мы с помощью маскирования сбрасываем три младших бита в 0, ну конечно не в самом регистре, а в переменной, в которую мы его считали и затем уже исследуем полученный результат. В даташите на контроллер содержится перечень в виде нескольких таблиц различных кодов статуса. Несколько их потому, что есть несколько условий — ведущее или ведомое устройство и чтение или запись происходила.
Ну а два младших бита — TWPS1 и TWPS0 — это биты для делителя частоты шины, с которыми мы уже знакомились немного в 1 части занятия.
Вот такие вот могут быть варианты комбинаций данных битов и зависимость делителя от этих комбинаций
Займёмся теперь непосредственно шиной.
Как же всё работает:
Нам для начала любой посылки необходимо сгенерировать условие СТАРТ, чтобы ведущие устройства «проснулись» и начали приём, а потом уже «думали», не их ли адрес к ним пришёл. Условие СТАРТ генерируется путём перехода из высокого логического состояния шины SDA в низкое (отрицательного фронта), а затем черз некоторое время должно то же самое произойти и с шиной SCL. Вот тогда ведущий и поймёт, что по шине началась какая-то передача. А если контроллер у нас ведомый, то мы наоборот должны отследить данный процесс на наших проводах.
Но у нас шина аппаратная и париться на этот счёт нам не нужно, ибо всё контроллер сделает сам.
Соответственно, как мы видим из графика наверху, условие СТОП генерируется наоборот. Сначала положительный фронт на шине SCL, а затем на SDA.
В случае, если контроллер у нас ведомый, то мы, для того, чтобы сгенерировать условие СТАРТ, должны сделать следующее.
Вот таким вот образом у меня подключен модуль с микросхемой
Вернёмся в проект и напишем для генерации условия СТАРТ отдельную функцию в файле twi.c
void I2C_StartCondition(void)
{
TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
while(!(TWCR&(1<<TWINT)));//подождем пока установится TWIN
}
Вот такая вот интересная функция. Что же здесь происходит?
А происходит следующее.
Мы сначала устанавливаем определённые биты в регистре управленя, говоря при этом шине о том, что мы посылаем условие СТАРТ (TWSTA), а также запускаем шину (TWEN). Ну а бит TWINT мы соответственно устанавливаем в единицу. А в ноль он, соответственно установится тогда, когда данное задание закончится. Вот для этого и существует вторая строка, где мы висим в цикле, пока он, собственно, и не установится. Именно в ноль! А не в единицу. Об этом говорит восклицательный знак в условии.
Давайте сразу и воспользуемся данной функцией, соответственно сначала создав на неё прототип, а затем вызвав в функции main(). Сначала мы, конечно, будем во внешнюю память EEPROM писать, так как читать из неё ещё нечего. Поэтому начнём в main() писать следующий код
I2C_Init();
//Чтение
I2C_StartCondition(); //Отправим условие START
Ну и, раз уж у нас есть чем, то давайте считаем статус операции и посмотрим успешно ли всё у нас прошло.
Для этого мы просто отправим значение статусного регистра в шину USART. Младшие три бита мы маскировать не будем, они у нас и так все в нулях.
I2C_StartCondition(); //Отправим условие START
USART_Transmit(TWSR);//читаем статусный регистр
Запустим терминальную программу, нажмем там Connect, соберём код и прошьём контроллер.
И вот мы что там видим
Посмотрим в таблице данный статус
То есть условие СТАРТ у нас сгенерировано и отправлено.
Остальные эксперементы с шиной I2C мы будем проделывать в следующей части.
Предыдущая часть Программирование МК AVR Следующая часть
Техническая документация на микросхему AT24C32
Программатор и модуль RTC DS1307 с микросхемой памяти можно приобрести здесь:
Программатор (продавец надёжный) USBASP USBISP 2.0
Модуль RTC DS1307 с микросхемой памяти
Смотреть ВИДЕОУРОК (нажмите на картинку)
Согласно кода TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN); мы получаем TWCR = 10100100, а в строке while(!(TWCR & (1 << TWINT))); получается 10100100 & 10000000 даст результат 10000000, а это true и знак "!" даст false, что мы ничего ждать не будем, а вообще пропустим цикл. Получается лишний знак "!" или что то я тоже не так понимаю.
Здравствуйте! Писал код буква в букву, но при запуске в терминал ничего не выводит (USART работает корректно). Убираю цикл while(!(TWCR&(1<<TWINT))); и в терминал выводит F8. Не могу понять в чем проблема. Все вроде работает, но он почему то не выходит из этого цикла. Пробовал на разных мк (все atmega8) и с разными программаторами.
Разобрался благодаря вашему комментарию на ютубе. Нужно был подключить переферию)
TWWC – бит устанавливается при попытке записи в регистр данных TWDR, а не в адресный буфер.
См. фирменную документацию:
The TWWC bit is set when attempting to write to the TWI Data Register – TWDR when
TWINT is low.
И ещё, к стати! Там же — «при попыткЕ», а не «при попыткИ».
И ещё! Вот тут:
ЦИТАТА
Ну а бит TWINT мы соответственно устанавливаем в единицу. А в ноль он, соответственно установится тогда, когда данное задание закончится. Вот для этого и существует вторая строка, где мы висим в цикле, пока он, собственно, и не установится. Именно в ноль! А не в единицу.
КОНЕЦ ЦИТАТЫ
Смотрим документацию:
ЦИТАТА
This bit is set by hardware when the TWI has finished its current job…
The TWINT Flag must be cleared by software by writing a logic one to it.
КОНЕЦ ЦИТАТЫ
ПЕРЕВОД:
Этот бит устанавливается аппаратно, когда TWI закончит свою работу…
Флаг TWINT очищается программным спососом с помощью записи в него логической единицы.
КОНЦ ПЕРЕВОДА
Программный код правильный. Не правильное только его толкование.
Ну, как же Вы так!
А в случае если к мк подключено несколько устройств по I2C. Как определить что подключено определённое устройство? Например у меня три микросхемы памяти 24сХХХ и мне нужно сначала проверить подключена ли одна из них к I2C.