Чтение и запись флеш-памяти с помощью Arduino на примере микросхемы 25L8005
1Микросхема флеш-памяти25L8005
Микросхема флеш-памяти (или ПЗУ) 25L8005 изготавливается в разных корпусах. Но как правило, ввиду малого размера, удобнее использовать микросхемы поверхностного монтажа. У меня как раз такая. Она выполнена в корпусе типа SOP 8L.
Поэтому сначала нужно сделать так, чтобы мы могли с лёгкостью подключиться к микросхеме флеш-памяти. Для этого можно воспользоваться либо специальным переходником, к которому придётся припаять микросхему, либо (что предпочтительнее) использовать панель с нулевым усилением (т.н. панель ZIF, купить можно на Али).
Так выглядит наша микросхема ПЗУ, припаянная к плате-переходнику с DIP-выводами:
А вот так выглядит микросхема флеш-памяти в ZIF-панели:
И под микроскопом:
Купить ПЗУшку можно, например, в Китае.
Теперь мы можем использовать макетную плату (бредборд) и с лёгкостью подключаться к микросхеме.
2Подключение Arduino к микросхеме flash-памяти 25L8005
Назначение выводов микросхемы ПЗУ, приведённое в техническом описании на микросхему (datasheet), следующее:
Кстати, datasheet на микросхему флеш-памяти 25L8005 можно скачать в конце статьи.
Будем использовать для программирования флэш-памяти интерфейс SPI, поэтому подключимся к стандартным SPI выводам Arduino:
Вывод микросхемы флеш-памяти | Вывод платы Arduino |
---|---|
CS# | цифровой пин 10 |
SI | цифровой пин 11 |
SO | цифровой пин 12 |
SCLK | цифровой пин 13 |
WP# | 3V3 |
HOLD# | 3V3 |
VCC | 3V3 |
GND | GND |
Здесь символом решётки # отмечены выводы, которые активируются логическим нулём.
Соберём электрическую схему подключения микросхемы флеш-памяти MX25L8005 к Arduino.
У меня ПЗУ на монтажной плате и подключённое к Arduino выглядит вот так:
3Очистка флеш-памяти с помощью Arduino
Перед тем как записывать данные в флеш-память, необходимо стереть тот сектор или страницу, в который мы собираемся записывать. Если записываемых данных немного (в нашем учебном примере это будут всего 16 байт), то достаточно стереть 1 сектор. Из документации на микросхему мы видим, что последовательность стирания такая: выставить разрешение на запись (1 байт), послать команду на стирание (1 байт) и адрес (3 байта), выставить запрет записи (1 байт). Данная последовательность приведена на рисунке ниже:
Именно это и делает приведённый ниже скетч:
#include <SPI.h> const int SSPin = 10; const byte WREN = 0x06; const byte WRDI = 0x04; const byte SER = 0x20; const byte ADDR1 = 0; const byte ADDR2 = 0; const byte ADDR3 = 0; void setup() { pinMode(SSPin, OUTPUT); SPI.begin(); SPISettings mySet(100000, MSBFIRST, SPI_MODE0); // Выставление разрешения записи: SPI.beginTransaction(mySet); digitalWrite(SSPin, LOW); SPI.transfer(WREN); digitalWrite(SSPin, HIGH); SPI.endTransaction(); // Очистка сектора: SPI.beginTransaction(mySet); digitalWrite(SSPin, LOW); SPI.transfer(SER); SPI.transfer(ADDR1); SPI.transfer(ADDR2); SPI.transfer(ADDR3); digitalWrite(SSPin, HIGH); SPI.endTransaction(); // Выставление запрета записи: SPI.beginTransaction(mySet); digitalWrite(SSPin, LOW); SPI.transfer(WRDI); digitalWrite(SSPin, HIGH); SPI.endTransaction(); } void loop() { // ничего не делаем в цикле }
Загрузим этот скетч в Arduino с подключённой по приведённой выше схеме микросхемой памяти. После того как он отработал, флешка готова к записи.
4Запись данных в флеш-память с помощью Arduino
Теперь запишем на неё данные. Для примера возьмём небольшой массив из 16-ти байтов. Как видно из документации, для записи данных во флеш сначала нужно выставить разрешение на запись (1 байт), затем послать команду на запись (1 байт), передать начальный адрес (3 байта) и данные (в нашем примере 16 байт), а в конце выставить запрет записи (1 байт):
Напишем скетч, который записывает массив из 16-ти байт данных в ПЗУ:
#include <SPI.h> const int SSPin = 10; const byte WREN = 0x06; const byte WRDI = 0x04; const byte READ = 0x03; const byte PP = 0x02; const byte ADDR1 = 0; const byte ADDR2 = 0; const byte ADDR3 = 0; void setup() { pinMode(SSPin, OUTPUT); SPI.begin(); SPISettings mySet(100000, MSBFIRST, SPI_MODE0); // Выставление разрешения записи: SPI.beginTransaction(mySet); digitalWrite(SSPin, LOW); SPI.transfer(WREN); digitalWrite(SSPin, HIGH); SPI.endTransaction(); // Запись массива данных в ПЗУ: SPI.beginTransaction(mySet); digitalWrite(SSPin, LOW); SPI.transfer(PP); SPI.transfer(ADDR1); SPI.transfer(ADDR2); SPI.transfer(ADDR3); byte data[] = {0x48,0x45,0x4c,0x4c,0x4f,0x2c,0x20,0x53,0x4f,0x4c,0x54,0x41,0x55,0x2e,0x52,0x55}; for (int i=0; i<sizeof(data); i++) { SPI.transfer(data[i]); } digitalWrite(SSPin, HIGH); SPI.endTransaction(); // Выставление запрета записи: SPI.beginTransaction(mySet); digitalWrite(SSPin, LOW); SPI.transfer(WRDI); digitalWrite(SSPin, HIGH); SPI.endTransaction(); } void loop() { // ничего не делаем в цикле }
Загрузим скетч в Arduino. Кстати, вот так выглядит на логическом анализаторе обмен по SPI между Arduino и ПЗУ 25L8005, когда выполняется данный скетч.
После выполнения данного скетча во флеш-память должен был записаться наш тестовый массив. Давайте проверим, так ли это.
4Чтение данных из флеш-памяти с помощью Arduino
Согласно документации, чтение из флешки выполняется посредством такой последовательности: отправка команды на чтение (1 байт), начальный адрес (3 байта), а далее запрашивается столько байтов, сколько хотим прочитать из ПЗУ. Собственно, мы будем передавать в ПЗУ 16 нулей. Так как SPI – синхронный интерфейс, нам в ответ вернутся 16 записанных в ПЗУ байтов. Вот такая диаграмма приводится в описании к микросхеме:
Напишем скетч для чтения наших заветных 16-ти байт из микросхемы флеш-памяти 25L8005:
#include <SPI.h> const int SSPin = 10; const byte READ = 0x03; const byte ADDR1 = 0; const byte ADDR2 = 0; const byte ADDR3 = 0; void setup() { Serial.begin(9600); pinMode(SSPin, OUTPUT); SPI.begin(); } void loop() { SPISettings mySet(100000, MSBFIRST, SPI_MODE0); SPI.beginTransaction(mySet); digitalWrite(SSPin, LOW); SPI.transfer(READ); SPI.transfer(ADDR1); SPI.transfer(ADDR2); SPI.transfer(ADDR3); for (int i=0; i<16; i++) { byte data = SPI.transfer(0); Serial.print((char)data); } Serial.println(); digitalWrite(SSPin, HIGH); SPI.endTransaction(); delay(1000); // повторяем чтение каждую секунду }
Загрузим скетч в Arduino и откроем монитор последовательных портов. В мониторе, как и ожидалось, 1 раз в секунду будет выводиться наш массив, считанный из флеш-памяти с помощью Arduino.
Вот как выглядит чтение из ПЗУ, если подключиться к линиям SPI логическим анализатором:
Как видно, байты этого массива соответствуют кодам ASCII строки "HELLO, SOLTAU.RU", которые мы и записали в микросхему памяти 25L8005 :-)
Download attachments:
Поблагодарить автора:
Поделиться
Related items
11 comments
-
Гриша Вторник, 17 Декабрь 2019 20:51 Ссылка на комментарий
Спасибо, все понятно написано, благодаря вашей статье быстро разобрался!
-
Никита Воскреснье, 26 Апрель 2020 16:31 Ссылка на комментарий
Доброго времени суток!
Подскажите, инструкция подходит для работы с W25Q64 ?
Пока в наличии нет (жду посылку), ищу инструкцию для работы с указанной flash памятью.
Буду благодарен за помощь! -
aave1 Воскреснье, 26 Апрель 2020 16:57 Ссылка на комментарий
Никита, доброго времени!
Я не изучал подробно вашу ПЗУшку W25Q64, но с первого взгляда она очень похожа на описываемую здесь. По крайней мере, подключение аналогично (если берём в том же корпусе), а на 21-ой странице datasheet приведён набор инструкций SPI, в котором команды чтения, записи, разрешения записи и некоторые другие аналогичны. Надо, конечно, ознакомиться внимательнее, но в целом ничего сложного тут нет. Действуйте согласно документации разработчика. Соберите схему и начните изучать работу с микросхемой с чтения регистров. Хорошие кандидаты для начала - регистры Device ID (0x90), JEDEC ID (0x9F). Если "пощупаете" их, прочитаете значения, которые должны там быть согласно документации, то станет понятно, что вы на верном пути, и это придаст уверенности в дальнейшем изучении. -
Сергей Четверг, 19 Ноябрь 2020 17:58 Ссылка на комментарий
Доброго времени суток! Прочитал и отработал ваши примеры в данной статье на микросхеме флэш памяти W25Q32FV. Спасибо за материал. Всё работает нормально. Начал экспериментировать с кодом.
В вашем примере адрес передается тремя байтами ADDR1, ADDR2, ADDR3. Разобрался по даташиту со структурой памяти W25Q32FV но всё равно возникли вопросы.
Как записать данные в другой адрес отличного от примера? То есть, как записать и как разбить, например адрес типа 8192 (0x2000h) начального байта сектора 2, на три байта и приписать их переменным ADDR1, ADDR2, ADDR3 ??? Пробовал по разному. У меня получилось только побитовой передачей в цикле for.
ПРИМЕР:
const byte ADDR[24] = {0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0}; //24-битный адрес 0x2000h в двоичном виде и в массиве
for(byte b=0; b = 24; b++){SPI.transfer(ADDR[b]);} //предаём 24-битный адрес побитно, вставлял его вместо ADDR1....ADDR3 -
Сергей Четверг, 19 Ноябрь 2020 18:03 Ссылка на комментарий
И ещё вопрос не по теме:
В ардуино к сожалению не работает оператор round с указанием количества знаков после запятой.
То есть, выражение:
float number = 1,674938;
float VOLT = round(number,2); //округление до двух знаков после запятой.
НЕ РАБОТАЕТ!!! Оператор "round" в ардуино умеет округлять только до целых чисел.
Работает следующий вариант:
float number = 1,674938;
int NUM = round(number * 100); //умножаем на 100 и округляем до целых чисел. Получаем 167.
float VOLT = float(NUM / 100); //167 делим на 100 и получаем 1.67
Это я проверял. Это правда или я что-то не так делаю? -
aave1 Суббота, 21 Ноябрь 2020 19:01 Ссылка на комментарий
Сергей, что касается записи ПЗУшек, то в двух словах, ADDR1...ADDR3 - это 24-разрядный адрес, который следует читать как одно большое число. Например, адрес 8192 будет записываться как 0x002000 - теми же 24-мя битами (3 байта). Судя по тому, что я вижу в даташите, в вашем случае адрес передаётся аналогично - 3-мя байтами. Адресное пространство ПЗУшки состоит из 64-х блоков по 64 кб, т.е. в каждом блоке адреса с 0x000000 по 0x00ffff.
-
aave1 Суббота, 21 Ноябрь 2020 19:02 Ссылка на комментарий
А что касается округления чисел... В штатной библиотеке ардуино функция round() умеет округлять только до целого числа, так и есть. Ваш вариант с умножением и потом делением рабочий. Можно ещё вот так сделать:
double p = 3.14159265359;
void setup()
{
Serial.begin(9600);
Serial.println(p, 10); // выводим с точностью до 10 знаков
Serial.println(roundTo(p, 2), 10); // округляем до 2-х знаков и выводим 10 после запятой
Serial.println(roundTo(p, 3), 10); // округляем до 3-х и выводим 10
Serial.println(roundTo(p, 4), 10); // округляем до 4-х и выводим 10
}
double roundTo(double value, int decimal)
{
double pwr = pow(10, decimal);
return (round(value * pwr) / pwr);
}
Вывод данного скетча будет:
3.1415927410
3.1400003433
3.1420006752
3.1416006088
Если обратите внимание, даже вывод числа с такой большой точностью в первой строчке уже некорректен. Микроконтроллер не очень дружит с точной математикой. -
Аллайя Воскреснье, 15 Август 2021 21:37 Ссылка на комментарий
Может кому пригодится.
Подключение флешек на 1.8 вольта - https://disk.yandex.ru/i/TAQuHij2_kf1Fg