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
631
Во время создания прошивки для одного прибора, столкнулся с тем фактом, что мне нужно создать импровизированную Базу Данных, количество записей в которую может достигать пару тысяч. Понятно, что для такого количества данных, встроенного ЕЕПРОМ в 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 Исправлен баг с нулевым адресом .
 

Вложения

Изменено:

Старик Похабыч

★★★★★★★
14 Авг 2019
4,220
1,291
Москва
Вот я так реализовал.
Это готовый скетч, там есть запись и чтение массива, а так же пример чтения структуры (кусок массива читается в структуру)
Замутил какую то фигню с расчетом размера страницы, но получилось, что просто отвязался от жесткого размера буфера.
Да, в мои функции жесток заточены под один адрес памяти - мне так надо для другой задачи.
Моё видение:
struct TestStr
{
  byte a=5, b=4, c=3, d=2;
};

#define EEPROM_ADDR 0x50
#define EEPROM_PGSZ 0x20

#include <Wire.h>
//------------------------------------------------------------------------//
bool TestAddres(byte address)
{
  Wire.beginTransmission(address);
  byte error = Wire.endTransmission();
  return (error == 0);
}
//------------------------------------------------------------------------//
void StartMEM()
{
  if (TestAddres(EEPROM_ADDR))
  {
    Serial.println("EEPROM Ok");
  }
  else
  {
    Serial.println("EEPROM not found");
  }
}
//------------------------------------------------------------------------//
bool writeEEPROM_i2c(uint16_t addr, uint16_t len, const uint8_t * data )
{
  uint8_t One_Page = EEPROM_PGSZ;
  // подбираем размер странцы, как правило будет 16, но если размер буфера отличный от 32, то может быть и другой.
  // если буфер не степень 2 может быть ой. Но обычно делают степентю. Да и страница тоже степень
  while (((BUFFER_LENGTH - 2) / One_Page) == 0) One_Page /= 2;


  uint16_t data_pntr = 0;

  uint16_t first_page = addr / One_Page;                  // начальная страница памяти
  uint16_t last_page = (addr + len - 1) / One_Page + 1;;   // конечная страница памяти

  uint16_t first_byte = addr;
  uint16_t end_byte = min((first_page + 1) * One_Page - 1, addr + len - 1) + 1;

  for (int i = first_page; i < last_page; i++)
  {
    Wire.beginTransmission(EEPROM_ADDR);
    Wire.write(first_byte >> 8);
    Wire.write(first_byte & 0xFF);

    for (int j = first_byte; j < end_byte; j++)
      Wire.write(data [data_pntr++]);

    bool err = Wire.endTransmission(); // добавить обработчик ошибок
    if (err != 0) return false;
    delay(5);
    first_byte = end_byte;
    end_byte = min(first_byte + One_Page - 1, addr + len - 1) + 1;
  }
  return true;
}
//------------------------------------------------------------------------//

bool readEEPROM_i2c(uint16_t addr,  uint16_t len, uint8_t * data)
{
  Wire.beginTransmission (EEPROM_ADDR);
  Wire.write ((uint8_t ) (addr >> 8));    // старший байт адреса чтения
  Wire.write ((uint8_t ) (addr & 0xFF));  // младший байт адреса чтения
  bool err = Wire.endTransmission();
  if (err != 0)  return false;  // ошибка доступа!

  /* выбираем размер для чтения, либо по буферу либо по странице EEPROM
     предполагаем, что размер буфера кратен размеру страницы памяти
     буфер Wire 32, буфер TinyWireM 16
     Страницы памяти 32 64 128 256 байт
  */

  uint8_t One_Page = min(EEPROM_PGSZ, BUFFER_LENGTH);

  uint16_t first_page = addr / One_Page;; // начальная страница памяти
  uint16_t last_page = (addr + len - 1) / One_Page + 1;; // конечная страница памяти
  uint16_t first_byte;// адрес 1-го байта для чтения
  uint16_t end_byte; // адрес последнего байта для чтения
  uint16_t data_pntr = 0;

  for (int i = first_page; i < last_page; i++)
  {
    first_byte = i * One_Page;
    end_byte = (i + 1) * One_Page - 1;
    first_byte = max(addr, first_byte);
    end_byte = min(end_byte, addr + len - 1);
   
    Wire.requestFrom (EEPROM_ADDR, end_byte - first_byte + 1);
    uint8_t cntr = 0;
    while (Wire.available())
    {
      data [data_pntr] = Wire.read ();
      data_pntr++;
      cntr++;
    }  
    if ((end_byte - first_byte + 1) != cntr) return false; // считано меньше чем надо. Ошибка!
  }
  return true;
}

