Справочник программиста Ардуино C++

Как постоянно хранить данные на вашем Arduino

Узнайте, как использовать внутреннюю EEPROM вашего Arduino и как добавить дополнительную память для постоянного хранения данных.

Комплектующие

  • Arduino плата
  • 24LC16B микросхема EEPROM

Некоторые платы Arduino позволяют вам постоянно хранить данные в EEPROM без необходимости подключения платы. Эта статья научит вас писать в встроенную EEPROM (если она есть в вашем Arduino) и познакомит вас с альтернативным методом, который позволит вам использовать внешнюю память.

Вы можете использовать это руководство для любого микроконтроллера, который поддерживает связь по шине I2C.

Поддерживаемые платы Arduino

В следующей таблице указано, сколько данных может хранить каждый MCU (Microcontroller Unit — микроконтроллер):

Микроконтроллер Размер EEPROM
ATmega328P 1 Кб
ATmega168 и ATmega8 512 байт
ATmega1280 и ATmega2560 4 Кб
Arduino / Genuino 101 1 Кб (эмулируется)

Взаимодействие со встроенной EEPROM

EEPROM расшифровывается как электронно стираемое программируемое постоянное запоминающее устройство (англ. Electrically Erasable Programmable Read-Only Memory). Хотя вы можете перезаписать данные на чипе, вы сможете сделать это только ограниченное количество раз, прежде чем он начнет работать со сбоями. Тем не менее, вы можете читать с него столько раз, сколько захотите.

Метод записи write()

В следующем примере показано, как можно сохранить байт.

#include <EEPROM.h>

void setup()
{
    int word_address = 0;
 
    EEPROM.write(word_address, 0x7F);
}

void loop()
{ }

Используйте метод write() вместе с адресом и значением, которое вы хотите сохранить. Адрес должен быть значением от нуля до EEPROM.length()-1, и он сообщает MCU, где хранить значение.

Метод read()

Следующий пример читает байт из EEPROM:

#include <EEPROM.h>

void setup()
{
    Serial.begin(9600);

    int word_address = 0;
    byte value;
 
    value = EEPROM.read(word_address);
 
    Serial.println(value, HEX);
}

void loop()
{ }

Метод read() также примет адрес в качестве параметра и вернет значение в виде байта.

Очистка памяти

Чтобы очистить память, сохраняйте ноль в каждой позиции EEPROM:

void erase(void)
{
    for (int i = 0 ; i < EEPROM.length() ; i++)
        EEPROM.write(i, 0);
}

Пример "Hello World"

Следующий код очистит EEPROM и сохранит в нем «Hello World!» перед записью строки в консоль:

#include <EEPROM.h>

void erase(void)
{
  for (int i = 0 ; i < EEPROM.length() ; i++)
	EEPROM.write(i, 0);
}

void printMessage(byte* first, size_t len)
{
  for (int i = 0; i < len; i++)
  {
	Serial.print((char)first[i]);
  }
}

void writeMsg(byte* first, size_t len)
{
  for(int i = 0; i < len; i++)
  {
	EEPROM.write(i, first[i]);
  }
}

void readMsg(size_t len)
{
  byte res;
 
  Serial.print("Message: ");
  for(int i = 0; i < len; i++)
  {
	res = EEPROM.read(i);
	Serial.print((char)res);
  }
  Serial.println("");
}

void setup()
{
  char* string = "Hello World!";
 
  Serial.begin(9600);
 
  Serial.print("Serial connection opened!\n");
  Serial.print("EEPROM length: ");
  Serial.println(EEPROM.length());
 
  Serial.print("Attempting to erase EEPROM... ");
  erase();
  Serial.print("Done!\n");
 
  Serial.print("Message: ");
  printMessage(string, 12);
  Serial.print("\n");
 
  Serial.print("Attempting to write to EEPROM...\n");
  writeMsg(string, 12);
  Serial.print("Done!\n");
  Serial.print("Attempting to read from EEPROM...\n");
  readMsg(12);
  Serial.print("Done!\n");
}

void loop()
{ }

Использование внешнего EEPROM

Если вы не используете Arduino или хотите иметь дополнительное место для хранения, вы можете использовать внешнюю EEPROM IC для хранения байтов. В этом примере мы будем использовать 4LC16B (PDF), который является 162 КБ EEPROM I2C.

