ESP8266 Урок 9. I2C. Подключаем внешний EEPROM



На данном уроке мы попробуем поработать с шиной I2c.

Как таковой аппаратной поддержки передачи по данной шине у контроллера ESP8266 нет, нет такого модуля, нет даже специальных регистров для этого.

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

Давайте и мы попытаемся что-то передать и принять, настроив такую шину.

Вообще с протоколом передачи данных по I2C мы знакомы давно. Мы работали с ним с использованием различных контроллеров — AVR, STM32, PIC. Причём работаем мы с ней до сих пор, так как многие устройства подключаются по данной шине.

Поэтому нет нам смысла повторно изучать, как именно работает интерфейс I2C, какого типа сигналы по каким проводам передаются, как подтверждаются пакеты, как остановить передачу, что такое адрес устройства и т.д.

В качестве устройства для практики по данному интерфейсу мы возьмём микросхему EEPROM — AT24C32, которая установлена в модуле с часовой микросхемой DS3231 и также в часовом модуле с микросхемой DS1307.

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

Выглядит модуль DS1307, с которым мы будем работать на данном уроке вот так

 

 

Теперь немного о программной поддержке I2C в ESP8266.

Если ничего не менять в функционале библиотеки SDK, то подключаем мы устройства по I2C с следующим ножкам

 

 

Подключим модуль к нашей отладочной плате, только провода для SCL и SDA возьмём с ещё одним отводом для подключения логического анализатора, чтобы потом наглядно увидеть, как будут передаваться данные по шине, а заодно и скорость их передачи. Подключим сразу и логический анализатор

 

 

А с функциями для работы с I2C библиотеки SDK мы будем знакомиться по ходу работы с проектом.

Проект мы создадим из проекта урока 7 с именем UART_TX и назовём его I2C_EEPROM.

Откроем наш проект в Eclipse и первым делом в user_init() удалим всю инициализацию GPIO2, так как он у нас занят в I2C

 

PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2);

gpio_output_set(0, 0, (1 << LED), 0);

 

Из бесконечного цикла удалим строки, где меняются уровни на ножке

 

gpio_output_set(0, (1 << LED), 0, 0);

gpio_output_set((1 << LED), 0, 0, 0);

 

Удалим также инкрементирование счётчика

 

i++;

if(i>9999) i=1;

 

И удалим вывод информации в терминальную программу

 

os_printf(«String %04d\r\n», i);

os_printf(«SDK version: %s\n», system_get_sdk_version());

os_printf(«Version info of boot: %d\n», system_get_boot_version());

os_printf(«Userbin address: 0x%x\n», system_get_userbin_addr());

os_printf(«Time = %ld\r\n», system_get_time());

os_printf(«RTC time = %ld\r\n», system_get_rtc_time());

os_printf(«Chip id = 0x%x\r\n», system_get_chip_id());

os_printf(«CPU freq = %d MHz\r\n», system_get_cpu_freq());

os_printf(«Flash size map = %d\r\n», system_get_flash_size_map());

os_printf(«Free heap size = %d\r\n», system_get_free_heap_size());

system_print_meminfo();

 

Чтобы использовать I2C, подключим соответствующий хедер-файл

 

 

Вызовем функцию инициализации I2C в user_init()

 

 

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

 

 

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

 

 

Добавим макросы для адреса устройства и для битов записи и чтения

 

 

 

Также добавим пустой массив на 20 элементов для сбора информации из микросхемы и инициализированный такого же размера для передачи информации в микросхему

 

 

Добавим функцию передачи серии байтов для записи их во внешний EEPROM с определённого адреса, где первый аргумент — адрес, второй — адрес буфера для передачи, третий — количество байтов, которые мы собираемся записать в память

 

 

Вызовем данную функцию в user_init()

 

 

В данной функции объявим переменную для итерации, переменную для проверки передачи адреса и вызовем функцию передачи в шину условия START

 

 

Затем передадим адрес устройства, чтобы оно, узнав себя, откликнулось

 

 

