AVR Урок 16. Интерфейс TWI (I2C). Часть 6



 

Урок 16

Часть 6

 

Интерфейс TWI (I2C)

 

В предыдущей части занятия уже продолжили свою работу с отправкой значений в память микросхемы EEPROM по интерфейсу TWI. Только проверить мы смогли это, исключительно веря статусам.

А сегодня мы уже это проверим точно, считав данные из микросхемы. Соответственно здесь всё будет не совсем просто. Функция и вообще процедура чтения данных по шине I2C не совсем простая, есть свои нюанся. Но, думаю, используя наш предыдущий опыт, мы обязательно с ней справимся.

Откроем следующую таблицу в технической документации на микросхему

 

image22

 

Это диаграмма считывания из определённой ячейки памяти одного байта.

Она чем-то похожа на процедуру записи, но не совсем это так.

Сначала мы также создаём условие СТАРТ на шине, затем передаём адрес с битом записи, именно записи. Тут вопрос, почему записи. Потому, что мы должны отправить адрес устройства и адрес ячейки памяти. из которой мы потом и заберём байт. Затем подтверждение, затем старшая часть адреса ячейки памяти, подтверждение, младшая часть ячейки памяти, подтверждение.

Затем, как ни странно, опять условие СТАРТ. Именно так. Затем уже передаём адрес устройства с битом чтения (1), подтверждение, затем после подтверждения читаем данные из регистра данных, а затем уже подтвержденя не ждём, то есть генерируем бит без подтверждения, то есть шина данных становится в логический 1, а затем условие СТОП. Вот так.

Так что, как видите, здесь не совсем всё так просто.

И это всё при условии, что надо нам считать только один байт. Это удобно, если мы тестируем память на случайное чтение, постоянно читая какой-то случайный бит, поэтому и диаграмма названа Random Read.

Но в большинстве случаев нам требуется сразу считать несколько байт, расположенных последовательно в ячейках памяти. Пример из жизни: данный тип микросхем очень широко применяется в спутниковых рессиверах и T2-приёмниках и у нас, например глюканула эта микросхема, что происходит отнюдь не редко. А в памяти данной микросхемы находятся все практически настройки. Мы берем живую микросхему из другого такого же устройства и считываем оттуда прошивку. Вот такая процедура тут уже и пригодится. Затем уже мы эту считанную прошивку либо загружаем в микросхему со сбойными данными, либо в новую, на которую сбойную потом меняем.

Для такой процедуры существует другая диаграмма в даташите (нажмите на картинку для увеличения размера)

 

image23_0500

 

Данная диаграмма нам показывает, как нужно читать байты целыми блоками.

Начало не отличается от предыдущей процедуры, а затем мы начинаем считывать каждый байт, после каждого считанного байта ждём подтверждение, а вот после последнего уже не ждём, то есть генерируем условие NO ASK. Как это проделать, разберёмся по мере написания функций.

Пока пользуемся уже существующими функциями, так как всё начинается с процедуры записи в шину. В функции main() закомментируем весь код процедуры записи серии байтов, и начнем писать процедуру чтения из шины

Скопируем в данную процедуру то, что будет вообще без изменения

 

//Чтение

I2C_StartCondition(); //Отправим условие START

USART_Transmit(TWSR);//читаем статусный регистр

I2C_SendByte(0b10100000);//передаем адрес устройства и бит записи (0)

USART_Transmit(TWSR);//читаем статусный регистр

I2C_SendByte(0);//передаем старшую часть адреса ячейки памяти

USART_Transmit(TWSR);//читаем статусный регистр

I2C_SendByte(0);//передаем младшую часть адреса ячейки памяти

USART_Transmit(TWSR);//читаем статусный регистр

 

После передачи младшего байта адреса памяти мы генерируем условие СТАРТ

 

I2C_SendByte(0);//передаем младшую часть адреса ячейки памяти

USART_Transmit(TWSR);//читаем статусный регистр

I2C_StartCondition(); //Отправим условие START

USART_Transmit(TWSR);//читаем статусный регистр

 

Затем ещё раз передаём адрес устройства, но уже с установленным битом чтения (1)

 

I2C_StartCondition(); //Отправим условие START

