ARDUINO EEPROM I2C 24Сxxx. Универсальный инструмент. (библиотека)

EEPROM I2C 24Сxxx. Универсальный инструмент. (библиотека)
Во время создания прошивки для одного прибора, столкнулся с тем фактом, что мне нужно создать импровизированную Базу Данных, количество записей в которую может достигать пару тысяч. Понятно, что для такого количества данных, встроенного ЕЕПРОМ в Atmega328, явно недостаточно. Остановил свой выбор на микросхеме внешнего EEPROM, с поддержкой интерфейса I2C, 24-й серии, а точнее на 24C256. Перебрав кучу разных странных библиотек, понял что задержки чтения\записи меня совершенно не устраивают. К примеру нужный мне массив записей создавался более 10 минут (минут, Карл!), а поиск в этом массиве мог занимать более 15 секунд, что было совершенно неприемлемо. Мне нужна была реакция прибора не более секунды.
Почитав даташит на эти микросхемы, я узнал, что большинство из них умею писать\читать в страничном режиме. В связи с этим у меня встал вопрос о том, что нужно создать свой инструмент, который сможет работать в таком режиме, потому как практически все библиотеки использовали побайтовый способ записи\чтения.
Взяв за основу известный инструмент EEPROMAnything.h написал свой. Прошу любить и жаловать.

В связи с ограничение буфера чтения и записи I2C в ардуино в 32 байта, запись и чтение страницами работает не в полной мере, но скорость все равно выросла в десятки и сотни раз. Так запись нужного мне массива данных, вместо 10 минут, стала занимать несколько десятков секунд, а поиск в такой БД ~ 0,7c., что меня вполне устраивает.

Инструмент достаточно универсален, и пригоден к использованию практически с любой микросхемой серии 24, использующей два байта адреса, как 24c32 так и вплоть до 24c512. Увы, но микросхемы 24с1024 и 24с2048, без специальных ухищрений не могут использоваться с этим инструментом, в связи с ограничениями библиотеки Wire из ARDUINO.

Настройка этого инструмента в вашем проекте очень проста. Вам нужно узнать размер страницы из даташита на вашу микросхему и вписать в дефайн, вписать в дефайн правильный адрес на шине (или оставить дефолтный), посмотреть сколько мс занимает физическая зарядка ячеек (тоже из даташита) и вписать свое значение, или оставить дефолтные 5мс, которых хватает как правило, и все.
Еще не плохо бы глянуть с какой скоростью ваша микросхема может работать с шиной, возможные значения для атмеги 100кГц, 400кГц, 800кГц и 1МГц. Само собой, стоит запускать шину с максимальной скоростью (если скорость действительно важна), но нужно учитывать ограничения других устройств на шине. К примеру, LCD1602 c I2C приблудой не работает с частотой шины выше 400кГц.

Для использования:
#include "EEPROM24xxx.h"


запись EEPROM_put(adress, value);
чтение EEPROM_get(adress, value); // НЕ value = EEPROM_get(adress, value), темплейт пишет на прямую в переменную в памяти.

Размер и тип данных может быть любой, как однобайтный, так и сложные структуры размером до 65,535 байт.
Лично я писал\читал структуры размером в 43 байта.

Буду рад, если этот инструмент будет кому-то полезен.

EEPROM24xxx.h:
#ifndef EEPROM24Cxxx_h
#define EEPROM24Cxxx_h

#include <Arduino.h>                          
#include <Wire.h>

#define DEVICE_ADRESS 0x50          // Адресс микросхемы по умолчанию, А0-А2 подключены к GND (или висят в воздухе, что не желательно).
#define PAGE_SIZE 64                // смотрите даташит на свою микросхему 24Схххх, размер страницы может быть 32 байта, 64 так и 128 байт.
#define WAIT_FOR_FISICAL_WRITE 5    // время миллисекунд для физической зарядки ячеек памяти, зависит от типа микросхемы, смотрите даташит




/*
  Физический буффер i2c шины в Ардуино равен 32 байта. Поэтому пишем блоками по 16 байт (потому как в буфер записи помещаются не только данные,
  но и адрес, все 32 байта буфера не доступны), наполняя страницу за страницей. И только если страница передана полностью, или кончились данные,
  - происходит физическая запись. Объект будет записан полностью за один вызов функции записи.
  Читаем блоками по 32 байта, набивая страницу за страницей, пока весь объект (переменная, структура и т.п.) не будет вычетана полностью за один
  вызов функции чтения.
  Таким образом удается воспользоваться страничным режимом, что резко увеличивает скорость чтения\записи в ЕЕПРОМ, поддерживающую страничный
  режим  записи\чтения данных. В случае чтения\записи больших объектов, скорость увеличивается в несколько десятков, а при записи и сотен, раз.
*/