const uint16_t ASize = 50;
byte TestArray[ASize];

//------------------------------------------------------------------------//
void setup() {
  Serial.begin(115200);
  Wire.begin();

  for (int i = 0; i < ASize; i++) TestArray[i] = i;
  StartMEM();

  if (!writeEEPROM_i2c(15, ASize, TestArray)) Serial.println("Write error!");
  else Serial.println("Write Ok!");

  if (!readEEPROM_i2c(15, ASize, TestArray))  Serial.println('Read error!');
  else Serial.println("Read Ok!");

  TestStr Testttt;
  Serial.print("Size:");Serial.println(sizeof(Testttt));
  if (!readEEPROM_i2c(20, sizeof(Testttt), (void*)&Testttt) != 0)  Serial.println("Read error!");
  else Serial.println("Read Ok!");
  Serial.println(Testttt.a);
  Serial.println(Testttt.b);
  Serial.println(Testttt.c);
  Serial.println(Testttt.d);
  Serial.println("Array:");

  for (int i = 0; i < ASize; i++)   Serial.print(String(TestArray[i]) + ' ');

}
//------------------------------------------------------------------------//
void loop()
{

}
 

kostyamat

★★★★★★✩
29 Окт 2019
1,098
631

@Старик Похабыч,
То есть, я не понял, процедуру подбора размера страницы вы делаете при каждом обращении к функции? (На телефоне не удобно код разглядывать, общая картина не видна).
И я вот подумал, ни мое и ни ваше не сможет работать с 24C1024. У нее адрес 17 бит, то есть не пролазит через Wire.
Я когда это писал, не понимал вот этой конструкции
Wire.write ((uint8_t ) (addr >> 8)); // старший байт адреса чтения
Wire.write ((uint8_t ) (addr & 0xFF)); // младший байт чтения.
Просто слямзил с аналогичных кодов. Потому по незнанию и приводил к unsigned long, а компилятор ругался и приводил к нормальному виду.
Теперь же посмотрел код и понял свою не критичную, но таки ошибку.
Так вот, получается, что для корректной работы с 24C1024 эту конструкцию придется модернизировать, разбивая адрес на три байта, но только для 1024.

Спасибо за толчок в нужном направлении, нужно будет поправить несуразности и подумать над 17-ти битным адресом.
 
Изменено:

Старик Похабыч

★★★★★★★
14 Авг 2019
4,220
1,291
Москва
ну да, я подбираю каджый раз. По хорошему его надо в define запихать. я еще думал как то менять под разные размеры, но потом плюнул и оставил как есть.
А мегабитной памяти у меня нет, но там надо передавать 2 слова, т.е. 4 байта адреса, судя по даташиту. Теоретически сделать можно , но как оно будет работать не проверишь.
О точно, надо просто в начале сделать #define HALFSIZE BUFFER_LENGTH /2 и забить на это. будет работать для всех видов.

Да, чего то я перегрелся ) Поставил в дефайн запись делать на пол буфера. Но надо будет попробовать оптимизировать пакеты под весь размер буфера и делать кратно страницам памяти. Сейчас просто кратно страницам памити.

И посмотрел 2мбитную еепром. 18 бит и 2 старших бита задаются в адресе , т.е. у еепрома полчается разный адрес... Вернее адрес на который она отзывается одинаковый, но при обращении надо писать разный в зависимости от памяти . Во...
 
Изменено:

Arhat109

★★★★✩✩✩
9 Июн 2019
473
203
Если размер не меняется на ходу, то лучше в дефайн, кмк. Причем, в таких случаях обрамляю ifdef ом. Если извне НЕ задано, то определяем свой дефайн. Иначе пользуем то, что сказал ползатель. :)
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,220
1,291
Москва
Да, уже поменял в дефай. Это я подбирал и тестировал... А голова уже запудрилась.
Было удивлением. что и в Wire и в TinnyWireM размер буфера не обрамлен ифдефом...

Надо все таки делать страницу запист не половину буфера , а высчитывать исходя из размер страницы микросхемы. моей памяти (не та, что в голове, а та что микросхема) размер страницы 32 кб и какого либо серьезный прирост не видиться.
Имеем 100000 байт на запись.
Если буфер 32 байт и записывать по 16, то
При размере страницы 32 кб имеем 3125 страниц, 6250 эпизодов записи по 5 мс. 31,25 сек
При размере страницы 64 кб имеем 1563 страниц, те же 6250 эпизодов записи по 5 мс. 31,25сек
Если буфер 32 байт и записывать по 25 , то
При размере страницы 64 кб имеем 1563 страниц, те же 4688 эпизодов записи по 5 мс. 23,44 сек, что на 25% быстрее

