logo
+7 (951) 999-89-94
428003, г. Чебоксары, ул. Федора Гладкова, д.9, оф.319

Работа с памятью M25P40. Часть 11. Восстановление (backup) секторов

На прошлом уроке мы познакомились с приёмами и методами копирования секторов памяти микросхемы M25P40VP (всего она содержит 8 секторов по 65536 байт). В этой статье мы разберём более приближенный к реальной жизни сценарий использования M25P40VP, а именно, сохранение и последующее восстановление информации из различных секторов этого чипа. Это может понадобиться вам для сохранения, восстановления и дублирования данных и настроек, которые вы будете хранить в M25P40VP на беспроводных контроллерах uniSensors nRF24.

Организация памяти M25P40VP

Вся память микросхемы M25P40VP разбита на несколько зон и доступ к записи и данным в этих зонах может осуществляться разными способами. Со всеми доступными способами вы можете ознакомиться в предыдущих статьях этого цикла, а здесь мы сосредоточимся на изучении посекторного доступа.

Посекторный доступ — это разбиение всего адресного пространства микросхемы M25P40VP на 8 секторов по 65536 байт в каждом. Вот таблица соответствий секторов и их шестнадцатеричных и десятичных адресов:

Сектор 0 — 00000h (0)

Сектор 1 — 10000h (65536)

Сектор 2 — 20000h (131072)

Сектор 3 — 30000h (196608)

Сектор 4 — 40000h (262144)

Сектор 5 — 50000h (327680)

Сектор 6 — 60000h (393216)

Сектор 7 — 70000h (458752)

Также нужно помнить, программирование (изменение) данных (битов) в памяти M25P40VP происходит от 1 к 0, а стирание (посекторно или целиком всей памяти) от 0 к 1.

Сохранение и восстановление (backup) секторов

Сейчас мы создадим скетч, который реализует сценарий записи нужной вам информации в один из 8-и секторов памяти M25P40VP, сохранения (дублирование) этих данных в другом секторе M25P40VP, стирание (заполнение другими данными) начального сектора и восстановление исходных данных в начальном секторе.

В качестве изначального сектора с нужными нам данными будет выступать нулевой сектор, а в качестве сектора для «бакапов» мы используем 7-й (последний) сектор.

Сценарий, который нам предстоит реализовать:

Шаг первый. Посекторно стираем всю память чипа M25P40VP и подготавливаем микросхему для дальнейших экспериментов.

Шаг второй. Проверяем корректность стирания нулевого сектора памяти микросхемы M25P40VP.

Шаг третий. Заполняем (записываем) нулевой сектор байтами со значением 65 (символ «A»).

Шаг четвёртый. Проверяем корректность записи нулевого сектора символами «A».

Шаг пятый. Стираем (подготавливаем для записи) 7-й сектор M25P40VP.

Шаг шестой. Производим полное копирование всего содержимого сектора 0 (65536 символов «A») в сектор 7 (backup).

Шаг седьмой. Проверяем корректность записи и копирования содержимого нулевого сектора в сектор 7. При успешном завершении копирования сектор должен стать заполненным байтами со значением 65 (символ «A»).

Шаг восьмой. Стираем (подготавливаем для записи) 0-й сектор M25P40VP.

Шаг девятый. Заполняем (записываем) нулевой сектор «мусорными» (другими) байтами со значением 70 (символ «F»).

Шаг десятый. Проверяем корректность записи нулевого сектора символами «F».

Шаг одиннадцатый. Стираем (подготавливаем для записи) 0-й сектор M25P40VP.

Шаг двенадцатый. Производим полное копирование всего содержимого сектора 7 (65536 символов «A») в сектор 0 (восстановление).

Шаг тринадцатый. Проверяем корректность записи и восстановления информации в нулевом секторе M25P40VP. При успешном завершении операции восстановления нулевой сектор должен быть заполнен байтами со значением 65 (символ «A»).

Теперь давайте поближе познакомимся с реализацией нашей задачи в коде скетча M25P40 Sector Backup:

/*
  M25P40 Sector Backup
*/

#include <SPI.h>
#include <SPIFlash.h>

#define FLASH_SS 8

#define SECTOR_SIZE 65536

byte tmpArray[256];

SPIFlash flash(FLASH_SS);

