EEPROM 24C128 - 1024: универсальный инструмент
- Войдите на сайт для отправки комментариев
Недавно сам встретился с тем фактом, что библиотек много и разных, но некоторые неудобны для использования. А некоторые работаю очень медленно по причине того, что применяется побайтовая запись\считывание. При этом, по стандарту, после записи микросхеме нужно около 5мс для физической зарядки ячеек. Соответствено, при таком способе записи, структура размером\переменная, размером 43 байта, в количестве более 1000 экземпляров, в 24C512 писались более десяти минут, а на считываниет этой своеобразной БД и поиск между десятью полями в каждоой - более 17 секунд.
Но есть решение, оказывается - современные 24C128 - 512 обладают возможность записи\считівания в страничном режиме. То-есть, - за один запрос с ЕЕПРОМ можно считать, или записать в нее, 64 - 128 байт за один цикл. (конкретно размер страниці можно узнать из даташита на конкретный экземпляр)
Перелопатив кучу инфы, нашел несколько вариантов решений, в том числе так называемый "EEPROMAnything.h", описаный тут https://playground.arduino.cc/Code/EEPROMWriteAnything/
в результате родилось это:
#include <Arduino.h> // for type definitions #include <Wire.h> #define deviceAddress 0x50 // Адресс микросхемы по умолчанию, А0-А2 подключены к GND (или висят в воздухе, что не желательно). #define pageSize 128 // смотрите даташит на свою микросхему 24Схххх, размер может как 64, так и 128 /* Физический буффер i2c шины в Ардуино равен 32 байта, поэтому, воспользоваться прелестями чтения, 64 или 128 байт за раз, не получится. К тому же, увеличение буффера до 128 байт было бы слишком расточительно для ОЗУ ардуино. */ template <class T> void EEPROM_get(uint32_t eeaddress, T& value) { uint8_t num_bytes = sizeof(value); byte* p = (byte*)(void*)&value; byte countChank = num_bytes / 32; byte restChank = num_bytes % 32; uint32_t addressChank = 0; if (countChank > 0) { for (byte i = 0; i < countChank; i++) { addressChank = eeaddress + 32 * i; Wire.beginTransmission(deviceAddress); Wire.write((uint16_t)(addressChank >> 8)); Wire.write((uint16_t)(addressChank & 0xFF)); Wire.endTransmission(); Wire.requestFrom(deviceAddress, 32); while (Wire.available()) *p++ = Wire.read(); } } if (restChank > 0) { if (countChank > 0) addressChank += 32; Wire.beginTransmission(deviceAddress); Wire.write((uint32_t)((addressChank) >> 8)); Wire.write((uint32_t)((addressChank) & 0xFF)); Wire.endTransmission(); Wire.requestFrom(deviceAddress, restChank); while (Wire.available()) *p++ = Wire.read(); } } template <class T> void EEPROM_put(uint32_t eeaddress, const T& value) { const byte* p = (const byte*)(const void*)&value; byte i = 0, counter = 0; uint32_t address; byte page_space; byte page = 0; byte num_writes; uint16_t data_len = 0; byte first_write_size; byte last_write_size; byte write_size; // Calculate length of data data_len = sizeof(value); // Calculate space available in first page page_space = int(((eeaddress / pageSize) + 1) * pageSize) - eeaddress; // Calculate first write size if (page_space > 16) { first_write_size = page_space - ((page_space / 16) * 16); if (first_write_size == 0) { first_write_size = 16; } } else { first_write_size = page_space; } // calculate size of last write if (data_len > first_write_size) { last_write_size = (data_len - first_write_size) % 16; } // Calculate how many writes we need if (data_len > first_write_size) { num_writes = ((data_len - first_write_size) / 16) + 2; } else { num_writes = 1; } i = 0; address = eeaddress; for (page = 0; page < num_writes; page++) { if (page == 0) { write_size = first_write_size; } else if (page == (num_writes - 1)) { write_size = last_write_size; } else { write_size = 16; } Wire.beginTransmission(deviceAddress); Wire.write((uint32_t)((address) >> 8)); Wire.write((uint32_t)((address) & 0xFF)); counter = 0; do { Wire.write((byte) *p++); i++; counter++; } while ((counter < write_size)); Wire.endTransmission(); address += write_size; // увеличиваем адрес для следующего буфера delay(5); // задержка нужна для того, чтобы ЕЕПРОМ успела // зарядить ячейки памяти (смотрите даташит своего устройства) } }
Просто интересно - для чего такая конструкция: Wire.write((uint32_t)((address) >> 8));
Способ использования прост до безобразия. Создаем новую вкладку в скетче, обзываем ее к примеру EEPROM24Cxxx.h, потом в основном скетче
Функции читают и пишут все подряд - любые данные, любого типа (вплоть до сложных структур, размером от 1-го до 256 байтов.
ПС. написано все это для новичков, в первую очередь (сам себя, по факту, еще из этого статуса не исключил), Гуру тутышние "сами с усами". Так что сильно не пинайте, кому-то пригодится. ;)
ППС. @Модераторы: Закрепить бы где, а то утонет. Вещь вроде полезная.
Просто интересно - для чего такая конструкция: Wire.write((uint32_t)((address) >> 8));
честно, сам не знаю, срисовал :))
Тут ЕвгенийП как-то писал, что подобные штуки темплейтами не очень полезно делать. Тем более, что первой операцией у Вас сразу идёт приведение указателя к байтовому.
size_t
EEPROM_put(uint32_t eeaddress,
const
uint8_t* ptrData,
должна меньше бить МК по ресурсам.const
size_t sizeData) {...; return writtenBytes; }Просто интересно - для чего такая конструкция: Wire.write((uint32_t)((address) >> 8));
честно, сам не знаю, срисовал :))
Wire.h : inline size_t write(unsigned long n) { return write((uint8_t)n); }
Ну вобще работать с ЕЕПРОМ странично - очень верно. Про неуместность шаблонов уже написано. Если уж сильно хочется имет функции прям для классов - ну напишите сразу для буфера а из шаблона уже её вызывайте, всеж компактней. Но куда интересней вот это:
// структура размером\переменная, размером 43 байта
однако
Физический буффер i2c шины в Ардуино равен 32 байта, поэтому, воспользоваться прелестями чтения, 64 или 128 байт за раз, не получится.
Получается не решена задача. Можна бы одной страницей, а прийдется за 2-3 ?
К тому же, увеличение буффера до 128 байт было бы слишком расточительно для ОЗУ ардуино.
И с этим я согласен. Потому логично не делать буферизацию чтения. Тогда и структуру принять можна, и ОЗУ не разбрасыватся. Попробуйте калбэк, вам понравится. Тем более что i2c синхронная шина, она просто просит такое.
Сейчас лучше FRAM использовать. При питании работает как ОЗУ, сама при снятии питания сохраняет, а при включении восстанавливает данные. И скорость шины официально до 1МГц.
Ну вобще работать с ЕЕПРОМ странично - очень верно. Про неуместность шаблонов уже написано. Если уж сильно хочется имет функции прям для классов - ну напишите сразу для буфера а из шаблона уже её вызывайте, всеж компактней. Но куда интересней вот это:
// структура размером\переменная, размером 43 байта
однако
Физический буффер i2c шины в Ардуино равен 32 байта, поэтому, воспользоваться прелестями чтения, 64 или 128 байт за раз, не получится.
Получается не решена задача. Можна бы одной страницей, а прийдется за 2-3 ?
К тому же, увеличение буффера до 128 байт было бы слишком расточительно для ОЗУ ардуино.
И с этим я согласен. Потому логично не делать буферизацию чтения. Тогда и структуру принять можна, и ОЗУ не разбрасыватся. Попробуйте калбэк, вам понравится. Тем более что i2c синхронная шина, она просто просит такое.
А что не так с шаблонами? Можно где то почитать?
Да, 43 байта, такова задача.
Проблема чтения страницами по 64/128 байт действительно решена не полностью, читаем фреймами по 32 байта, но за один вызов функции.
"Попробуйте калбэк, вам понравится. Тем более что i2c синхронная шина, она просто просит такое." - можно поподробнее, особенно интересно что вы имеете в виду под словом "калбек"?
Кстати, запись тех же данных сейчас - около 23 секунд, было больше 10 минут, а скорость поиска 1,2 секунды, а было 17. Если запускать шину на 800мГц, то ищет за 0.7 секунды.
Сейчас лучше FRAM использовать. При питании работает как ОЗУ, сама при снятии питания сохраняет, а при включении восстанавливает данные. И скорость шины официально до 1МГц.
В какой области фрамка сама сохраняет и восстанавливает данные - в ОЗУ микроконтроллера что ли?
Все просто. При инициализации обмена (далее для определенности пусть чтение будет) вызываем соответствующую функцию с адресом и новый параметр - указатель на функцию принимающую данные, оно и есть калбек. Считали байт - вызвали эту функцию передав этот считаный байт, она уже знает что с ним делать, может и в структуру сложить, а может сразу обработать, зачемто же он нужен. Это простейшая реализация. Она позволяет отказатся от буфера но имеет и недостаток - побайтно может оказатся слишком медленно.
Совершенствуем. Пусть калбек принимает не один байт, а несколько, через небольшой буфер, который сам калбек и укажет и размер которого сама и сообщит. Тогда работа будет такая. Инитим обмен, передаем ей адрес и калбек. Начинается страничное чтение, но сколько считать и куда девать - неизвестно. Поэтому функция чтения вызывает калбек, а он возвращает сколько байт нужно считать и указатель на буфер куда их положить. Это может быть часть структуры, или вся, вобщем по ситуации. Функция страничного чтения считывает сколько нужно и снова вызывает калбек, передав считаные данные. При этом страничное чтение не завершается, оно просто откладывается пока калбек отработает. А синхронная шина это терпит, она нас никуда не торопит! Калбек их может обработать по ходу, а может и просто сохранить, но в любом случае он снова скажет сколько байт нужно и куда их укладывать. И так пока калбек не вернет 0 - чтение закончено. Так можна без буфера сразу в свою структуру читать, а если какие данные не нужны - то и пропустить можна. Насколько стандартная либа i2c к такому готова - незнаю, я её не пользую, вероятно допиливать надо.
Таким образом можна строить и цепочки по обработке данных. Правда есть один подводный камень, не так давно напоролся. Предполагалось читать с 24с512 данные логирования процесса и выводить на экран график. Я построил вот так цепочку. И оно не заработало. Потому что экран и 24с512 на одном i2c висели, и вывод данных на экран нарушал страничное чтение. Решение очевидное - сделал второй аппаратный i2c и нормас.
Сейчас лучше FRAM использовать. При питании работает как ОЗУ, сама при снятии питания сохраняет, а при включении восстанавливает данные. И скорость шины официально до 1МГц.
В какой области фрамка сама сохраняет и восстанавливает данные - в ОЗУ микроконтроллера что ли?
)))
В блокнот записывает;)
Загугли бы шоле FRAM, не позорился бы лишний раз.
Так страничная работа рулит однозначно!
Ну так... я уже хотел от задачи отказаться, скорость реакции микроконтроллера с такой памятью сильно не устаривала, но интересно стало. Сейчас, в принципе, скорость практически устраивает.
Кстати, в этих "моих" функциях (хотя чтение я сам писал, а запись сильно правил) буфер как таковой даже не применяется. Я вот не помню, буфер I2C аппаратный, или на стеке висит? Но так или иначе он уже висит в памяти, а свой я не создаю, а даже переменную не создаю для возврата, а прямо в область памяти живой переменной данные вписываю. Остальные созданные переменные, при выходе из функции умрут.
Похоже это таки оптимально. Или ошибаюсь?
Кстати, если кто из грамотных людей это все еще причешет - я только рад буду. Может получится практически универсальная библиотека для 24Cxxx. Увы микросхемы поменьше объемом не совместимы остаются, у меня с 24C32 не заработало. Видать страничный режим ей не по зубам, даташит не искал, лень было, не очень то и хотелось, просто воткнул вместо 512, - нет так нет.
я делал с общим кэшем на запись/чтение на 16 байт, скорость мня устроила.
Я вот не помню, буфер I2C аппаратный, или на стеке висит?
Вобще и я таких вещей стараюсь не запоминать ;) Но открываю Wire.c, а в нем
Ну и в Wire.h загляну: #define BUFFER_LENGTH 32
У 24C32 страницы менше, может из-за этого. И кажется адресация немного по другому, т.к. адрес короче.
Адресация одинакова от 24с32 до 24с512, адрес 2 байта. Размер страниц разный.
24с32 стоит на китайских модулях часов 3231, я с ней баловался. Этот же код без переделок работал на внешних модулях 24с256, вроде ошибок не было, я там пиктограммы храню, при косяках с адресацией все наглядно на экранчике видно.
Вы правы, в даташите на 24С32 размер страницы равен 32 байта, мои функции должны вполне себе работать. Только в 24Cxxx.h нужно дефайн размера страницы в 32 поставить.
Я вот не помню, буфер I2C аппаратный, или на стеке висит?
Вобще и я таких вещей стараюсь не запоминать ;) Но открываю Wire.c, а в нем
Ну и в Wire.h загляну: #define BUFFER_LENGTH 32
У 24C32 страницы менше, может из-за этого. И кажется адресация немного по другому, т.к. адрес короче.
Не лазил, не знаю.
В первичном коде были некоторые шероховатости. Все забываю выложить окончательный исправленный вариант
вот вспомнил
Запись 30-ти байтными блоками быстрее, нежели 16-ти. И написано... ужос(.
Запись 30-ти байтными блоками быстрее, нежели 16-ти. И написано... ужос(.
а если отказаться от использования Wire.h, то можно будет писать блоками неограниченного размера, хоть всю микросхему одним движением. Да и размер кода поменьше.
писал подобное для FRAM : https://arduino.ru/forum/proekty/mini-biblioteka-zapisi-i-chteniya-pamyati-fram-fm24
Запись 30-ти байтными блоками быстрее, нежели 16-ти. И написано... ужос(.
с 16-байтными блоками арифметика проще. Я писал давно для одного проекта универсальный (пстрактный) классик TCachedROM, от которого потом наследуются и класс встроенного EEPROM и любые внешние AT24Cxx, дак тоже остановился на 16 байтном буфере, в итоге получилось проще.
Это правда чистая. :)
Запись 30-ти байтными блоками быстрее, нежели 16-ти. И написано... ужос(.
с 16-байтными блоками арифметика проще. Я писал давно для одного проекта универсальный (пстрактный) классик TCachedROM, от которого потом наследуются и класс встроенного EEPROM и любые внешние AT24Cxx, дак тоже остановился на 16 байтном буфере, в итоге получилось проще.
дай ссылку
Нет
Нет
нет потому что нет или потому что её нет?
Найду, тока когда дома буду. В октябре. А оно тебе точно ннада?
Найду, тока когда дома буду. В октябре. А оно тебе точно ннада?
срочно точно не надо
а если отказаться от использования Wire.h, то можно будет писать блоками неограниченного размера, хоть всю микросхему одним движением. Да и размер кода поменьше.
писал подобное для FRAM : https://arduino.ru/forum/proekty/mini-biblioteka-zapisi-i-chteniya-pamyati-fram-fm24
Вместо Wire есть совсем компактные: https://habr.com/ru/company/ruvds/blog/670392/.
У FRAM адресация иная. И готовность не нужно ждать.)
У обычных 24с, при страничной записи, время записи получается всего в 2 раза ниже времени чтения.
Не так уже и плохо.
У обычных 24с, при страничной записи, время записи получается всего в 2 раза ниже времени чтения.
Не так уже и плохо.
я бы сказал - отлично!
У обычных 24с, при страничной записи, время записи получается всего в 2 раза ниже времени чтения.
это через что?
TWI без Wire, 128 байт блок, 24с512.
TWI без Wire, 128 байт блок, 24с512.
а подряд через границы страниц оно не пишет? у ФРАМ можно хоть весь чип одним разом записать
Мне пока нет необходимости - как раз нужно писать 128-ти байтными блоками.
Но и с произвольными адресами/размерами не сложно сделать.
у ФРАМ можно хоть весь чип одним разом записать
Там по разному. Но в общем, страницы выбираются битами в slave адресе.
страницы выбираются битами в slave адресе.
все верно. Но если выбрать стартовый адрес за 2 байта до конца страницы и потом записать по этому адресу пачку в 20 байт - 2 байта запишутся на текущую страницу, а 18 байт автоматически будут записаны в начало следующей.
Да? Я понял по другому:
Может не у всех?
Да? Я понял по другому:
Может не у всех?
ну то есть твой текст подтверждает мои слова :).
От нуля вплоть до адреса 8000h память непрерывна, а это сразу целых 256 кбит , то есть покрывает все нижние серии ФРАМ
ОК. Вопрос в терминологии.
Фактически это и есть две (большие) страницы.)
По крайней мере, ведут они себя аналогично at24c512, разница только в размере этих страниц.
PS. Скорее, как два отдельный чипа.)
ОК. Вопрос в терминологии. Фактически это и есть две (большие) страницы.)
точно, в терминологии. Главное не путать эти две страницы и вот эти:)
страницы выбираются битами в slave адресе.
за инфо про "большие" страницы в 256кбит спасибо, не знал этого