Схема соединения проста и вам нужно всего лишь добавить нагрузочный резистор 100 кОм и подключить микросхему к Arduino (или любому другому MCU, который вы хотите использовать):

Седьмой вывод этой микросхемы - индикатор защиты от записи. Подключите этот вывод к GND, если вы хотите записать в память. Если он высокий, чип не сохранит никаких данных. Чтение возможно независимо от состояния пина.

Связь с внешней памятью

Настраивая связь между Arduino и внешней памятью, все становится сложнее по сравнению со встроенной памятью.

Спецификация IC 4LC16B точно описывает, как связаться с ней для хранения данных. Используйте скетч ниже для связи с внешней EEPROM. Я протестировал его с вариантом 16 КБ, но он должен работать с любым другим размером (от этого производителя), если связь работает одинаково:

#include <Wire.h>

static const byte DEVICE_BASE_ADDRESS = 0x50;

void setup()
{
  Wire.begin();
  Serial.begin(9600);
}

byte readByteFromEEPROM(byte block, byte word_offset)
{
   Wire.beginTransmission(block);
   Wire.write(int(word_offset));
   Wire.endTransmission(true);
  
   Wire.requestFrom(int(block), 1);
  
   if (Wire.available())
   	return Wire.read();
}

void readBlockFromEEPROM(byte block, byte outArray[256])
{
  for(int i = 0; i < 256; i++)
  {
	outArray[i] = readByteFromEEPROM(block, i);
  }
}

void readPageFromEEPROM(byte block, byte word_offset, byte outArray[16])
{
  for(int i = 0; i < 16; i++)
  {
	outArray[i] = readByteFromEEPROM(block, word_offset + i);
  }
}

void writeByteToEEPROM(byte block, byte word_offset, byte data)
{
  writePageToEEPROM(block, word_offset, &data, 1);
}

/**
 * block:
 * 0x50 = first block = DEVICE_BASE_ADDRESS
 * 0x51 = second block
 * ...
 * 0x57 = eight block
 */

void writePageToEEPROM(byte block, byte word_offset, byte *data, size_t len)
{
  Wire.beginTransmission(block);
  Wire.write(word_offset);
 
  for(int i = 0; i < len; i++)
  {
	Wire.write(data[i]);
  }
 
  Wire.endTransmission(true);
  delay(10);
}

Память состоит из восьми блоков по 256 байт, и каждый блок может быть адресован напрямую. DEVICE_BASE_ADDRESS (0x50) представляет первый, а 0x57 - последний блок.

Разные чипы тоже имеют разные базовые адреса. Обратитесь к таблице данных вашей EEPROM и обновите код, если это необходимо.

Еще один пример "Hello World"

Эта программа сохранит «Hello World» на первой странице первого блока на внешней EEPROM, а затем прочитает весь первый блок и выведет его на консоль:

void printBlock(byte blockContent[256])
{
  for(int i = 0; i < 16; i++)
  {
	Serial.print("Page ");
	
	if(i+1 < 10)
  	    Serial.print("0");
  	
	Serial.print(i+1);
	Serial.print(": ");
	
	for(int u = 0; u < 16; u++)
	{
   	    Serial.print((char)blockContent[i*16+u]);
   	
   	    if(u==7)
      	        Serial.print(" ");
	}
	
           Serial.println("");
  }
}

void loop()
{
  byte result[256];
  writePageToEEPROM(DEVICE_BASE_ADDRESS, 0, "Hello World!", 12); 
  readBlockFromEEPROM(DEVICE_BASE_ADDRESS, result);
  printBlock(result);
  delay(20000);
  exit(0);
}

Выполнение этого примера даст вам следующий результат (или что-то похожее в зависимости от содержимого вашей EEPROM):

Понимание EEPROM важно для начинающих

Этот очень простой проект идеально подходит для начинающих, которые хотят узнать о памяти и коммуникациях I2C (и всех, кто хочет повысить свое мастерство), потому что он позволяет вам постоянно хранить информацию на вашем Arduino (или другом MCU) даже без питания.

Знание того, как хранить информацию, делает ваши проекты мобильными, а также дает вам уверенность в том, что самые важные части вашего проекта хранятся независимо от того, подключена ли ваша плата к питанию или нет.

/ 15.03.2019

Ардуино+