void setup() {
  Serial.begin(115200);
  Serial.println(F("M25P40 Sector Backup start..."));

  Serial.print(F("Init "));
  if (flash.initialize()) {Serial.println(F("OK"));}
                     else {Serial.println(F("FAIL"));}

  eraseSectors(); Serial.println();
  Serial.println(F("Read sector 0"));
  readSector(0); Serial.println();
  
  Serial.println(F("Write sector 0 (A) (fill test data)"));
  writeSector(0, 'A'); Serial.println();
  Serial.println(F("Read sector 0"));
  readSector(0); Serial.println();

  Serial.println(F("Erase sector 7 (preparation)"));
  erase64K(7); Serial.println();
  Serial.println(F("Copy sector 0->7 (backup)"));
  copySector(0, 7); Serial.println();
  Serial.println(F("Read sector 7"));
  readSector(7); Serial.println();

  Serial.println(F("Erase sector 0 (preparation)"));
  erase64K(0); Serial.println();
  Serial.println(F("Write sector 0 (F) (fill test)"));
  writeSector(0, 'F'); Serial.println();
  Serial.println(F("Read sector 0"));
  readSector(0); Serial.println();

  Serial.println(F("Erase sector 0 (preparation)"));
  erase64K(0); Serial.println();
  Serial.println(F("Copy sector 7->0 (restoration)"));
  copySector(7, 0); Serial.println();
  Serial.println(F("Read sector 0"));
  readSector(0); Serial.println();
  
  Serial.println(F("DONE"));
}

void eraseSectors() {
  for (byte i = 0; i < 8; i++) {
    erase64K(i);
  }
}

void fillTmpArray(byte val) {
  for (int i = 0; i < 256; i++) {
    tmpArray[i] = val;
  }
}

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

void readSector(byte sec) {
  uint32_t adr = sec * SECTOR_SIZE;
 
  for (int i = 0; i < 256; i++){
    flash.readBytes(adr + i*256, tmpArray, 256);
    printArray(tmpArray, 256);
    //Serial.println();
  }
  Serial.println();
}

void writeSector(byte sec, byte val) {
  uint32_t adr = sec * SECTOR_SIZE;
  
  fillTmpArray(val);

  for (int i = 0; i < 256; i++) {
    flash.writeBytes(adr + i*256, tmpArray, 256);
  }
}

void copySector(byte s1, byte s2) {
  byte tmpArray[256];

  uint32_t adr1 = s1 * SECTOR_SIZE;
  uint32_t adr2 = s2 * SECTOR_SIZE;

  for (int i = 0; i < 256; i++) {
    flash.readBytes (adr1 + i*256, tmpArray, 256);
    flash.writeBytes(adr2 + i*256, tmpArray, 256);
  }
}

uint32_t sectorAdr(byte i) {
  return SECTOR_SIZE * i;
}

void checkBusy() {
  uint32_t stt = millis();
  while (flash.busy()) { }
  uint32_t del = millis() - stt;
  if (del) {
    Serial.print(F(" (")); Serial.print(del); Serial.print(F(" ms)"));
  }
  Serial.println();
}

void erase64K(byte sec) {
  uint32_t adr = sectorAdr(sec);
  Serial.print(F("Sector "));
  Serial.print(sec);
  Serial.print(F(" ("));
  Serial.print(adr);
  Serial.print(F(" + 64K) erase..."));
  
  flash.blockErase64K(adr);
  checkBusy();
}

void loop() {
  
}

Разберём подробно работу скетча. Вначале мы определяем размер каждого из секторов микросхемы M25P40VP.

#define SECTOR_SIZE 65536

Затем вводим вспомогательный массив (буфер) tmpArray для временного хранения 256 байт данных при чтении и записи секторов M25P40VP.

byte tmpArray[256];

Далее мы (посекторно) стираем всё содержимое памяти чипа M25P40VP.

  eraseSectors(); Serial.println();

при помощи функции eraseSectors()

void eraseSectors() {
  for (byte i = 0; i < 8; i++) {
    erase64K(i);
  }
}

которая, в свою очередь, вызывает функцию erase64K() посекторного стирания информации в памяти M25P40VP, которой в качестве параметра передаётся номер текущего (нужного нам в данный момент) сектора.

void erase64K(byte sec) {
  uint32_t adr = sectorAdr(sec);
  Serial.print(F("Sector "));
  Serial.print(sec);
  Serial.print(F(" ("));
  Serial.print(adr);
  Serial.print(F(" + 64K) erase..."));
  
  flash.blockErase64K(adr);
  checkBusy();
}

