STM32 - SPI флешка W25Qxx





Здравствуйте.

Библиотека для работы микроконтроллера stm32 c SPI-флешками W25Qxx…

W25Q512
W25Q256
W25Q128
W25Q64
W25Q32
W25Q16
W25Q80
W25Q40
W25Q20
W25Q10


Так же должно работать с W25Xxx.

Со всеми я конечно не проверял, но W25Q128 и W25Q64 точно работает.

Почитать подробнее про сабж можно здесь, и в даташите.


Эти флешки устроены так же как флеш-память у stm32, то есть память у них разбита на условные страницы по 256 байт, страницы объединяются в сектора по 4096 байт, а сектора в блоки по 65536 байт. Организацию флешки можно посмотреть на схеме…

схема

Страниц здесь не показано (только сектора и блоки), но условно они есть.


Перед тем как что-то записать, нужно стереть сектор (4096 байт) или блок (65536 байт). Можно стереть несколько секторов или блоков, или весь чип полностью. Во время стирания ячейки заполняются значениями 0xFF. Да, слово «стирание» не очень то верно отражает суть этого процесса, но ничего не поделаешь, так назвали.

почему 0xFF
Почему нужно стирать (заполнять значениями 0xFF) память?

Дело в том, что когда происходит запись байта в ячейку, то биты в этой ячейки не просто так берут и перезаписываются, а совершается операция логического «И» над тем что есть в ячейке и новым значением.

Наглядно это выглядит так. Ячейка у нас стёрта (биты заполнены единицами) и мы записываем в неё число 7 (0х07)


Выполняется логическое «И», и в ячейку записывается нужное нам число 7.

А теперь допустим что мы хотим записать в эту же ячейку число 13 (0x0d) поверх старого значения…


Выполняется логическое «И» между старым значением 0х07 и новым 0x0d. В результате вместо желаемого числа 13, в ячейку записывается число 5.


Сектор, это минимальный кусочек памяти, который можно стереть. То есть, если нам нужно записать один, два, пять или сто байт, то в любом случае придётся стирать весь сектор. Однако как вы уже поняли (посмотрев спойлер выше), это не значит что после того как вы запишите в этот сектор несколько байт, а потом захотите дописать ещё, его нужно опять стирать. Дописывать данные можно, но нельзя их перезаписывать поверх старых. Впрочем записать то конечно можно, но только запишется не то что нужно . Если хотите перезаписать, тогда нужно предварительно стереть.


При старте программа покажет маркировку чипа, размеры и количество страниц, секторов и блоков...




Стирание

Сектора…

W25qxx_EraseSector(0);

Аргумент, это номер сектора, в данном случае будет стёрт нулевой сектор, то есть с нулевой по 4095-ую ячейки. Если указать 1, тогда будут стерты ячейки с 4096 по 8192, и т.д.


Блока…

W25qxx_EraseBlock(0);

Аргумент, это номер блока, в данном случае будет стёрт нулевой блок, то есть с нулевой по 65535-ую ячейки. Если указать 1, тогда будут стерты ячейки с 65536 по 131072, и т.д.


Стирание чипа полностью…

W25qxx_EraseChip();



Запись и чтение данных

Записывать данные можно по разному. Если нужно записать один или пару-тройку байт тогда достаточно функции W25qxx_WriteByte()

uint8_t b0 = 's';
uint8_t b1 = 't';
uint8_t b2 = 'D';

W25qxx_WriteByte(b0, 25);
W25qxx_WriteByte(b1, 26);
W25qxx_WriteByte(b2, 27);

Записываем три байта в 25-ую, 26-ую и 27-ую ячейки.

Эта функция не привязана к страницам, секторам или блокам, мы просто указываем любой адрес от нуля до максимального размера флешки.


Чтение байтов

uint8_t buf[64] = {0,};

W25qxx_ReadByte(&buf[0], 25);
W25qxx_ReadByte(&buf[1], 26);
W25qxx_ReadByte(&buf[2], 27);



Когда нужно записывать большое количество данных, тогда используются другие функции, но прежде чем говорить о них надо сделать некоторые пояснения. Есть функции для записи страницы, сектора и блока, однако за один раз можно записать не больше 256 байт (одну страницу).

Это связано с тем, что у флешки есть внутренний буфер на 256 байт (на схеме он справа-снизу), и когда мы записываем данные, то они сначала помещаются в этот буфер, а оттуда уже перемещаются в ячейки памяти. Как только данные переместятся из буфера на своё место, флешка отправит команду что операция записи выполнена и можно подавать следующую порцию. Соответственно если попытаться запихать во флешку сразу же больше 256 байт, то она не сможет их «переварить».

Таким образом получается следующее:

Если нужно записать не больше 256 байт, используем функцию для записи страницы…

uint8_t w_buf[] = "istarik.ru";

W25qxx_WritePage(w_buf, 0, 0, 10);

Первый аргумент это буфер, который хотим записать. Второй — это порядковый номер страницы (страницы, сектора и блоки нумеруются от нуля), то есть записываем в нулевую страницу. Третий — это сдвиг на какое-то кол-во байт, в данном случае мы не сдвигаемся, а пишем в самое начало нулевой страницы.