template <class T> void EEPROM_get(uint16_t eeaddress, T& value) {
  uint16_t num_bytes = sizeof(value);
  byte* p = (byte*)(void*)&value;
  byte countChank = num_bytes / 32;
  byte restChank = num_bytes % 32;
  uint16_t addressChank = 0;
  if (countChank > 0) {
    for (byte i = 0; i < countChank; i++) {
      addressChank = eeaddress + 32 * i;
      Wire.beginTransmission(DEVICE_ADRESS);
      Wire.write((uint8_t)(addressChank >> 8));
      Wire.write((uint8_t)(addressChank & 0xFF));
      Wire.endTransmission();
      Wire.requestFrom(DEVICE_ADRESS, 32);
      while (Wire.available()) *p++ = Wire.read();
    }
  }

  if (restChank > 0) {
    if (countChank > 0)
      addressChank += 32;
    else
      addressChank = eeaddress;


    Wire.beginTransmission(DEVICE_ADRESS);
    Wire.write((unsigned long)((addressChank) >> 8));
    Wire.write((unsigned long)((addressChank) & 0xFF));
    Wire.endTransmission();
    Wire.requestFrom(DEVICE_ADRESS, restChank);
    while (Wire.available()) *p++ = Wire.read();
  }

}



template <class T> void  EEPROM_put(uint16_t eeaddress, const T& value) {
  const byte* p = (const byte*)(const void*)&value;

  byte counter = 0;
  uint16_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 / PAGE_SIZE) + 1) * PAGE_SIZE) - 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;
  }


  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(DEVICE_ADRESS);
    Wire.write((uint8_t)((address) >> 8));
    Wire.write((uint8_t)((address) & 0xFF));
    counter = 0;
    do {
      Wire.write((byte) *p++);
      counter++;
    } while ((counter < write_size));
    Wire.endTransmission();
    address += write_size;                                // увеличиваем адрес для записи следующего буфера
    delay(WAIT_FOR_FISICAL_WRITE);                        // задержка нужна для того, чтобы ЕЕПРОМ успела физически зарядить ячейки памяти
  }
}
#endif
UPDATE 25\11\2023 Исправлен баг с нулевым адресом .
 

Вложения

Изменено:

Комментарии

kostyamat

★★★★★★✩
29 Окт 2019
1,098
632
Во время создания прошивки для одного прибора, столкнулся с тем фактом, что мне нужно создать импровизированную Базу Данных, количество записей в которую может достигать пару тысяч. Понятно, что для такого количества данных, встроенного ЕЕПРОМ в Atmega328, явно недостаточно. Остановил свой выбор на микросхеме внешнего EEPROM, с поддержкой интерфейса I2C, 24-й серии, а точнее на 24C256. Перебрав кучу разных странных библиотек, понял что задержки чтения\записи меня совершенно не устраивают. К примеру нужный мне массив записей создавался более 10 минут (минут, Карл!), а поиск в этом массиве мог занимать более 15 секунд, что было совершенно неприемлемо. Мне нужна была реакция прибора не более секунды.
Почитав даташит на эти микросхемы, я узнал, что большинство из них умею писать\читать в страничном режиме. В связи с этим у меня встал вопрос о том, что нужно создать свой инструмент, который сможет работать в таком режиме, потому как практически все библиотеки использовали побайтовый способ записи\чтения.
Взяв за основу известный инструмент EEPROMAnything.h написал свой. Прошу любить и жаловать.

В связи с ограничение буфера чтения и записи I2C в ардуино в 32 байта, запись и чтение страницами работает не в полной мере, но скорость все равно выросла в десятки и сотни раз. Так запись нужного мне массива данных, вместо 10 минут, стала занимать несколько десятков секунд, а поиск в такой БД ~ 0,7c., что меня вполне устраивает.

Инструмент достаточно универсален, и пригоден к использованию практически с любой микросхемой серии 24, использующей два байта адреса, как 24c32 так и вплоть до 24c512. Увы, но микросхемы 24с1024 и 24с2048, без специальных ухищрений не могут использоваться с этим инструментом, в связи с ограничениями библиотеки Wire из ARDUINO.

Настройка этого инструмента в вашем проекте очень проста. Вам нужно узнать размер страницы из даташита на вашу микросхему и вписать в дефайн, вписать в дефайн правильный адрес на шине (или оставить дефолтный), посмотреть сколько мс занимает физическая зарядка ячеек (тоже из даташита) и вписать свое значение, или оставить дефолтные 5мс, которых хватает как правило, и все.
Еще не плохо бы глянуть с какой скоростью ваша микросхема может работать с шиной, возможные значения для атмеги 100кГц, 400кГц, 800кГц и 1МГц. Само собой, стоит запускать шину с максимальной скоростью (если скорость действительно важна), но нужно учитывать ограничения других устройств на шине. К примеру, LCD1602 c I2C приблудой не работает с частотой шины выше 400кГц.

Для использования:
#include "EEPROM24xxx.h"


запись EEPROM_put(adress, value);
чтение EEPROM_get(adress, value); // НЕ value = EEPROM_get(adress, value), темплейт пишет на прямую в переменную в памяти.

