Урок 16
Часть 4
Интерфейс TWI (I2C)
В предыдущей части занятия мы поближе познакомились со всеми практически регистрами шины TWI и уже написали код отправки в шину условия СТАРТ. Причем не только написали, а ещё и проверили отправку, считав данные из регистра статуса через порт USART. Также мы бегло ознакомились с протоколом передачи и приема. Если нам впоследствии что-то потребуется, то мы к нему ещё вернёмся.
Будем теперь думать, как нам ещё что-нибудь в шину I2C отправить. Для этого будут ещё нужны некоторые функции.
Раз уж есть функция для условия СТАРТ, то, конечно же, нужна функция для передачи условия СТОП.
Поэтому зайдём в файл twi.c и напишем её, скопировав полностью предыдущую функцию и немного её подправив
void I2C_StopCondition(void)
{
TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);
}
Здесь только изменился бит TWSTA на TWSTO, а также в этом случае мы уже никакого подтверждения от ведомого не ждём.
Также нам нужно написать функцию для передачи байта в шину I2C
void I2C_SendByte(unsigned char c)
{
TWDR = c;//запишем байт в регистр данных
TWCR = (1<<TWINT)|(1<<TWEN);//включим передачу байта
while (!(TWCR & (1<<TWINT)));//подождем пока установится TWIN
}
Здесь мы сначала записываем байт, предназначенный для отправки, в регистр TWDR, затем запустим передачу байта, установив биты TWINT и TWEN и здесь мы уже ждём подтверждения от ведомого устройства. То есть, если мы передаем адрес устройства, то если среди всех присутствующих на шине найдётся именно владелец данного адреса, то он и установит шину в низкое состояние, после чего контроллер, поняв это, установит бит TWINT в ноль, ну, или сбросит его.
Также напоминаю о необходимости добавления прототипов на данные функции в файле twi.h.
Теперь продолжим, используч вышенаписанные функции, писать код в функцию main(), чтобы нам удостовериться, что у нас шина I2C исправна и устройство, подключенное к ней, нормально откликается.
Также для достижения данной цели нам неохдодимо изучить, как именно нужно передавать данные нашей микросхеме EEPROM. Для этого зайдём в техническую документацию и посмотрим следующую картинку
Здесь рассказано, как передать один байт для записи в определённый адрес памяти микросхемы. Сначала мы передаём адрес и бит записи, затем подтверждение, затем старший байт адреса памяти, подтверждение, младший бит адреса памяти. подтверждение, байт данных для записи, подтверждение и СТОП.
Только нам вовсе не интересно в данную память передавать по одному байту данных, поэтому существует ещё одна картинка в даташите
Здесь мы в начале наблюдаем ту же картину. Старт, адрес устройства с битом записи, подтверждение, старший байт адреса памяти, с которого начинаем запись байтов, подтверждение, младший байт адреса памяти, подтверждение, и затем идут подряд байты с подтверждениями, которые будут укладываться в ячейки памяти EEPROM, начиная с переданного адреса до тех пор, пока мы на шине не сгенерируем условие СТОП.
Ну, давайте же что-то попытаемся записать в микросхему. Также после каждой операции будем смотреть статус её выполнения. Напишем следующий код в функцию main()
I2C_StartCondition(); //Отправим условие START
USART_Transmit(TWSR); //читаем статусный регистр
I2C_SendByte(0b10100000); //передаем адрес и бит записи (0)
USART_Transmit(TWSR); //читаем статусный регистр
I2C_SendByte(0);//переходим на 0x0000 — старший байт адреса памяти
USART_Transmit(TWSR); //читаем статусный регистр
I2C_SendByte(0); // — младший байт адреса памяти
USART_Transmit(TWSR); //читаем статусный регистр
I2C_StopCondition(); //Отправим условие STOP
USART_Transmit(TWSR); //читаем статусный регистр
while(1)
Чем занимается данный код, прекрасно видно из комментариев.
Соберём код и прошьём контроллер.
Глянем в терминальную программу
И теперь с помощью таблицы будем разбираться, что же нам «рассказал» регистр TWSR
Вот коды, которые мы получали
0x08 — условие СТАРТ было передано
0x18 — адрес и бит записи был передан и подтверждение получено
0x28 — байт данных был передан и подтверждение получено
Адрес ячейки памяти для нашего контроллера таковым не считается, это для него обычные данные.
А 0xF8 — это типа ошибки. Ну нам уже это не важно. Скорей всего это потому, что после СТОП ведомый уже перед нами не отчитывается и флаг также не сбросится.
В следующей части занятия мы постараемся уже что-то в память микросхемы записать.
Предыдущая часть Программирование МК AVR Следующая часть
Техническая документация на микросхему AT24C32
Программатор и модуль RTC DS1307 с микросхемой памяти можно приобрести здесь:
Программатор USBASP USBISP с адаптером USBASP USBISP 3.3 с адаптером
Модуль RTC DS1307 с микросхемой памяти
Смотреть ВИДЕОУРОК (нажмите на картинку)
А почему мы не используем прерывания? Ведь если у нас ведомый в процессе приёма отвалится, то мы навечно подвесим МК в этом цикле:
while (!(TWCR & (1<<TWINT)));
Но даже если ведомый примет посылку, получается, что всё время передачи, несмотря на наличие аппаратного TWI наш микроконтроллер будет работать с частотой 100 кГц.
Или я чего-то не понимаю?
Потому что во время выхода данного урока я ещё не знал, как их использовать.
Сделал все как указано, но у меня вместо 18 и 28 получаются соответственно 20 и 30, т.е. у меня отправляется не сигнал ACK, а сигнал NOT ACK. Что я делаю не так?
Флаг должен быть правильный установлен. Посмотрите урок по внешнему EEPROM, там мы используем и первый и другой вариант (и с подтверждением и без).
Спасибо.
Ваш коментарий мне помог.
Добавление к предыдущему вопросу. Модуль спаял сам, с ардуинкой работаем без проблем — время показывает. Но сигнал возвращается именно NO ACK.
Нашел проблему. Если NO ACK указывает, что по данному адресу устройство не обнаружено. Запустил на ардуинке i2c_scaner, который показал адрес ds1307 есть 0х68. перевел в двоичное — 1101000, добавил 0 как флак записи. Т.е. получилось I2C_SendByte(0b11010000); После этого получил долгожданные 18 и 28.
Отлично!
Спасибо.
Но разве мы пишем в часовую микросхему??? Это у часовой 0х68, а у микрухи памяти на этом блоке сканер показывает 0х50
while (!(TWCR & (1<<TWINT)));получается что ожидания флага можно не делать?
while (!(TWCR & (1<<TWINT))) означает, что: если бит TWINT регистра TWCR скинется в 0, то выражение (TWCR & (1<<TWINT)) будет 0, а знак ! даст 1. Цикл while же означает, что пока условие истинно, цикл исполняется, то есть, мы повисаем в этом цикле, а выходим из него только когда бит TWINT устанавливается в 1, а не в 0, как сказано в уроках!!!