Чтоб было понятнее: если бы мы хотели записать эту строчку после тех трёх байт, тогда сдвинули бы запись на 28 ячеек, то есть так…

W25qxx_WritePage(w_buf, 0, 28, 10);


Если бы хотели записать в начало первой страницы, тогда второй аргумент был бы 1…

W25qxx_WritePage(w_buf, 1, 0, 10);

Думаю с нумерацией всё понятно. Сектора и блоки нумеруются так же.

Последний аргумент, это количество байт для записи.


Теперь очень важный нюанс. Если мы хотим записать 256 байт, тогда нужно указывать начало страницы, сдвиг делать нельзя. То есть, если мы сделаем так…

W25qxx_WritePage(w_buf, 0, 20, 256);

… тогда 236 байт запишутся как положено, а остальные не перейдут на следующую страницу, они потеряются. То есть, если вы записываете какое-то количество байт не в начало страницы, тогда это количество должно умещаться в этот остаток.


Чтение страницы

uint8_t buf[256] = {0,};
W25qxx_ReadPage(buf, 0, 28, 10);

Первый аргумент это буфер в который запишутся данные, остальные аргументы делают то же самое что и при записи. Это так же относится к чтению секторов и блоков.


Если нужно записать не больше 4096 байт, используем функцию для записи сектора…

W25qxx_WriteSector(w_buf, 0, 1350, 2500);

С аргументами всё то же самое что и при записи страницы.


Чтение сектора

W25qxx_ReadSector(buf, 0, 1350, 10);



Ну и наконец если нужно записать не больше 65536 байт, используем функцию для записи блока…

W25qxx_WriteBlock(w_buf, 0, 9350, 10);

И с аргументами опять таки всё то же.

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

Функции записи сектора и блока содержат в себе функцию записи страницы, так что запись в любом случае ведётся по 256 байт. Таким образом размер поступающих данных не превышает внутреннего буфера флешки.


Чтение блока

W25qxx_ReadBlock(buf, 0, 9350, 10);



Помимо функций чтения страницы, сектора и блока, есть универсальная функция, которая может читать любое кол-во байт с любого места…

W25qxx_ReadBytes(buf, 9350, 10);

Здесь просто указывается конкретная ячейка с которой нужно начинать чтение, и количество байт.



Проверка стерта ли страница (сектор, блок) полностью или частично

Для страницы это делается так…

uint8_t clear = W25qxx_IsEmptyPage(0, 0);

if(clear) HAL_UART_Transmit(&huart1, (uint8_t*)"Clear\n", 6, 100);
else HAL_UART_Transmit(&huart1, (uint8_t*)"Not clear\n", 10, 100);

Функция возвращает либо 1 — стёрта, либо 0 не стёрта.

Первый аргумент это номер страницы, второй — это сдвиг. То есть в данном случае мы проверяем нулевую страницу полностью, с самого начала.

Если хотим проверить не всю страницу, а начиная с пятой ячейки, тогда аргументы функции будут такими…

uint8_t clear = W25qxx_IsEmptyPage(0, 5);


Для сектора…

uint8_t clear = W25qxx_IsEmptySector(0, 0);


Для блока…

uint8_t clear = W25qxx_IsEmptyBlock(0, 0);

Аргументы те же что и для страницы.

Все описанные функции продемонстрированы в библиотеке.



Подключение



Питание от 2.7 до 3.6 В.

Это подключение для работы в обычном режиме SPI. Ещё есть режимы Dual и Quad SPI, в которых можно работать с удвоенной и учетверённой скоростью. В этих режимах и подключение другое, и работать нужно по другому. Я не пробовал поэтому ничего не скажу.



Программа

В Кубе настраиваем какой-нибудь SPI, у меня будет второй…


Скорость SPI можно указать повыше (если камень позволяет), флешка поддерживает до 80МГц.


Настраиваем какую-нибудь ножку как GPIO_Output, это будет Chip Select (на картинке он обозначен как NSS)


Output level ставим в High, и даём ей название FLASH_CS (тогда в библиотеке не придётся ничего менять).

Ну и настраиваем какой-нибудь USART для вывода инфы. Всё, можно генерить проект.


Теперь скачиваем библиотеку и добавляем в проект файлы w25qxx.c и w25qxx.h.

В main.c инклюдим хедер…

/* USER CODE BEGIN Includes */
#include "w25qxx.h"
/* USER CODE END Includes */


В файле w25qxx.h прописываем номера своих SPI и USART'а…

extern SPI_HandleTypeDef hspi2;
extern UART_HandleTypeDef huart1;

#define DEBUG_UART               &huart1
#define W25QXX_SPI_PTR           &hspi2


Собственно на этом всё, перед бесконечным циклом пишем функцию инициализации…

W25qxx_Init();

… и дальше смотрите в моём main.c.


Да, чуть не забыл, это не моя библиотека, я её только немного модифицировал, оригинал находится здесь.


Всем спасибо


Телеграм-чат istarik

Телеграм-чат STM32


  • 0
  • 36678
Поддержать автора


Telegram-чат istarik

Задать вопрос по статье
Telegram-канал istarik

Известит Вас о новых публикациях






Комментарии (0)

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.