USART_Transmit(TWSR);//читаем статусный регистр

I2C_SendByte(0b10100001);//передаем адрес устройства и бит чтения (1)

USART_Transmit(TWSR);//читаем статусный регистр

 

Далее мы должны будем уже читать байты из шины. Только, к сожалению, у нас для этого пока нет функции.

 

 

Но не будем этого бояться и напишем данную функцию, так как она будет подобна функции, применяемой для записи, за некоторым отличием. Напишем данную функцию в файле eepromext.c.

Сначала в данном файле создим глобальную переменную для статуса ошибки, так как вернуть нам надо будет байт, считанный из шины, а возвращаемых аргументов может быть только один, и если что-то не так пойдёт. то мы подключим в главном файле программы данную переменную и там её считаем. Но, надеюсь, что всё пойдёт нормально, и она нам просто не потребуется

 

#include «eepromext.h»

char err1=0;// сюда вернем ошибку

 

Теперь непосредственно сама функция чтения

 

unsigned char EE_ReadByte(void)

{

  err1=0;

  TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWEA);//включим прием данных

  while(!(TWCR & (1<<TWINT)));//подождем пока установится TWIN

  if ((TWSR & 0xF8) != TW_MR_DATA_ASK) err1=1;

  else err1=0;

  return TWDR;

}

 

Главное отличие данной функции от функции чтения состоит в том, что мы теперь не заносим никаких данных в регистр TWDR, а, наоборот возвращаем считанное значение из него в качестве возвращаемого аргумента функции.

В управляющем регистре мы практически включаем те же биты, и плюс бит, разрешающий подтверждение от ведомого.

Также затем мы ждём установки флага в 0 по окончанию заполнения регистра TWDR, затем проверяем статус и возвращаем значение регистра данных TWDR.

Вот, в принципе и вся функция. Но данная функция нам подходит для чтения не всех байтов. Байт последний, как мы знаем читается по-особому, поэтому для него будет похожая, но несколько другая функция

 

unsigned char EE_ReadLastByte(void)

{

  TWCR = (1<<TWINT)|(1<<TWEN);//включим прием данных

  while(!(TWCR&(1<<TWINT)));//подождем пока установится TWIN

  if ((TWSR & 0xF8) != TW_MR_DATA_NASK) err1=1;

  else err1=0;

  return TWDR;

}

 

Здесь практически всё один в один, за исключением лишь того, что мы не включаем бит TWEA, разрешающий подтверждение от ведомого, а также статус отслеживаем тоже другой — 0x58.

 

 

Ну вот. Соответственно, на все наши функции должны быть прототипы в файле eepromext.h

 

int EE_WriteByte(unsigned char c);

unsigned char EE_ReadByte(void);

unsigned char EE_ReadLastByte(void);

#endif /* EEPROMEXT_H_ */

 

Идём теперь в функцию main() и продолжим код.

Мы также здесь можем использовать цикл, но в нём мы считаем только 31 байт, так как 32-й мы считываем другой функцией

 

I2C_SendByte(0b10101111);//передаем адрес устройства и бит чтения (1)

USART_Transmit(TWSR);//читаем статусный регистр

for(i=0;i<=30;i++)

{

  bt[i] = EE_ReadByte(); //прочитаем байт из микросхемы

  USART_Transmit(TWSR);//читаем статусный регистр

}

 

Также мы будем сначала смотреть статусы операций, а не сами считанные байты, которые мы, как видим, складываем в наш массив.

Считаем последний байт, используя соответствующую функцию

 

  bt[i] = EE_ReadByte(); //прочитаем байт из микросхемы

  USART_Transmit(TWSR);//читаем статусный регистр

}

bt[31] = EE_ReadLastByte(); //прочитаем байт из микросхемы

USART_Transmit(TWSR);//читаем статусный регистр

 

Ну и теперь сгенерируем условие СТОП

 

bt[31] = EE_ReadLastByte(); //прочитаем байт из микросхемы

USART_Transmit(TWSR);//читаем статусный регистр

I2C_StopCondition(); //Отправим условие STOP

USART_Transmit(TWSR);//читаем статусный регистр

 