Размер и тип данных может быть любой, как однобайтный, так и сложные структуры размером до 65,535 байт.
Лично я писал\читал структуры размером в 43 байта.

Буду рад, если этот инструмент будет кому-то полезен.

EEPROM24xxx.h:
#ifndef EEPROM24Cxxx_h
#define EEPROM24Cxxx_h

#include <Arduino.h>                          
#include <Wire.h>

#define DEVICE_ADRESS 0x50          // Адресс микросхемы по умолчанию, А0-А2 подключены к GND (или висят в воздухе, что не желательно).
#define PAGE_SIZE 64                // смотрите даташит на свою микросхему 24Схххх, размер страницы может быть 32 байта, 64 так и 128 байт.
#define WAIT_FOR_FISICAL_WRITE 5    // время миллисекунд для физической зарядки ячеек памяти, зависит от типа микросхемы, смотрите даташит




/*
  Физический буффер i2c шины в Ардуино равен 32 байта. Поэтому пишем блоками по 16 байт (потому как в буфер записи помещаются не только данные,
  но и адрес, все 32 байта буфера не доступны), наполняя страницу за страницей. И только если страница передана полностью, или кончились данные,
  - происходит физическая запись. Объект будет записан полностью за один вызов функции записи.
  Читаем блоками по 32 байта, набивая страницу за страницей, пока весь объект (переменная, структура и т.п.) не будет вычетана полностью за один
  вызов функции чтения.
  Таким образом удается воспользоваться страничным режимом, что резко увеличивает скорость чтения\записи в ЕЕПРОМ, поддерживающую страничный
  режим  записи\чтения данных. В случае чтения\записи больших объектов, скорость увеличивается в несколько десятков, а при записи и сотен, раз.
*/

template <class T> void EEPROM_get(uint16_t eeaddress, T& value) {
  uint16_t num_bytes = sizeof(value);
  byte* p = (byte*)(void*)&value;
  byte countChank = num_bytes / 32;
  byte restChank = num_bytes % 32;
  uint16_t addressChank = 0;
  if (countChank > 0) {
    for (byte i = 0; i < countChank; i++) {
      addressChank = eeaddress + 32 * i;
      Wire.beginTransmission(DEVICE_ADRESS);
      Wire.write((uint8_t)(addressChank >> 8));
      Wire.write((uint8_t)(addressChank & 0xFF));
      Wire.endTransmission();
      Wire.requestFrom(DEVICE_ADRESS, 32);
      while (Wire.available()) *p++ = Wire.read();
    }
  }

  if (restChank > 0) {
    if (countChank > 0)
      addressChank += 32;
    else
      addressChank = eeaddress;


    Wire.beginTransmission(DEVICE_ADRESS);
    Wire.write((unsigned long)((addressChank) >> 8));
    Wire.write((unsigned long)((addressChank) & 0xFF));
    Wire.endTransmission();
    Wire.requestFrom(DEVICE_ADRESS, restChank);
    while (Wire.available()) *p++ = Wire.read();
  }

}



template <class T> void  EEPROM_put(uint16_t eeaddress, const T& value) {
  const byte* p = (const byte*)(const void*)&value;

  byte counter = 0;
  uint16_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 / PAGE_SIZE) + 1) * PAGE_SIZE) - 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;
  }


  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(DEVICE_ADRESS);
    Wire.write((uint8_t)((address) >> 8));
    Wire.write((uint8_t)((address) & 0xFF));
    counter = 0;
    do {
      Wire.write((byte) *p++);
      counter++;
    } while ((counter < write_size));
    Wire.endTransmission();
    address += write_size;                                // увеличиваем адрес для записи следующего буфера
    delay(WAIT_FOR_FISICAL_WRITE);                        // задержка нужна для того, чтобы ЕЕПРОМ успела физически зарядить ячейки памяти
  }
}
#endif
UPDATE 25\11\2023 Исправлен баг с нулевым адресом .
 

Вложения

Изменено:

bort707

★★★★★★✩
21 Сен 2020
3,046
909
@Yurgen, ваша микросхема аналог 24с08, эта библиотека с такими не работает, почему - объяснил в #44

Вообще библиотек под i2c eeprom чипы море, ищите ту, что поддерживает маленькие размеры 24с02 - 24с16.
 

Dreman

✩✩✩✩✩✩✩
25 Апр 2024
8
7
tesanoff.klah.ru
Я случайно обнаружил, что на этой странице (здесь) расположен весьма интересный код, работающий с AT24C16.
Код этот интересен тем, что работает очень быстро (время записи 2048 байт я не замечаю глазом).
А работает от быстро потому, что вместо жестко зашитых пауз (чтобы микросхема успела записать данные) там используется вот что:

C++:
static void at24cxx_wait(int slave)
{
    int resault = 0;
    do
    {
        Wire.beginTransmission(slave);
        resault = Wire.endTransmission();
    } while (resault != 0);
}
Возможно, это будет полезно уважаемому сообществу.

2048 байт (полная емкость) пишутся за 231 мс.