Далее проводим операцию чтения нужного нам сектора памяти M25P40VP (в данном случае нулевого).

  Serial.println(F("Read sector 0"));
  readSector(0); Serial.println();

Чтение сектора производится функцией readSector(), которой в качестве параметра передаётся номер нужного нам сектора (в данном случае нулевого).

void readSector(byte sec) {
  uint32_t adr = sec * SECTOR_SIZE;
 
  for (int i = 0; i < 256; i++) {
    flash.readBytes(adr + i*256, tmpArray, 256);
    printArray(tmpArray, 256);
    //Serial.println();
  }
  Serial.println();
}

Здесь мы вычисляем реальный («линейный») адрес нужной нам ячейки памяти.

  uint32_t adr = sec * SECTOR_SIZE;

И в цикле из 256 итераций производим блочное чтение (по 256 байт) данных из нужного сектора (256 * 256 = 65536).

  for (int i = 0; i < 256; i++) {
    flash.readBytes(adr + i*256, tmpArray, 256);
    printArray(tmpArray, 256);
    //Serial.println();
  }

Читаем блоки по 256 байт

    flash.readBytes(adr + i*256, tmpArray, 256);

И выводим результат чтения на печать для контроля корректности результата.

    printArray(tmpArray, 256);

Непосредственный вывод содержимого массива производится функцией printArray(), где данные выводятся не в шестнадцатеричной форме, а (для компактности) в виде символов.

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

Далее мы заполняем нулевой сектор байтами со значением 65 (символы «A»).

  Serial.println(F("Write sector 0 (A) (fill test data)"));
  writeSector(0, 'A'); Serial.println();

Запись сектора производится функцией writeSector(), которой в качестве параметра передаётся номер нужного нам сектора (в данном случае нулевого).

void writeSector(byte sec, byte val) {
  uint32_t adr = sec * SECTOR_SIZE;
  
  fillTmpArray(val);

  for (int i = 0; i < 256; i++) {
    flash.writeBytes(adr + i*256, tmpArray, 256);
  }
}

Здесь мы снова вычисляем реальный («линейный») адрес нужной нам ячейки памяти.

  uint32_t adr = sec * SECTOR_SIZE;

Вызываем функцию fillTmpArray()

  fillTmpArray(val);

и заполняем с её помощью временный массив tmpArray нужными нам значениями (в данном случае байтами со значением 65).

void fillTmpArray(byte val) {
  for (int i = 0; i < 256; i++) {
    tmpArray[i] = val;
  }
}

И в цикле записываем блок из 256 байт в нужный нам сектор по нужному нам смещению.

  for (int i = 0; i < 256; i++) {
    flash.writeBytes(adr + i*256, tmpArray, 256);
  }

Далее производим контрольное чтение нулевого сектора.

  Serial.println(F("Read sector 0"));
  readSector(0); Serial.println();

Затем стираем 7-й сектор, тем самым подготавливая его для последующей записи.

  Serial.println(F("Erase sector 7 (preparation)"));
  erase64K(7); Serial.println();

И производим процедуру копирования содержимого сектора 0 в сектор 7, который будем использовать для хранения дублированных данных.

  Serial.println(F("Copy sector 0->7 (backup)"));
  copySector(0, 7); Serial.println();

Копирование производится функцией copySector(), которой в качестве параметров передаются номера исходного сектора и сектора назначения.

void copySector(byte s1, byte s2) {
  byte tmpArray[256];

  uint32_t adr1 = s1 * SECTOR_SIZE;
  uint32_t adr2 = s2 * SECTOR_SIZE;

  for (int i = 0; i < 256; i++) {
    flash.readBytes (adr1 + i*256, tmpArray, 256);
    flash.writeBytes(adr2 + i*256, tmpArray, 256);
  }
}

Здесь снова вычисляются нудные адреса

  uint32_t adr1 = s1 * SECTOR_SIZE;
  uint32_t adr2 = s2 * SECTOR_SIZE;

и производится блочное чтение и запись при помощи временного массива tmpArray в нужные (переданные в качестве параметров) сектора.

  for (int i = 0; i < 256; i++) {
    flash.readBytes (adr1 + i*256, tmpArray, 256);
    flash.writeBytes(adr2 + i*256, tmpArray, 256);
  }
}