Затем проверим отклик и пошлём условие STOP, мы пока ничего кроме адреса не будем передавать в шину, чтобы проверить её работу

 

 

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

 

 

Отлично! Адрес передан, скорость 50 кГц.

Убедимся также что в UART у нас также не передалось ничего лишнего

 

 

Всё отлично.

Теперь передадим наши байты. Для этого передадим сначала адрес в шину, сначала его старший байт, затем младший, а затем поочерёдно отправим все наши байты

 

 

 

В user_init() также отправим данные байты в строковом выражении в терминальную программу

 

 

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

 

 

 

 

В терминальной программе тоже всё нормально

 

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

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

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

Дело в том, что память в микросхеме AT24 не плоская, а постраничная. В нашем экземпляре на 32 килобита (4 килобайта) она делится на 128 страниц по 32 байта

 

 

Писать непрерывно мы можем только в рамках одной страницы, Если посчитать, то можно определить, что страницы начинаются с адресов, кратных 32, то есть в шестнадцатеричном выражении они заканчиваются на 20, 40, 60, 80, A0, C0, E0.

Если мы пишем, например с адреса 0x015B, то мы не можем записать больше 5 байт, так как шестой байт уже будет приходиться на следующую страницу. Поэтому, если мы попытаемся записать 20 байт с данного адреса, то самое страшное в этом, что мы не получим никакой ошибки. А потом уже, когда попытаемся считать данные байты, то первые 5 байтов будут, какие мы записали, а остальные, которые оказались в другой странице, останутся неизменными. Кстати, чтение мы можем производить, невзирая на страницы, то есть мы можем сразу прочитать блок с адреса 0 длиной в 4096 байт.

Мы же выбрали адрес 0x054A, что вполне позволяет записать, начиная с него 20 байтов, так как следующая страница будет начинаться с адреса 0x0560, а если из данного адреса вычесть наш, то мы получим 22 байта, а у нас 20, так что всё нормально и всё влезет в одну страницу.

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

 

 

А оговорен он вот здесь

 

 

Теперь попробуем прочитать наши байты.

Добавим ниже функции AT24C_WriteBytes функцию чтения серии байтов из микросхемы с подобными параметрами, в которой мы также объявим такие же локальные переменные, также пока передадим условие START, адрес устройства и условие STOP

 

 

В user_init() закомментируем пока вызов функции записи байтов и вызовем функцию чтения

 

 

Закомментируем также отправку байтов в UART, а отправим теперь такие же байты из буфера чтения

 

 

Продолжим писать тело функции AT24C_ReadBytes и также передадим там в шину адрес памяти, с которого мы будем читать байты

 

 

Передадим ещё одно условие START, такой порядок чтения памяти по I2C

 

 

Теперь передадим адрес устройства с битом чтения, так как сейчас мы начинаем оттуда читать

 

 

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

 

 

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

 

 

 

 

 

Всё отлично принялось!

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

Всем спасибо за внимание!

 

 

 

Предыдущий урок Программирование МК ESP8266 Следующий урок

 

Исходный код

 

 

Модуль ESP NodeMCU можно купить здесь: Модуль ESP NodeMCU

Различные модули ЕSP8266 можно приобрести здесь Модули ЕSP8266

Переходник USB to TTL можно приобрести здесь ftdi ft232rl

Многофункциональный переходник CJMCU FT232H USB к JTAG UART FIFO SPI I2C можно приобрести здесь ftdi ft232rl

Логический анализатор 16 каналов можно приобрести здесь

 

 

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

ESP8266 I2C. Подключаем внешний EEPROM

10 комментариев на “ESP8266 Урок 9. I2C. Подключаем внешний EEPROM
  1. Andry03:

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

  2. Алекс:

    А где же автор?

  3. max:

    уже переживаем, дайте хоть сигнал ка вы

  4. Очень полезно. Написал небольшую библиотечку для работы с часами реального времени DS3231: задать время, считать время в символьный буфер для дальнейшего вывода на LCD или в консоль.

  5. Serj82:

    Куда и кому можно задать вопросы по ESP8266? На форуме нет раздела по еспэшке?

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

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

*