После подробного разбора работы с секторами памяти микросхемы M25P40VP мы переходим к изучению работы с отдельными блоками информации внутри этой микросхемы. Под «блоками информации» здесь подразумеваются любые участки памяти, размером менее целого сектора M25P40VP (а сектор имеет размер 65536 байт).
Это может понадобиться вам в практической работе с M25P40VP, когда нужно стереть, скопировать или переместить некий объём данных внутри одного сектора или из одного сектора в другой.
Организация памяти 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. Сценарий, который нам предстоит реализовать:
1-й шаг. Посекторно стираем всю память чипа M25P40VP и подготавливаем микросхему для дальнейших экспериментов.
2-й шаг. Читаем (первый) блок в 256 байт из 0-го сектора со смещением 100 внутри этого сектора и проверяем корректность стирания памяти микросхемы M25P40VP.
3-й шаг. Заполняем (записываем) первый блок в 256 байт в 0-м секторе со смещением 100 внутри этого сектора байтами со значением 65 (символ «A»).
4-й шаг. Проверяем корректность заполнения первого блока в 0-м секторе символами «A».
5-й шаг. Читаем (второй) блок в 256 байт из 1-го сектора со смещением 200 внутри этого сектора (проверяем корректность проведённого ранее стирания).
6-й шаг. Производим копирование 1-го блока в 256 байт из 0-го сектора (по смещению 100 внутри сектора) во 2-й блок в первом секторе со смещением 200 (256 символов «A»).
7-й шаг. Проверяем корректность проведения операции копирования первого блока в одном секторе во второй блок в другом секторе.
Пояснение. Байт со значением 65 и символ «A» — это одно и то же, это просто разные варианты представления (интерпретации или визуализации) числа 65. Число 65 и его представление в виде символа «A» выбраны нами просто для удобства и компактного представления информации на экране.
Теперь код скетча M25P40 Blocks:
/* M25P40 Blocks */ #include <SPI.h> #include <SPIFlash.h> #define FLASH_SS 8 #define SECTOR_SIZE 65536 #define SHIFT1 100 #define SHIFT2 200 byte tmpArray[256]; SPIFlash flash(FLASH_SS); void setup() { Serial.begin(115200); Serial.println(F("M25P40 Blocks start...")); Serial.print(F("Init ")); if (flash.initialize()) {Serial.println(F("OK"));} else {Serial.println(F("FAIL"));} eraseSectors(); Serial.println(); Serial.println(F("Read Block1 in Sector 0 (+shift 100)")); readBlock(0, SHIFT1); Serial.println(); Serial.println(F("Write Block1 in Sector 0 (+shift 100) (A)")); writeBlock(0, SHIFT1, 'A'); Serial.println(); Serial.println(F("Read Block1 in Sector 0 (+shift 100)")); readBlock(0, SHIFT1); Serial.println(); Serial.println(F("Read Block2 in Sector 1 (+shift 200)")); readBlock(1, SHIFT2); Serial.println(); Serial.println(F("Copy Block 1->2")); copyBlock(0, SHIFT1, 1, SHIFT2); Serial.println(); Serial.println(F("Read Block2 in Sector 1 (+shift 200)")); readBlock(1, SHIFT2); 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 readBlock(byte sec, word sft) { uint32_t adr = sec * SECTOR_SIZE + sft; flash.readBytes(adr, tmpArray, 256); printArray(tmpArray, 256); Serial.println(); } void writeBlock(byte sec, word sft, byte val) { uint32_t adr = sec * SECTOR_SIZE + sft; fillTmpArray(val); flash.writeBytes(adr, tmpArray, 256); } void copyBlock(byte s1, word sft1, byte s2, word sft2) { byte tmpArray[256]; uint32_t adr1 = s1 * SECTOR_SIZE + sft1; uint32_t adr2 = s2 * SECTOR_SIZE + sft2; flash.readBytes (adr1, tmpArray, 256); flash.writeBytes(adr2, 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
Также мы определяем смещения внутри секторов для первого и второго тестовых блоков.
#define SHIFT1 100 #define SHIFT2 200
Затем вводим вспомогательный массив (буфер) 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(); }
Далее проводим операцию чтения первого блока в 256 байт из 0-го сектора памяти M25P40VP (со смещением 100 в этом секторе).
Serial.println(F("Read Block1 in Sector 0 (+shift 100)")); readBlock(0, SHIFT1); Serial.println();
Чтение блока данных производится функцией readBlock(), которой в качестве параметра передаётся номер нужного нам сектора и смещения внутри этого сектора. В этом скетче мы работаем с тестовыми блоками размером 256 байт.
void readBlock(byte sec, word sft) { uint32_t adr = sec * SECTOR_SIZE + sft; flash.readBytes(adr, tmpArray, 256); printArray(tmpArray, 256); Serial.println(); }
Здесь мы вычисляем реальный («линейный») адрес нужной нам ячейки памяти.
uint32_t adr = sec * SECTOR_SIZE + sft;
Далее производим блочное чтение (256 байт) данных из нужного сектора и с заданным смещением.
flash.readBytes(adr, 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 Block1 in Sector 0 (+shift 100) (A)")); writeBlock(0, SHIFT1, 'A'); Serial.println();
Запись сектора производится функцией writeBlock(), которой в качестве параметров передаются номер нужного нам сектора, смещение внутри него и само записываемое значение.
void writeBlock(byte sec, word sft, byte val) { uint32_t adr = sec * SECTOR_SIZE + sft; fillTmpArray(val); flash.writeBytes(adr, tmpArray, 256); }
Здесь мы снова вычисляем реальный («линейный») адрес нужной нам ячейки памяти.
uint32_t adr = sec * SECTOR_SIZE + sft;
Вызываем функцию fillTmpArray()
fillTmpArray(val);
и заполняем с её помощью временный массив tmpArray нужными нам значениями (в данном случае байтами со значением 65).
void fillTmpArray(byte val) { for (int i = 0; i < 256; i++) { tmpArray[i] = val; } }
И далее записываем блок из 256 байт в нужный нам сектор по нужному нам смещению.
flash.writeBytes(adr, tmpArray, 256);
Далее производим контрольное чтение содержимого первого блока.
Serial.println(F("Read Block1 in Sector 0 (+shift 100)")); readBlock(0, SHIFT1); Serial.println();
Затем читаем содержимое второго блока для контроля корректности его стирания вначале работы скетча.
Serial.println(F("Read Block2 in Sector 1 (+shift 200)")); readBlock(1, SHIFT2); Serial.println();
И производим процедуру копирования содержимого первого блока во второй.
Serial.println(F("Copy Block 1->2")); copyBlock(0, SHIFT1, 1, SHIFT2); Serial.println();
Копирование производится функцией copyBlock(), которой в качестве параметров передаются номера исходного сектора и сектора назначения.
void copyBlock(byte s1, word sft1, byte s2, word sft2) { byte tmpArray[256]; uint32_t adr1 = s1 * SECTOR_SIZE + sft1; uint32_t adr2 = s2 * SECTOR_SIZE + sft2; flash.readBytes (adr1, tmpArray, 256); flash.writeBytes(adr2, tmpArray, 256); }
Здесь снова вычисляются нужные нам адреса
uint32_t adr1 = s1 * SECTOR_SIZE + sft1; uint32_t adr2 = s2 * SECTOR_SIZE + sft2;
и производится блочное чтение и запись при помощи временного массива tmpArray в нужные (переданные в качестве параметров) блоки.
flash.readBytes (adr1, tmpArray, 256); flash.writeBytes(adr2, tmpArray, 256);
Далее снова производим контрольное чтение содержимого второго блока.
Serial.println(F("Read Block2 in Sector 1 (+shift 200)")); readBlock(1, SHIFT2); Serial.println();
И в завершение выводим сообщение об окончании работы скетча.
Serial.println(F("DONE"));
Вот результат работы нашего скетча (за пределы экрана выходят блоки данных в 256 байт):
Всё произошло в точности так как мы и планировали — блок из нулевого сектора был заполнен символами «A» и затем скопирован в первый сектор — тестовое задание успешно выполнено.
Заключение
На этом уроке мы научились работать с блоками памяти микросхемы M25P40VP — это важный навык, который пригодиться вам в практической работе с чипом 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