Если буфер 32 байт и записывать по 27 , то
При размере страницы 128 кб имеем 782 страниц, те же 3910 эпизодов записи по 5 мс , 19.55 сек, что на 35% быстрее

Существенный выигрыш при использовании буфера на относительно больших объемах данных, для 1-2 страниц выиграть 5 мс не всегда интересно
 
  • Лойс +1
Реакции: Arhat109

kostyamat

★★★★★★✩
29 Окт 2019
1,098
631

@Старик Похабыч,
Что-то я полностью потерял нить разговора. Смотрю вашу математику и не совсем понимаю о чем вы.

Смотрите, если исходить из логики работы моего темплейта записи, то эпизод записи наступает только в трёх случаях: передана вся страница; все страницы уже записаны и мы дописали остаток; кончились данные (меньше страницы).
Как результат, количество эпизодов записи никак не зависит от размера чанка, делает его хоть кратным, хоть изголяйся и делай его максимально большим с учётом того, что в него ещё и адрес должен поместится на ряду с данными. Не кратный размер чанка ведёт к потерям на исполнение дополнительного кода, кратный - к потерям на переоткрытие сессии связи с ЕЕПРОМ. Думаю что разница в потерях времени между тем и этим подходом на столько мизерная, что не стоит того, чтобы парить себе голову с дополнительным кодом. Ведь в результате эпизодов записи будет равное количество.

Единственное но, мой темплейт - блокирующий. Если вам нужно решить задачу записи 100000 байт за раз, то вам нужно написать функцию записи с калбеком и какой-то планировщик. Иначе контроллер у вас увиснет на те же 30секунд.
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,220
1,291
Москва
у меня видимо другой алгоритм. я исхожу из 1) размера буфера и 2) размера страницы памяти.
При этом я не пишу и не читаю из разных страниц памяти микросхемы - у меня были неприятные моменты, когда запись "ломалась" при переходе с одной страницы на другую. А читать вроде можно как угодно. Но это я уже перемудрил.
Поэтому я пишу так. Определяю пакет данных для записи в 16 байт. если Пишу с начального адреса до конца страницы, далее постранично заполняю память сколько надо. и в конце с начала страницы до последнего байт.
 

kostyamat

★★★★★★✩
29 Окт 2019
1,098
631

@Arhat109,
Вопрос немного не по теме. А вы не думали над тем, чтобы atmega328 оснастить флешем к примеру 25-й серии, и использовать его для исполняемого кода? Это было бы реально круто. Там бутлоадер нужен наверное свой, который флешку проинициализирует и с нее бутнет прикладную программу.
 

Arhat109

★★★★✩✩✩
9 Июн 2019
473
203
@kostyamat, у меня оно не работа, а так .. домашнее хобби. Начиналось с пристрелом что "дитенка растет, интересуется роботами - пусть учится" но .. Жизнь показала, что его интересе "увял" и если он и ушел то в "чистые питоновцы" .. а интерес остался у меня. :)

atmega328 пользую исключительно в виде Ардуино НАНО и как "макетирование" .. для нормальной работы, построения чего-то "для дома" есть "Ар-го!" .. в проектах тема "Самодельная плата с ОЗУ 520кб" (8 своиз и 512 внешних) .. там флеша 256кб своих, ОЗУ - "за глаза", скоро допилю SD-карт ридер будет еще и "жесткий диск" .. зачем мне "последовательная память" хоть в каком виде?
;)
 

kostyamat

★★★★★★✩
29 Окт 2019
1,098
631

@Arhat109,
Вопрос чисто теоретический, вот смотрю есть моды optiboot для прошивки самого камня с SPI флешки/сдшки. Но нет ни одного проекта, где камень исполняет программу с внешней флеш. Вот и задался вопросом - оно никому не надо, или физически не возможно? И если второе, то в чем причина?
 

Arhat109

