Пришла мне посылка из Китая в которой лежит микросхема EEPROM фирмы Atmel. Которую хочется подключить к Arduino. Но совершенно не хочется использовать готовую библиотека, а разобраться самому. По этому статья получиться немного объемной и скучной и разделем ее на три части :
- Теория интерфейса I2C.
- EEPROM, описания моей микросхемы(AT24C256) и подключение.
- Написание библиотеки для работы с памятью.
Часть первая, I2C и библиотека «Wire».
Последовательный протокол обмена данными IIC (также называемый I2C — Inter-Integrated Circuits, межмикросхемное соединение). Разработана фирмой Philips Semiconductors в начале 1980-х как простая 8-битная шина внутренней связи для создания управляющей электроники. Так как право на использование его стоит денег фарма Atmel назвала его TWI, но смысл от этого не меняется.
Как это работает ?
Для передачи данных используются две двунаправленные лини передачи данных. SDA (Serial Data) шина последовательных данных и SCL (Serial Clock) шина тактирования. Обе шины подтянуты резисторами к плюсовой шине питания. Передача/Прием сигналов осуществляется прижиманием линии в 0, в единичку устанавливается сама, за счет подтягивающих резисторов.
В сети есть хотя бы одно ведущее устройство (Master), которое инициализирует передачу данных и генерирует сигналы синхронизации и ведомые устройства (Slave), которые передают данные по запросу ведущего. У каждого ведомого устройства есть уникальный адрес, по которому ведущий и обращается к нему. Конечно понятно что Ведущий это наш микроконтроллер , а ведомый наша память. Ведущее устройство начинает прижимать шину SCL к нулю с определенной частотой, а шину SDA прижимать или отпускать на определенное число тактов передавая Единичку или Нолик. Передача данных начинается с сигнала START потом передается 8 бит данных и 9-тым битом Ведомое устройство подтверждает прием байт прижимая шину SDA к минусу. Заканчивается передача сигналом STOP.
Библиотека «Wire».
Для облегчения обмена данными с устройствами по шине I2C для Arduino написана стандартная библиотека Wire которая есть уже в комплекте IDE. Она имеет следующие основные функции:
Wire.begin(Address) вызывается один раз для инициализации и подключения к шини как Ведущий или Ведомое устройство. Если Address не задан подключаемся как Мастер устройство.
Wire.beginTransmission(address) начинает передачу на ведомое I2C устройство с заданным адресом.
Wire.endTransmission() прекращает передачу данных ведомому. Функция возвращает значение типа byte:
- 0 — успех.
- 1- данные слишком длинны для заполнения буфера передачи.
- 2 — принят NACK при передаче адреса.
- 3 — принят NACK при передаче данных.
- 4 — остальные ошибки.
Wire.write() запись данных от ведомого устройства в отклик на запрос от ведущего устройства, или ставит в очередь байты для передачи от мастера к ведомому устройству.Фактически записывает данные в буфер. Размер буфера 32 байта ( минус 2 байта адрес, фактически 30 байт), а передает буфер функция Wire.endTransmission().
- Wire.write(value) — value: значение для передачи, один байт.
- Wire.write(string) — string: строка для передачи, последовательность байтов.
- Wire.write(data, length) — data: массив данных для передачи, байты. length: количество байтов для передачи.
Wire.read() Считывает байт, который был передан от ведомого устройства к ведущему или который был передан от ведущего устройства к ведомому. Возвращаемое значение byte : очередной принятый байт.
Это самые основные функции библиотеке, остальные мы рассмотрим по ходу пьесы ))
Часть вторая, EEPROM.
EEPROM (англ. Electrically Erasable Programmable Read-Only Memory) — электрически стираемое перепрограммируемое ПЗУ (ЭСППЗУ), один из видов энергонезависимой памяти (таких, как PROM и EPROM). Память такого типа может стираться и заполняться данными до миллиона раз.
Мне прислали уже готовый модуль EEPROM с микросхемой AT24C256 фирмы Atmel объемом 32 кбайт. Что бы разобраться с этим чудом нам придется проштудировать datasheet который очень скучный и на английском. Так что я вам выдам уже готовый результат моих мук.
Характеристики :
- Низковольтные и стандартные питание. VCC = 1.7V to 5.5V.
- 400kHz (1.7V) and 1MHz (2.5V, 2.7V, 5.0V) совместим с частотой синхронизации.
- Выносливость: 1,000,000 Циклов Записи.
- Внутренне организованный как 32 768 страниц x 8 бит.
выводы :
- WP — защита от записи. Если вывод подключен к GND то можно записывать данные в память.
- A0…A2 — выводы задающие адрес устройства.
- Vcc — питание плюс.
- GND — питание минус.
Адрес памяти:
Задается тремя ногами A0..A2. Если нога прижата к Gnd то значение бита 0, если к Vcc то 1. Микросхема использует восьми битный адрес, последний бит отвечает за выбор операции. Если значение бита высокий то инициализируется операция чтения, если низкий(ноль) то операция записи.
То есть если все три вывода прижаты к GND и мы хотим записать в память, адрес устройства будет выглядеть как 10100000 (в библиотеке «Wire» используется 7-ми битный адрес, сдвигаем все в право на один бит 01010000 0x50).
Запись данных в память:
Для записи мы с начало обращаемся к памяти с битом Записи в адресе. Потом посылаем два 8-ми битных адреса(то есть у нас 0x8000 адресов) , затем байт данных и сигнал STOP. После этого EEPROM входит во внутренне синхронизированный цикл записи tWR(Write Cycle Time 5 ms) в энергонезависимую память. Все входные сигналы
отключено во время этого цикла записи, и EEPROM не ответит, пока запись не будет завершена.
Копаемся дальше и находим в datasheet что память микросхемы организована как 512 страниц по 64 байта. То есть мы может записать сразу до 64 байт информации за одну команду. Для этого мы передаем все 64 байта информации и только после этого посылаем сигнал STOP.
Чтение данных :
С чтением данных все интересней. Память поддерживает три варианта чтения:
- Читать текущий адрес;
- Читать случайный адрес;
- Последовательное чтение;
Память запоминает последний адрес записи пока не отключено питание, по этому мы можем прочитать последний байт без указания адреса.
Что бы прочитать случайный адрес нам нужно с начало отправить команду на запись и передать адрес который хотим прочитать(не забывайте что адрес состоит из двух 8-ми битных частей). За тем отправить команду на чтение и получить искомый байт, завершив все командой STOP.
Последовательное чтение может выполняться как с текущего адреса так и со случайного и будет продолжаться пока микроконтроллер не отправит сигнал СТОП. При переполнении адреса память ее сбросить и адресация начнется с начала.
Ну что же пришло время попробовать что нибудь записать:
#include <Wire.h> // Подключаем библиотеку #define EEPROM_ADDRESS 0x53 // Задаем адрес памяти word address = 0; // Адрес куда будем записывать byte data_send = 170 ; // Даные void setup() { Wire.begin(); // Инициализируем I2C Serial.begin(9600); Serial.print("Write byte to EEPROM memory..."); Serial.println (data_send); Wire.beginTransmission(EEPROM_ADDRESS); // Начинаем передачу Wire.write(address >> 8); Wire.write(address & 0xFF); // Отправляем два байта адреса Wire.write(data_send); // Отправляем данные byte status= Wire.endTransmission(); // Заканчиваем передачу и проверяем статус передачи. if (status == 0)Serial.println ("Ок"); // delay(10); Serial.println("Read byte from EEPROM memory..."); Wire.beginTransmission(EEPROM_ADDRESS); // Что бы прочитать данные отравляем сначала адрес где они лежат. Wire.write(address >> 8); Wire.write(address & 0xFF); status= Wire.endTransmission(); if (status == 0)Serial.println ("Ок"); // stop transmitting Wire.requestFrom(EEPROM_ADDRESS, (byte)1); // отправляем команду на чтение одного байта данных byte data = 0; if (Wire.available()) // проверяем что есть данные для чтения. { data = Wire.read(); //читаем данные } Serial.println (data, DEC); } void loop() { }
Wire.requestFrom(address, quantity) — Используется мастером для запроса байтов от ведомого устройства. Эти байты могут быть получены с помощью методов available() и read(). Размер буфера такой же 32 байта.
- address: 7-битный адрес устройства, у которого запрашиваются байты;
- quantity: количество запрашиваемых байтов;
Wire.available() — Возвращает количество байтов, доступных для получения с помощью read().
Ну и пример с записью в память строки «Hello Word» :
#include <Wire.h> #define EEPROM_ADDRESS 0x53 word address = 0; char data_send[] = "Hello Word" ; void setup() { Wire.begin(); // Serial.begin(9600); Serial.print("Write byte to EEPROM memory..."); Serial.println (data_send); Wire.beginTransmission(EEPROM_ADDRESS); Wire.write(address >> 8); Wire.write(address & 0xFF); Wire.write(data_send); byte status= Wire.endTransmission(); if (status == 0)Serial.println ("Ок"); delay(10); Serial.println("Read byte from EEPROM memory..."); Wire.beginTransmission(EEPROM_ADDRESS); Wire.write(address >> 8); Wire.write(address & 0xFF); status= Wire.endTransmission(); if (status == 0)Serial.println ("Ок"); // stop transmitting Wire.requestFrom(EEPROM_ADDRESS, (byte)10); byte data = 0; for (int i=0 ; i<10 ;i++) { if (Wire.available()) { data = Wire.read(); } Serial.write (data); } } void loop() { }
Организация памяти:
Так как в datasheet об этом написано смутно. Я на практике пытался разобраться с этим. В datasheet написано что память микросхемы организована как 512 страниц по 64 байта. Что это значит ? Если мы захотим записать больше 64 байт сразу ,скажем по адресу 0x40( адрес начало второй страницы), при выходе адреса за границы страницы внутренний счетчик микросхемы сбросит адрес на начало страницы. А лишние байты будут записаны в начало страницы и сотрут данные которые были там записаны.
Для чтения таких ограничений нету, в даташипе написано только то что когда вы достигнете конца адресов вас автоматически перекинет на начало ( адрес 0х00).
На этом я думаю все. Вы конечно можете скачать уже готовую библиотеку для EEPROM , дума сможете и написать свою. Главное мы разобрались в основном принципе работы.