Теплица с восемью зонами полива съела весь килобайт за один вечер планирования. На каждую зону — день недели, час старта и минуты: три байта на зону, двадцать четыре на все. Плюс калибровки четырёх датчиков почвы, плюс счётчик поливов, плюс журнал событий, который я честно собирался вести. Складываю — и на Uno уже не хватает: её EEPROM ровно 1024 байта, и это вся энергонезависимая память внутри чипа. Расширять список желаний было некуда.
Выход нашёлся на той же полке, где лежали часы реального времени: чип AT24C256 — 32768 байт памяти, которые живут на тех же двух проводах I2C, что и часы. Тридцать два килобайта против одного — рост в тридцать два раза, цена вопроса — пара долларов за готовый модуль. И главное: провода уже протянуты, SDA сидит на A4, SCL на A5, — модуль памяти я просто воткнул параллельно часам, и шина приняла третьего жильца без единого нового провода.
В этом уроке подключим 24C256 к Arduino: разберём адресацию чипа через пины A0-A2, научимся писать и читать байт по любому из 32768 адресов и посчитаем, что реально влезает в 32 КБ. Ловушек здесь две, и обе собирают свою дань с каждого новичка: двухбайтовый адрес ячейки и пауза после записи — пройдём их медленно.
Сразу честно про симулятор: сам чип 24C256 движок не эмулирует — Wire-лог в нём есть, а писать в чип он не умеет. Поэтому сим-блок показывает логику честной моделью: массив байтов в скетче играет роль внешней памяти, функции myWrite и myRead — роль драйвера. Все числа и границы настоящие.
Два провода, восемь адресов, один чип
Как ведёт себя шина — мы уже разбирали в уроке про I2C: мастер Arduino выставляет на шину адрес устройства, устройства откликаются, дальше идут данные. У 24C256 адрес складывается из фиксированной части и трёх пинов настройки: A0, A1, A2. Базовый адрес чипа — 0x50; все три пина на земле дают ровно его, а подтяжка каждого пина к питанию добавляет единицу в свой разряд. Диапазон получается 0x50…0x57 — до восьми чипов на одной шине, и это уже 256 килобайт памяти на двух проводах, если вам когда-нибудь захочется именно этого.
Модули с рынка почти всегда приходят с распаянными подтяжками 4,7–10 кОм на SDA и SCL и адресными пинами на земле — то есть с адресом 0x50, крутить ничего не надо. Но правило «доверяй, потом проверяй» здесь работает в обратную сторону: сначала проверяй. Сканер I2C-адресов покажет, по какому адресу чип реально откликается, ещё до первого скетча записи. Сканер молчит — ищите обрыв, перепутанные SDA и SCL или мёртвый модуль, а не баг в коде.
Питание — 5 вольт от той же платы, ток у чипа копеечный: пара миллиампер в покое и около трёх в пике записи, так что отдельный стабилизатор не нужен. На плате чипа есть ещё пин WP — write protect. На земле он ничего не делает; подтянутый к питанию, переводит чип в режим только-чтение. На дешёвых модулях WP выведен отдельным контактом разъёма, и если память отказывается запоминать, смотрите первым делом на него, а уже потом в свой скетч.
#include <Wire.h> const byte CHIP = 0x50; // A0-A2 на земле - адрес 0x50 // запись одного байта по адресу 0..32767void eeWrite(int addr, byte val) { Wire.beginTransmission(CHIP); Wire.write(addr / 256); // старший байт адреса ячейки Wire.write(addr % 256); // младший Wire.write(val); // данные Wire.endTransmission(); delay(5); // внутренний цикл записи чипа} // чтение: сначала ставим внутренний указатель, потом читаемbyte eeRead(int addr) { Wire.beginTransmission(CHIP); Wire.write(addr / 256); Wire.write(addr % 256); Wire.endTransmission(); // данных нет - только установка указателя if (Wire.requestFrom(CHIP, 1) == 1) { return Wire.read(); } return 0; // чип не ответил} void setup() { Serial.begin(9600); Wire.begin(); eeWrite(100, 77); byte val = eeRead(100); Serial.print("addr=100 val="); Serial.println(val);} void loop() { delay(100);}Главная разница против внутреннего EEPROM прячется в адресе ячейки. У чипа 32768 ячеек, и в один байт такой адрес не влезает: после адреса устройства мастер передаёт два байта адреса памяти — старший, addr / 256, и младший, addr % 256. Это та же раскладка числа на байты, что мы отрабатывали в уроке про счётчик включений, только теперь она про адрес, а не про значение. Перепутанные местами байты — любимый способ записать данные не туда: чип честно отработает тот адрес, который вы ему скормили.
Вторая строка, на которой спотыкаются все: delay(5) после каждой записи. Получив последний байт, чип не пишет мгновенно — содержимое внутреннего буфера переписывается в ячейки, и по даташиту на это уходит до 5 мс (write cycle time). Всё это время чип занят и на обращения по шине не отвечает: новая запись или чтение просто не пройдут. delay(5) — решение грубое, но железобетонное; если паузы жалко, существует ACK-опрос, когда мастер шлёт обращения, пока чип снова не начнёт подтверждать свой адрес, — вернёмся к нему в следующем уроке.
Чтение устроено в два хода и зеркалит запись. Сначала beginTransmission и два байта адреса без данных — мы только переставляем внутренний указатель чипа. Потом requestFrom — чип отдаёт байт. Для одиночного байта выглядит громоздко, но у механизма есть бонус: после чтения указатель сам сдвигается вперёд, и последовательное чтение всего чипа идёт одним потоком. Первый байт из энергонезависимой памяти мы уже доставали в уроке про read и write — там во внутреннем EEPROM, здесь механика та же, добавились только два байта адреса и пауза на запись.
| EEPROM внутри ATmega328P | Внешний 24C256 по I2C | |
|---|---|---|
| Объём | 1024 байта на Uno и Nano | 32768 байта = 32 КБ |
| Доступ | EEPROM.read / write / update | Wire: адрес чипа плюс два байта адреса ячейки |
| Пауза после записи | около 3,3 мс на каждый байт | до 5 мс на пакет до 64 байт |
| Ресурс ячейки | не менее 100000 циклов | порядка 1000000 циклов по даташиту |
| Занимает пины | нет | A4 и A5 — общая шина со всеми I2C-устройствами |
Сколько там чего влезает
Считаем ёмкость на живых цифрах. 32768 байт — это 512 страниц по 64 байта (что такое страница и почему она важна — следующий урок) или, если ближе блоки настроек, тридцать два ящика размером с весь внутренний EEPROM Uno. Моя раскладка теплицы вышла такой: 64 байта на блок настроек со всеми калибровками, 4 байта на запись журнала — отметка времени и код события. При журнале раз в десять минут 8192 записей заполняют отведённые им 32 КБ почти за два месяца непрерывного потока, и всё это — параллельно с основным конфигом, не поцарапав его.
Что не переедет само: EEPROM.put и EEPROM.get из урока про структуры работают только с внутренней памятью. Для внешнего чипа раскладку структуры на байты придётся делать руками — приёмы из урока про int и float ложатся один в один, — либо взять библиотеку вроде I2C_EEPROM, которая оборачивает Wire в привычные read, write и update. Магии в библиотеке нет: те же два байта адреса и та же пауза, просто в аккуратной обёртке с проверками.
Моё правило выбора с условиями. Пока блок настроек помещается в четыре ячейки-килобайта, журналов нет, а пины A4 и A5 свободны — внутренняя память: ноль лишних деталей. Появились журналы, расписания, таблицы калибровок на несколько датчиков — внешний чип с адреса 0x50. А если на шине уже живут часы, экран и датчики, помните: шина одна на всех, и пока вы читаете большой кусок из памяти, остальные ждут. Честный компромисс — читать кусками по 32 байта с паузами и отдавать шину между кусками.
- 24C256 — 32768 байт на I2C: базовый адрес 0x50, пины A0-A2 дают восемь адресов 0x50…0x57 — до восьми чипов на шине
- Адрес ячейки передаётся двумя байтами: старший addr / 256, младший addr % 256; перепутанный порядок пишет данные не туда
- После каждой записи чип занят до 5 мс (write cycle time): delay(5) или ACK-опрос, иначе следующее обращение не пройдёт
- Чтение в два хода: установка указателя, потом requestFrom; указатель двигается сам — последовательное чтение идёт потоком
- WP на земле даёт запись; если чип виден сканером, но не запоминает — WP к питанию или теневой адрес у дефектной партии
- EEPROM.put/get на внешний чип не действуют: раскладка структуры — руками или через библиотеку вроде I2C_EEPROM
- 1. По какому адресу откликнется 24C256, если все три пина A0-A2 соединить с землёй?
- 2. Почему адрес ячейки передаётся двумя байтами?
- 3. Что будет, если убрать delay(5) после записи и сразу прочитать тот же байт?
- 4. Какая последовательность верна для записи одного байта в 24C256?
- 5. Пины A4 и A5 на Uno заняты часами DS3231. Куда подключить SDA и SCL модуля 24C256?
Подключение есть, байт ходит туда и обратно. Но умножьте задачу: журнал теплицы пишет блоками по несколько десятков байт, и если слать их по одному, каждый байт потянет за собой собственную паузу до 5 мс — страница в 64 байта займёт треть секунды. А ещё внутри чипа прячется заворот адреса, который молча затирает свежезаписанные данные старыми. Как устроена страничная запись, почему она в шестьдесят четыре раза быстрее побайтовой и где именно на ней ломают данные — следующий урок.
Частые вопросы по теме урока
- По какому адресу откликнется 24C256, если все три пина A0-A2 соединить с землёй?
- Базовый адрес чипа — 0x50, а пины A0-A2 прибавляют единицы в младшие разряды. Земля на всех трёх — ноль прибавок, итого 0x50. 0x50
- Почему адрес ячейки передаётся двумя байтами?
- Диапазон адресов 0…32767 шире одного байта: старший байт — addr / 256, младший — addr % 256. Отсюда и две записи Wire.write перед данными. 32768 ячеек не кодируются одним байтом
- Что будет, если убрать delay(5) после записи и сразу прочитать тот же байт?
- До 5 мс после команды записи чип переписывает буфер в ячейки и не отвечает на шине. Чтение в это время вернёт мусор или не состоится. Придёт старое значение, либо чип не ответит: он ещё занят внутренним циклом записи
- Какая последовательность верна для записи одного байта в 24C256?
- Адрес устройства, два байта адреса ячейки, затем данные — и только потом endTransmission, который запускает цикл записи. beginTransmission, старший байт адреса, младший, данные, endTransmission
- Пины A4 и A5 на Uno заняты часами DS3231. Куда подключить SDA и SCL модуля 24C256?
- I2C — шина: все устройства висят на одних SDA и SCL и различаются адресами. Часы 0x68, память 0x50 — не мешают друг другу. На те же A4 и A5, параллельно часам