Далее производим контрольное чтение содержимого сектора 7.

  Serial.println(F("Read sector 7"));
  readSector(7); Serial.println();

Теперь мы переходим к следующему блоку манипулирования данными внутри микросхемы M25P40VP, смысл которого сводится к следующему: мы стираем (подготавливаем для записи) сектор 0, заполняем его «мусорными» (тестовыми) данными и снова читаем его для контроля заполнения новыми данными. Другими словами, теперь (нужная нам) информация в нулевом секторе полностью стёрта и заменена мусорными данными. И нам предстоит восстановить начальное содержимое нулевого сектора.

  Serial.println(F("Erase sector 0 (preparation)"));
  erase64K(0); Serial.println();
  Serial.println(F("Write sector 0 (F) (fill test)"));
  writeSector(0, 'F'); Serial.println();
  Serial.println(F("Read sector 0"));
  readSector(0); Serial.println();

Далее идёт финальный блок в котором мы восстанавливаем исходные данные нулевого сектора из сохранённого «бекапа» в 7-м секторе и проверяем результат наших действий.

  Serial.println(F("Erase sector 0 (preparation)"));
  erase64K(0); Serial.println();
  Serial.println(F("Copy sector 7->0 (restoration)"));
  copySector(7, 0); Serial.println();
  Serial.println(F("Read sector 0"));
  readSector(0); Serial.println();

Ну и в завершение выводим сообщение об окончании работы скетча.

  Serial.println(F("DONE"));

Вот результат работы нашего скетча:

Разберём подробнее этот скриншот. В первом блоке мы видим вывод о посекторном стирании памяти чипа M25P40VP, которое происходит за 8 приёмов по одному сектору и занимает примерно 0,5 секунды на сектор.

Затем мы видим заполнение (запись) нулевого сектора тестовыми байтами со значением 65 (символ «A») и контрольное чтение нулевого сектора для проверки корректности работы скетча. Здесь тоже виден выходящий за края экрана массив байтов (символов) размером 64К.

Далее мы читаем стираем содержимое 7-го сектора (подготавливаем его для записи), копируем содержимое нулевого сектора в 7-й и проводим его контрольное чтение. Всё верно, теперь 7-й сектор заполнен символами «А» (байтами со значением 65).

Далее заполняем нулевой сектор «мусорными» (тестовыми) данными (в нашем случае символами «F») и проверяем корректность произведённых действий.

И в финальном блоке производим операцию восстановления (стёртых и перезаписанных на предыдущих шагах) данных в нулевом секторе. Отлично! Всё произошло точно по задуманному нами сценарию — данные в нулевом секторе полностью восстановлены.

Заключение

На этом уроке мы разобрали посекторное сохранение (backup) и восстановление данных микросхемы M25P40VP, на следующем уроке мы познакомимся с приёмами и методами работы с блоками данных внутри секторов памяти (блочная запись и чтение).

Ссылки по теме

Обзор контроллера uniSensors nRF24

Спецификации uniSensors nRF24

Программирование uniSensors nRF24

Работа с памятью M25P40. Часть 1. Спецификации и библиотека

Работа с памятью M25P40. Часть 2. Sleep, Wakeup, Erase и Busy

Работа с памятью M25P40. Часть 3. Read и Write Byte и Arrays

Работа с памятью M25P40. Часть 4. Работа с беззнаковыми типами данных

Работа с памятью M25P40. Часть 5. Работа со знаковыми типами данных

Работа с памятью M25P40. Часть 6. Read и Write Float

Работа с памятью M25P40. Часть 7. Read и Write Char array и String

Работа с памятью M25P40. Часть 8. Работа с секторами

Работа с памятью M25P40. Часть 9. Выборочное стирание секторов

Работа с памятью M25P40. Часть 10. Копирование секторов

Работа с памятью M25P40. Часть 11. Восстановление (backup) секторов

Работа с памятью M25P40. Часть 12. Работа с блоками памяти

Работа с памятью M25P40. Часть 13. Пишем библиотеку для M25P40

Работа с памятью M25P40. Часть 14. Пишем библиотеку для M25P40 (2)

Работа с памятью M25P40. Часть 15. Пишем библиотеку для M25P40 (3)

Где купить?

uniSensors nRF24 в магазине «Electromicro»

Техническая поддержка

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

  • Емейл для вопросов по нашей продукции: electromicro@bk.ru
  • Наш телефон: +7 (495) 997-37-74

Аналогичные товары