★★★★✩✩✩
9 Июн 2019
473
203
@kostyamat, делаю .. но очень медленно, ибо это "хобби" (то есть не на заказ) а времени не так много. :(
 

rGlory

★✩✩✩✩✩✩
11 Май 2021
200
20
Просто поинтересоваться, а зачем в обоих функциях лишнее приведение к void * ?
 

robertverte

✩✩✩✩✩✩✩
15 Фев 2023
1
0
@kostyamat,а не могли бы подсказать, как этим пользоваться? А именно куда этот файл прикрепить или создать библу? А где тогда второй файл?
 

Rgsv

✩✩✩✩✩✩✩
23 Ноя 2023
5
1
@kostyamat,
Спасибо , инструмент крайне полезный. Из-за моего не глубокого знания размеров буферов и особенностей работы Ардуино, никак не удавалось реализовать пакетную запись / чтение ( писалось не всё), библиотека внесла ясность, и избавила от написания велосипеда.

origin:
 if (restChank > 0) {
    if (countChank > 0) addressChank += 32;
В этом месте баг .Если читаем данные размером меньше одного блока то адрес всегда 0.

Должно быть так.
Fix:
if (restChank > 0) { 
    if (countChank > 0)
      addressChank += 32;
    else
      addressChank = eeaddress;

Unit test:
void setup() {
  Wire.begin();
    Serial.begin(115200); 

  Serial.println( "" );
  Serial.println( "------------------------" );
  uint32_t val[16] = {
    101,102,103,104,
    201,202,203,204,
    301,302,303,304,
    401,402,403,404 } ;

  EEPROM_put(0, val);

  memset( val , 0 , 16*sizeof( uint32_t ) ) ;

  EEPROM_get( 0 , val) ;

  Serial.print ("Read pack : ");
  for ( uint8_t i = 0 ; i < 16 ; ++i ) {
    Serial.print( val[i] );
    Serial.print( ";" );
  }

  Serial.println( "" );   
  Serial.println( "\n one_by_one " );

  for ( uint8_t i = 0 ; i < 16 ; ++i ) {
    uint32_t tmp{0};   
    EEPROM_get( i*sizeof( uint32_t ) , tmp) ;   
    Serial.print( tmp );
    Serial.print( ";" );   
  }
  Serial.println( "" );
}
 
Изменено:
  • Лойс +1
Реакции: kostyamat

bort707

★★★★★★✩
21 Сен 2020
2,951
887
@kostyamat
Есть еще один баг - ну или несовместимость.
Библиотека не работает с чипами размером 16Мбит и менее, потому что у них другой принцип адресации.

Сравните
Вот схема записи байта в AT24c32 :
at24c32.JPG

а вот та же картинка из даташита AT24c16:
at24c16.JPG

В первом случае адрес ячейки в ЕЕПРОМ состоит из двух байт, а во втором только из одного.
Ваш код соответствует первому варианту:
Wire.beginTransmission(DEVICE_ADRESS);
Wire.write((uint8_t)(addressChank >> 8));
Wire.write((uint8_t)(addressChank & 0xFF));
Wire.endTransmission();
Поэтому с чипами AT24c16 и ниже код работает неправильно.

Вы пишете в заголовке, что эта библиотека "максимально универсальна".
Инструмент максимально универсален, и пригоден к использованию практически с любой микросхемой серии 24, как 24c16 так и вплоть до 24c512.
Так вот, в этой рекламной фразе :) ошибка - как раз таки с 24c16 ваш инструмент не работает.
Этот код подходит только для чипов ЕЕПРОМ AT24C32 и более.
 

kostyamat

★★★★★★✩
29 Окт 2019
1,098
631

@bort707,
У меня нет сейчас ни 24с16, чтобы проверить в железе, ни необходимости что-то править. Разве что могу поправить шапку. )
Нашли ошибку и хотите, сделайте правки, я их внесу, нет - так нет.
 

Arhat109

★★★★✩✩✩
9 Июн 2019
473
203
@kostyamat, выложите на гитхаб и пусть внесет коммит стестовым покрытием - заапрувили и фсё.. делов-то.
:)
 
  • Лойс +1
Реакции: kostyamat

bort707

★★★★★★✩
21 Сен 2020
2,951
887
@kostyamat,
Спасибо что поправили в шапке.
Но там есть еще одна неточность
Вот:
Увы, но микросхемы 24с1024 и 24с2048, без специальных ухищрений не могут использоваться с этим инструментом, в связи с ограничениями библиотеки Wire из ARDUINO.
Насчет "не могут использоваться" - правда, а вот насчет ограничений библиотеки Wire - чушь. Нет в ардуино никаких ограничений, препятствующих работе с этими чипами, просто в микросхемах 24с1024 и 24с2048 используется третий вариант адресации, про который ваша либа ничего не знает.
Принцип очень похож на тот, что в "маленьких" микросхемах - 24c04, 24c08 и 24c16 - то есть часть адреса ячейки помещается в адрес самой микросхемы.
@Старик Похабыч писал Вам об этом в сообщении #28, только вы не обратили внимания.
 

Yurgen

✩✩✩✩✩✩✩
26 Фев 2024
3
0
Здравствуйте, kostyamat!
А могли бы Вы "подкрутить" инструмент EEPROM24xxx.h, чтобы можно было использовать микросхему PCX8582X-2 (Family 256 x 8-bit CMOS EEPROMS with I2C-bus interface).
Спасибо.