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

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

Поскольку вся память микросхемы M25P40VP на физическом уровне разбита на 8 секторов, то и работа с ней предусматривает постоянное обращение к этим секторам и различное манипулирование данными, находящимися в них (стирание, запись, перезапись и т. д.). Поэтому мы не можем обойти своим вниманием приёмы и методы работы с секторами памяти 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.

Копирование секторов

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

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

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

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

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

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

Шаг пятый. Читаем содержимое сектора 1. После начального стирания он должен содержать байты 0xFF (символы «я»).

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

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

Небольшое пояснение. В принципе, байт со значением 65 и символ «A» — это одно и то же, это просто разные варианты представления (интерпретации или визуализации) числа 65. Число 65 и его представление в виде символа «A» выбраны нами просто для удобства и компактного представления информации на экране.

Теперь код скетча M25P40 Sector copy:

/*
  M25P40 Sector copy
*/

#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 copy 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)"));
  writeSector(0, 'A'); Serial.println();
  
  Serial.println(F("Read sector 0"));
  readSector(0); Serial.println();
  
  Serial.println(F("Read sector 1"));
  readSector(1); Serial.println();

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

  Serial.println(F("Read sector 1"));
  readSector(1); Serial.println();
  
  Serial.println(F("DONE"));
} // setup()

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;
}

String hexy(byte b) {
  String s = F("0x");
  if (b < 16) {s += '0';}
  String s2 = String(b, HEX);
  s2.toUpperCase();
  return s + s2;
}

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)"));
  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();

Затем читаем содержимое сектора 1 для контроля корректности его стирания вначале работы скетча.

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

И производим процедуру копирования содержимого сектора 0 в сектор 1.

  Serial.println(F("Copy sector 0->1"));
  copySector(0, 1); 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);
  }
}

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

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

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

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

Вот результат работы нашего скетча, красным цветом указаны не вошедшие в экран массивы символов размером 64К (65536 байт — полный размер одного сектора):

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

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

Далее мы читаем содержимое сектора 1, которое, как и ожидалось, после стирания в начале скетча составляют байты со значением 0xFF (символ «я»).

Производим операцию копирования содержимого нулевого сектора (0) в первый (1).

И снова читаем содержимое сектора 1, которое теперь составляет байты со значением 65 («A»). Отлично, всё произошло именно так, как мы планировали — тестовое задание успешно выполнено.

Заключение

На этом уроке мы ещё раз поработали с секторами микросхемы M25P40VP и научились копировать данные из одного сектора памяти M25P40VP в другой. Эти знания и навыки, несомненно, пригодятся вам при работе над своими проектами на основе беспроводного контроллера uniSensors nRF24. На следующем уроке мы продолжим эту тему и разберём сохранение и восстановление (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

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