Давайте пока не будем смотреть принятые байты, а, запустив терминальную програму, запустив в ней соединение, собрав код и прошив контроллер, посмотрим статусы

 

image24

 

Давайте найдём все новые статусы в таблице. 0x08, 0x18, 0x28 и 0xF8 мы уже знаем из прошлой части занятия. Осталось нам разобраться со статусами 0x10, 0x40, 0x50 и 0x58.

Для этого соберём их из таблицы даташита нашего контроллера

 

image25

 

0x10 — Повторное условие СТАРТ передано

0x40 — Адрес ведомого устойства плюс бит чтения передан и подтверддён ведущим

0x50 — Байт данных из ведущего был принят, подтверждение возвращено

0x58 — Байт данных из ведущего был принят, возвращено «нет подтверждения».

Как видим, всё у нас правильно, всё соответстсвует техничегской документации. Осталось нам только посмотреть принятые байты

Для этого ещё в одном цикле уже из массива, в который они считаны из шины, отправим их в USART, перед этим закомментировав в момент приёма отправку статусов в данную шину

 

for(i=0;i<=30;i++)

{

  bt[i] = EE_ReadByte(); //прочитаем байт из микросхемы

  //USART_Transmit(TWSR);//читаем статусный регистр

}

bt[31] = EE_ReadLastByte(); //прочитаем байт из микросхемы

//USART_Transmit(TWSR);//читаем статусный регистр

I2C_StopCondition(); //Отправим условие STOP

USART_Transmit(TWSR);//читаем статусный регистр

for(i=0;i<=31;i++)

{

  USART_Transmit(bt[i]); //отправим считанные байты в ПК

}

 

Соберём код, прошьём контроллер и прочитаем результат чтения в терминале

 

image26

 

Мы видим здесь все наши байты, то есть процедура чтения также прошла удачно, а также она показала и удачность процедуры записи.

Таким образом. мы с Вами уже как следует изучили специфику шины I2C, организацию её в контроллерах AVR, а также научились её программировать. Также мы познакомились с микросхемой EEPROM, подключили её по данной шине и смогли записать в неё байты и считать.

В следующей части мы закрепим полученные знания и подключим по шине I2C переходник для модуля символьного дисплея, тем самым также получим возможность подключать дисплей по двум проводам.

 

 

Предыдущая часть Программирование МК AVR Следующая часть

 

Исходный код

 

Техническая документация на микросхему AT24C32

 

Программатор и модуль RTC DS1307 с микросхемой памяти можно приобрести здесь:

Программатор (продавец надёжный) USBASP USBISP 2.0

Модуль RTC DS1307 с микросхемой памяти

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

AVR Интерфейс TWI (I2C)

12 комментариев на “AVR Урок 16. Интерфейс TWI (I2C). Часть 6
  1. Павел:

    вот в результате чтения не вижу 3D, идут 3B 3C потом 00 и далее 3F… потери?

  2. Иван:

    У вас тут опечатка в битах I2C_SendByte(0b10101111);//передаем адрес устройства и бит чтения (1). В видео I2C_SendByte(0b10100001);

  3. А каким образом передаётся адрес старшего байта и адрес младшего байта если он не записан?

  4. И в какую ячейку будет записана информация если нет адреса?

  5. Iran:

    Здравствуйте! Стало интересно, как еепромка «узнает», какой байт считывания должен стать последним?! Я пробую прочитать всю страницу памяти (32байта), в результате читается только первые 28… Байты начина с адреса 0х1с(28) можно считать, если начало чтение установить с этого адреса! Что за магия?

  6. Iran:

    О… Дайте мне яду! Если в Вашем исходнике, в массиве записать числа от 0 до 31, то результат будет такой, как я писал выше! Кстати, исправьте массив с индексом 14(два раза повторяется).

  7. Iran:

    Второй вопрос, в уроке допущена ошибка в массиве. Почему в результате вывода в терминал вывелся ноль? По идее, массив с индексом 13, должен был проигнорится

  8. Iran:

    Затык после байта 0х1В происходит, после него чтение прекращается…

  9. Iran:

    Может кому сэкономит время, во всем виноват этот лукавый терминал, который байт 1b воспринимает как комманду esc!!!!

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*