Чтение и запись Flash памяти AT45DB041
Дата: 9 Октября 2014. Автор: Алексей
Понадобилось мне тут мнооого памяти) В общем внешнее EEPROM типа 24LC512 оказалась мелковато для нужд мегадевайса. Блуждая по интернету понял что курить мне придется самую обычную флешку. Но не ту что мы привыкли вставлять в USB порт ПК, а самую что не на есть микросхему. Называется это чудо AT45DB041. Ну съездил в магаз, купил. По даташиту сваял по быренькому отладочную платку.
Далее читаю принцип руления этой штуковиной. Какие-то буфера, страницы. Жуть. Нифига не понял. Ладно думаю, пойду спрошу у Яндекса, Гугла. Ну сказали они мне: "Вот те пару библиотек, одна для AVR другая для STM32". Первая оказалась фигней, ничего не заработало, а искать ошибки в чужом коде, бррр. По STM32 вроде ничего, да зараза под микросхему AT45DB161 и там что-то стоко всего накрутили. Видать с той библой можно микросхему аж за регистры пощупать руками))) Ну да ладно, короче решил я сам разузнать что по чем, так как в просторах великой паутины кроме как урывок с разных форумов я толком ничего не нашел.
Нус начнем. Пишу как я понял. Если где ошибусь, прошу знающих поправить. Начнем с того как устроена память в микросхеме. Как оказалась обратиться к памяти напрямую не так просто. Вот тут и всплыли буфера да страницы. Основная память устроена не сплошняком, а разбита на так называемые страницы, коих в данной микросхеме аж 2048 штуков. Каждая страница несет на своих плечах либо 256 байт, либо 264. Да я тут тоже репу почесал. Как я понял по умолчанию доступны 264 байта, а если ее перенастроить (я даже не заморачивался как это сделать) то на странице будет только 256 байт. Причем, опять как я понял, эта операция безвозвратна. То есть обрезал и радуйся. Чтобы это понять, можно представить память как например, кто помнит была в свое время фотопленка "Свема64", 36 кадров. Вот вся пленка и есть память, а кадры это страницы. То есть работать нужно не со всем массивом, а с одной выбранной страницей. Но к ней напрямую тоже нельзя обратиться. Для того чтобы писать/читать нужен посредник. Вот этой целью и занимаются два буфера. Зовут их Буфер1 и Буфер2. Эти два буфера располагаются в оперативке микросхемы. Да, у флеш памяти есть оперативка и даже больше. Запись в основную память из буфера и чтение из памяти в буфер происходят автоматически. Просто кидаем команду записать и микросхема сама пишет. Говорим читай и она читает. Отсюда появляется алгоритм записи и чтения данных. Давайте ка мы его рассмотрим.
Алгоритм записи, а потом чтения.
- 1. Записать данные в Буфер1/2
- 2. Дать команду на запись данных из буфера1/2 в страницу основной памяти.
- 3. Дать команду на запись данных из страницы в основной памяти в Буфер1/2
- 4. Прочитать данные из Буфера1/2
Теперь давайте этот алгоритм разобьем на четыре функции и пусть каждая занимается своим делом. Назовем их так:
- 1. df_WriteBuffer Запись в буфер
- 2. df_ReadBuffer Чтение буфера
- 3. df_WriteBufferToFlash Записать данные из буфера в память
- 4. df_ReadFlashToBuffer Записать данные из памяти в буфер
Но перед тем как начать писать далее код, давайте договоримся. Я писал все под МК STM32F103C8T6. Отсюда я не буду расписывать как сконфигурировать SPI. Так же нам понадобится функция приема/передачи данных по SPI. Я ее тоже описывать не буду, а просто представим что она работает и называется DF_SPI_RW. Почему я не буду их описывать. Функции которые мы напишем для записи и чтение в память фактически платформонезависимые и их можно использовать как в STM32 так и в AVR и даже в я думаю в PIC. Хотя с последними я вообще никогда не работал, но думаю там не сильно будет отличаться. Для пользователей STM32 бонус. В конце статьи будут лежать два файла под Кокос, которые подключив к проекту можно сразу использовать. По мимо основных функций в этих файлах прописано инициализация SPI1 и порта В который удачно расположен с выводами SPI. То есть эти файлы практически библиотека для флеш-памяти.
Поехали дальше. Вся передача данных в микросхему происходит в полнодуплексном режиме. То есть передача от МК до Флеш и обратно происходит одновременно. По этой причине есть небольшие сложности в общении с флешкой. Но все по порядку. Для выполнения той или иной команды, микросхеме нужно об этом сообщить. Для этих целей есть набор команд. Я выпишу те что нам нужны, а если понадобятся другие, то милости просим в документацию.
- 1. Запись в Буфер 1 код 0x84
- 2. Запись в Буфер 2 код 0x87
- 3. Запись Буфера 1 в память с предварительным стиранием страницы код 0x83
- 4. Запись Буфера 2 в память с предварительным стиранием страницы код 0x86
То есть если нам надо произвести запись в Буфер1, то надо подать команду 0x84. Важно помнить еще то, что передача должна идти всегда старшим битом вперед. Теперь давайте посмотрим как нам собрать функцию для записи данных в буфер. Для этого нам понадобится передать функции несколько аргументов. Первый это номер буфера. Ведь у нас два буфера, вот и надо сказать в какой записываем данные. Для простоты так и назовем BufferNo. Следующий аргумент это адрес байта в буфере. Ведь в буфере 264 байта, значит нужно сказать куда писать. А точнее с какого адреса писать в буфер. Это связано с тем, что команда запись в буфер принимает адрес ячейки памяти, а записать можно хоть все 264 байта сразу. То есть микросхема сама будет увеличивать адрес на единицу при каждой записи байта. Причем нужно самому следить за переполнением. Если количество передаваемых байт превысит объем памяти, то микросхема даже не пикнув продолжит писать но уже с нулевого адреса, переписывая предыдущие данные. Назовем его Addr. И последний аргумент это сам байт данных. Назовем его Data. Вот что у нас получилось.
df_ReadFlashToBuffer(uint8_t BufferNo, uint16_t Addr, uint8_t Data) { }Теперь давайте почитаем про команду записи в буфер. А пишут следующее: Для того чтобы записать данные в буфер, нужно подать команду 0x84, за которым следуют 15 незначащих бита и 9 адресных бит. Что сия абра-кадабра означает. Незначащие биты это просто нули в порт. Но что делать с 9 битами адреса? Байт-то у нас 8 бит. Вот тут умные люди нв Атмеле и приделали 15 незначащих бит. 15+9=24. А, 24/8=3. Да, три байта. То есть сначала кидаем в порт байт команды, затем байт с нулями. Потом старший байт адреса(он может принимать два значения, либо 0x00, либо 0x01). И добиваем все это дело младшим байтом адреса. Давайте все это теперь рассмотрим в коде.
void df_ReadFlashToBuffer(uint8_t BufferNo, uint16_t Addr, uint8_t Data) { uint8_t adr1 = (unsigned char)(Addr>>8); uint8_t adr2 = (unsigned char)Addr; SELECT(); if(BufferNo == 1) { DF_SPI_RW(Buf1Write); } else if (BufferNo == 2) { DF_SPI_RW(Buf2Write); } else {DESELECT(); return; } DF_SPI_RW(0x00); DF_SPI_RW(adr1); DF_SPI_RW(adr2); DF_SPI_RW(Data); DESELECT(); }Все по порядку. В первой строке мы переменной adr1 присваиваем старший байт адрес методом сдвига вправо на 8 бит. В следующей строке мы присваиваем переменной adr2 младший байт адреса. Далее следует строка SELECT(); Я совсем забыл про внешние ноги. Так как микросхема общается по SPI и всегда является ведомой, то на борту имеет ножку выбора микросхемы CS. Если нужно что-то сказать или получить от микросхемы, то нужно просто прижать ножку CS к земле, а когда общаться перестали, то подтягиваем ее к плюсу. Собственно SELECT(); и прижимает с земле. Тем самым мы говорим, что хотим общатся с микросхемой. Дальше идет условие выбора Буфера. Если мы выбрали первый буфер то запишем в микросхему 0x84, а если второй, то 0x87.
Buf1Write = 0x84
Buf2Write = 0x87
Третье условие нужно для того чтобы не промахнуться и не пихать данные в буфер 3, 4, 5 и т.д. После условия выбора буфера идет строка с передачей нулевого байта. Затем старший байт адреса, а за ним младший. Все, микросхема с этого момента готова принимать байты данных. В данной функции мы передаем лишь один байт. И в конце поднимаем CS к плюсу питания говоря что больше нам микросхема не нужна. После того как мы записали данные в буфер, нужно записать в основную память этот буфер. Для этого существует две команды. Первая записывает данные из буфера в страницу памяти с предварительным стиранием страницы, а вторая просто записывает. В чем разница. Все дело в том что запись в память происходит всего буфера, а он равен 264 байтам. То есть если мы записали всего один байт, то все равно запишем все 264. Почему я так и не понял, но перед записью в память нужно сначала стереть всю страницу в которую будет идти запись. Отсюда и две команды. Первая сама все за нас сделает, а вот перед второй соизвольте сами стереть страницу. Разобрались, теперь к делу. Аргументы функции. Первый опять номер буфера. Второй адрес страницы в которую будем записывать буфер.
void df_WriteBufferToFlash(uint8_t BufferNo, uint16_t PageAdr) { uint8_t adr1 = (unsigned char)(PageAdr>>7); uint8_t adr2 = (unsigned char)(PageAdr<<1); SELECT(); if(BufferNo == 1) { DF_SPI_RW(DF_BUF1_TO_FLASH_WITH_ERASE); } else if (BufferNo == 2) { DF_SPI_RW(DF_BUF2_TO_FLASH_WITH_ERASE); } else {DESELECT(); return; } DF_SPI_RW(adr1); DF_SPI_RW(adr2); DF_SPI_RW(0x00); DESELECT(); Delay(MS*20); }В первой строке мы опять записываем старший байт адреса, а во второй младший. Если вы заметили, то сдвиг в первой строке вправо на 7 бит, в во второй влево на 1 бит. В чем тут шаманство. Опять идем смотреть мануал. Для того чтобы записать буфер в страницу с предварительным стирание последней, нужно: Подать команду 0x83 или 0x86 в зависимости от номера буфера. Далее передать 3 зарезервированных бита, затем 12 бит адреса страницы и 9 незначащих бита. Во какая опа. Но тут опять все просто. 3+12+9=24. Опять наши 3 байта. Только тут немного по сложнее. Смотрите.
хххААААААААААААННННННННН я написал последовательность бит, х-зарезервированные, А-адресные и Н-незначащие. Теперь давайте разобьем по байтам.
хххААААА АААААААН НННННННННадеюсь теперь прояснилось) Мы адрес передаем двубайтовой переменной. То есть у нас адрес может принять максимальное значение 2048 -> 0b0000100000000000. Теперь если мы сдвинем значение вправо на 7 бит и присвоим это значение однобайтовой переменной, то получим 0b00010000. Вот они наши первые три зарезервированных бита, а за ними 5 бит адреса. Дальше нам нужно передать оставшиеся 7 бит адреса. Так как адрес в двубайтовои переменной записан с младшего бита, а нам нужно только 7, то просто сдвигаем биты влево на 1 бит. Вот и вся премудрость. Дальше в функции выполняем тоже самое что и при записи в буфер, за двумя но... Первое это нулевой байт шлем после адреса.(Помним 9 незначащих бит), а второй это пауза после того как подняли CS. Она нужна для того чтобы микросхема смогла сама стереть страницу, а потом записать туда буфер. Для справки, смотрим документацию tEP Время стирания и записи страницы 20 мс. Следующая функция записывает в буфер выбранную страницу. Аргументы функции такие же как и при записи буфера в страницу.
void df_ReadFlashToBuffer(uint8_t BufferNo, uint16_t PageAdr) { uint8_t adr1 = (unsigned char)(PageAdr>>7); uint8_t adr2 = (unsigned char)(PageAdr<<1); SELECT(); if(BufferNo == 1) { DF_SPI_RW(DF_FLASH_TO_BUF1); } else if (BufferNo == 2) { DF_SPI_RW(DF_FLASH_TO_BUF2); } else {DESELECT(); return; } DF_SPI_RW(adr1); DF_SPI_RW(adr2); DF_SPI_RW(0x00); DESELECT(); Delay(MS*20); }Собственно в этой функции поменялась лишь команда с записи в страницу на запись в буфер и все. И последняя функция чтение байта из буфера. Аргументы: Номер буфера и адрес байта.
uint8_t df_ReadBuffer(uint8_t BufferNo, uint16_t Addr) { uint8_t data = 0; uint8_t adr1 = (unsigned char)(Addr>>8); uint8_t adr2 = (unsigned char)Addr; SELECT(); if(BufferNo == 1) { DF_SPI_RW(Buf1Read); } else if(BufferNo == 2) { DF_SPI_RW(Buf2Read); } else { DERESET(); return 0; } DF_SPI_RW(0x00); DF_SPI_RW(adr1); DF_SPI_RW(adr2); data = DF_SPI_RW(0x00); DESELECT(); return data; }Здесь мы сначала создаем переменную data. Так как мы читаем байт, то функция должна нам его вернуть. Вот для этих целей и создаем эту переменную. Далее мы подготавливаем адрес, выбираем микросхему(помним CS), выбираем буфер, кидаем незначащие 8 бит, адрес. А вот для получения байта нам нужно послать в порт любой байт. Шлем ноль, а в ответ получаем байт из буфера. Отваливаемся от микросхемы CS. И возвращаем наш байт. Вот и все. Для старта этого вполне достаточно.
Библа которую я наваял для STM32 под CooCox
Мануал на AT45DB081B на русском языке. Любезно переведен http://piclist.ru.
Собственно та же микра, только на 8Mb
Алексей 24.06.17 11:27
Кто нибудь проверял код? собираю с этого кода библиотеку для AVR вопрос встал уже на первой функции:
void df_ReadFlashToBuffer(uint8_t BufferNo, uint16_t Addr, uint8_t Data)
{
uint8_t adr1 = (unsigned char)(Addr>>8);
uint8_t adr2 = (unsigned char)Addr;
SELECT();
if(BufferNo == 1) { DF_SPI_RW(Buf1Write); }
else if (BufferNo == 2) { DF_SPI_RW(Buf2Write); }
else {DESELECT(); return; }
DF_SPI_RW(0x00);
DF_SPI_RW(adr1);
DF_SPI_RW(adr2);
DF_SPI_RW(Data);
DESELECT();
}
- разве это функция не df_WriteBuffer?
в статье так же не нашел значения этих переменных:
DF_BUF1_TO_FLASH_WITH_ERASE
DF_BUF2_TO_FLASH_WITH_ERASE
DF_FLASH_TO_BUF1
DF_FLASH_TO_BUF2
Buf1Read
Buf2Read
может я не внимательно читал или что то не понимаю, но может вы пересмотрите статью?!
void df_ReadFlashToBuffer(uint8_t BufferNo, uint16_t Addr, uint8_t Data)
{
uint8_t adr1 = (unsigned char)(Addr>>8);
uint8_t adr2 = (unsigned char)Addr;
SELECT();
if(BufferNo == 1) { DF_SPI_RW(Buf1Write); }
else if (BufferNo == 2) { DF_SPI_RW(Buf2Write); }
else {DESELECT(); return; }
DF_SPI_RW(0x00);
DF_SPI_RW(adr1);
DF_SPI_RW(adr2);
DF_SPI_RW(Data);
DESELECT();
}
- разве это функция не df_WriteBuffer?
в статье так же не нашел значения этих переменных:
DF_BUF1_TO_FLASH_WITH_ERASE
DF_BUF2_TO_FLASH_WITH_ERASE
DF_FLASH_TO_BUF1
DF_FLASH_TO_BUF2
Buf1Read
Buf2Read
может я не внимательно читал или что то не понимаю, но может вы пересмотрите статью?!
Алексей 24.06.17 18:07
Код рабочий 100% так как я его запускал на живом МК. Если нужны функции под AVR, то у меня в проекте добавить эту память в axlib.
Алексей 25.06.17 13:52
А еще вопрос такой, кажется я его неправильно развязал, WP если не подтянуть к +5 то первые 256 страниц нельзя трогать?
А еще, питание микросхемы 2.5-3.6В?Если так то высокий уровень в SPI не критичен? и если я питал его от 5В то скорее всего сжег?
А еще, питание микросхемы 2.5-3.6В?Если так то высокий уровень в SPI не критичен? и если я питал его от 5В то скорее всего сжег?
Алексей 25.06.17 16:51
WP это блокировка записи. То есть если подтянуть к земле, то можно будет только читать. Если подтянуть к плюсу, то можно читать и писать. Напряжение питание микросхемы от 2,5в до 3,6в. От 5в я боюсь ей поплохеет.
Игорь 25.06.19 17:53
Так что там с планами по переводу функций под AVR :)
Есть уже библиотека чтобы из AVR контроллеров можно было с этой памятью работать?
Есть уже библиотека чтобы из AVR контроллеров можно было с этой памятью работать?
Алексей 25.06.19 21:32
Можно, но нет времени.
Игорь 26.06.19 13:56
Очень жаль :(
Александр 08.10.19 12:06
Самая первая функция дословно означает "считать флэш в буффер", хотя вы ее называете "записать в буфер". В тексте нет ни слова про проверку перед отправкой битов регистра spi. К тому же ставить паузы в коде - очень топорно. Надо либо использовать таймер, либо перед следующим запросом прочитать регистр статуса самой микросхемы, в частности бит RDY. Если он установлен, значет флэш готов к принятию/чтению данных