Всем привет!
Я хочу поделиться с вами, как однажды мне пришлось оживлять вторую флешку на микроконтроллере (далее «мк»), ну кому не нравится, то назовем это «программируемая система на кристалле». Наверное кто только зашел сюда и хочет более подробно разобраться и все понять, то вам сюда, так как я планирую все подробно рассказать от А до Я.
В данной статье будут подняты такие вопросы:
Инициализация QSPI (кто не знает, специальная периферия, позволяющая работать с флешкой на прямую).
Инициализация flash. Написание драйвера.
Проделав все пункты, которые будут затронуты в данной статье, вам станет более понятно как и что работает.
1. Вступление
Начнем с того, что же из себя представляет данный режим соединения флешек. В ПЛИС поддерживается 3 режима соединения, представлены на рисунке ниже.

Первый режим Single SS 4-bit представляет из себя простое соединения и связь с одной единственной флешкой на плате. Там ничего сложного при инициализации нет, но код ниже также подойдет для ее настройки. Второй и третий режимы как раз таки нужны для работы с двумя флешками, но в моем случае используется не 2 qspi, а всего лишь один. То есть упрощается задача с тем, что не нужно дергать Chip Select для работы с конкретной флеш. Ниже представлено соединение на моей плате, с флешкой типа WINBOND W25Q128JV.

Простым языком скажу так, что они соединены последовательно. Можете посмотреть как это соединено на китайском аналоге, у меня кастом.
Перед отладкой и работой убедитесь, что boot_mode у вас выставлен на 0. Это сигнализирует для платы, что работа мк идет через JTAG, записью напрямую в ОЗУ, в обход флешки. Теперь на этой чудесной ноте, можно перейти далее.
2. QSPI
Опущу моменты дефолтных инициализаций, так как статья не об этом. Для начала нам нужно заглянуть в документацию zynq-7000 и посмотреть поля регистров. Для красоты картинки создать структуру с ними, чтобы в дальнейшем в случае чего, можно было обращаться к той или иной памяти и перезаписывать требуемые нам биты.


Описание этих регистров толком в документации нет, только эти описания, что представлены в самом правом столбце. В этом плане конечно китайцы не молодцы, хд.
Далее представлен код драйвера для QSPI, для моих личных удобств, в теории это можно опустить, но пусть будет.
Заголовочный файлик и исходник
#ifndef QSPI_DRIVER_ZYNQ
#define QSPI_DRIVER_ZYNQ
#include "xqspips.h"
#include "xqspips_hw.h"
#include "xparameters_ps.h"
#include "w25q128jvsiq_driver.h"
#include "assert.h"
#define QSPI_BANK ((QspiBank_t*) QSPI0_BASE)
#define QSPI_TX ((QspiTx_t*) (QSPI0_BASE + 0x80))
#define QSPI_LQSPI ((QspiLqspi_t*) (QSPI0_BASE + 0xA0))
#define QSPI_MOD ((QspiMod_t*) (QSPI0_BASE + 0xFC))
#define QSPI_DEVICE_ID XPAR_XQSPIPS_0_DEVICE_ID
#define QSPI_CONNECTION_MODE (XPAR_XQSPIPS_0_QSPI_MODE)
#define QSPI_BUS_WIDTH (XPAR_XQSPIPS_0_QSPI_BUS_WIDTH)
#define SINGLE_FLASH_CONNECTION 0
#define DUAL_STACK_CONNECTION 1
#define DUAL_PARALLEL_CONNECTION 2
#define FLASH_SIZE_16MB 0x1000000
#define DEBUG_GENERAL 0x00000001 /* general debug messages */
#define DEBUG_INFO 0x00000002 /* More debug information */
#define LQSPI_CR_FAST_READ 0x0000000B
#define LQSPI_CR_FAST_DUAL_READ 0x0000003B
#define LQSPI_CR_FAST_QUAD_READ 0x0000006B /* Fast Quad Read output */
#define LQSPI_CR_1_DUMMY_BYTE 0x00000100 /* 1 Dummy Byte between
address and return data */
#define SINGLE_QSPI_CONFIG_FAST_READ (XQSPIPS_LQSPI_CR_LINEAR_MASK | \
LQSPI_CR_1_DUMMY_BYTE | \
LQSPI_CR_FAST_READ)
#define SINGLE_QSPI_CONFIG_FAST_DUAL_READ (XQSPIPS_LQSPI_CR_LINEAR_MASK | \
LQSPI_CR_1_DUMMY_BYTE | \
LQSPI_CR_FAST_DUAL_READ)
#define SINGLE_QSPI_CONFIG_FAST_QUAD_READ (XQSPIPS_LQSPI_CR_LINEAR_MASK | \
LQSPI_CR_1_DUMMY_BYTE | \
LQSPI_CR_FAST_QUAD_READ)
#define DUAL_QSPI_CONFIG_FAST_QUAD_READ (XQSPIPS_LQSPI_CR_LINEAR_MASK | \
XQSPIPS_LQSPI_CR_TWO_MEM_MASK | \
XQSPIPS_LQSPI_CR_SEP_BUS_MASK | \
LQSPI_CR_1_DUMMY_BYTE | \
LQSPI_CR_FAST_QUAD_READ)
#define DUAL_STACK_CONFIG_FAST_READ (XQSPIPS_LQSPI_CR_TWO_MEM_MASK | \
LQSPI_CR_1_DUMMY_BYTE | \
LQSPI_CR_FAST_READ)
#define DUAL_STACK_CONFIG_FAST_DUAL_READ (XQSPIPS_LQSPI_CR_TWO_MEM_MASK | \
LQSPI_CR_1_DUMMY_BYTE | \
LQSPI_CR_FAST_DUAL_READ)
#define DUAL_STACK_CONFIG_FAST_QUAD_READ (XQSPIPS_LQSPI_CR_TWO_MEM_MASK | \
LQSPI_CR_1_DUMMY_BYTE | \
LQSPI_CR_FAST_QUAD_READ)
#define SINGLE_QSPI_IO_CONFIG_FAST_READ (LQSPI_CR_1_DUMMY_BYTE | \
LQSPI_CR_FAST_READ)
#define SINGLE_QSPI_IO_CONFIG_FAST_DUAL_READ (LQSPI_CR_1_DUMMY_BYTE | \
LQSPI_CR_FAST_DUAL_READ)
#define SINGLE_QSPI_IO_CONFIG_FAST_QUAD_READ (LQSPI_CR_1_DUMMY_BYTE | \
LQSPI_CR_FAST_QUAD_READ)
#define DUAL_QSPI_IO_CONFIG_FAST_QUAD_READ (XQSPIPS_LQSPI_CR_TWO_MEM_MASK | \
XQSPIPS_LQSPI_CR_SEP_BUS_MASK | \
LQSPI_CR_1_DUMMY_BYTE | \
LQSPI_CR_FAST_QUAD_READ)
#define QSPI_BUSWIDTH_ONE 0U
#define QSPI_BUSWIDTH_TWO 1U
#define QSPI_BUSWIDTH_FOUR 2U
#define MICRON_ID 0x20
#define SPANSION_ID 0x01
#define WINBOND_ID 0xEF
#define MACRONIX_ID 0xC2
#define ISSI_ID 0x9D
typedef enum
{
HAL_SPIQF_STATE_RESET = 0x00U, ///< Сброс
HAL_SPIQF_STATE_READY = 0x20U, ///< Готов к работе
HAL_SPIQF_STATE_BUSY = 0x24U, ///< Занят выполнением операции
} HAL_QSPI_StateTypeDef_t;
typedef enum
{
QSPI_OK = 0x00U,
QSPI_ERROR = 0x01U,
QSPI_BUSY = 0x02U,
QSPI_TIMEOUT = 0x03U,
} QspiStatus_t;
typedef struct{
volatile u32 config_reg; ///< Address: 0x0 - qspi configuration
volatile u32 intr_status_reg; ///< Address: 0x4 - qspi interrupt status
volatile u32 intrpt_en_reg; ///< Address: 0x8 - interrupt enable
volatile u32 intrpt_dis_reg; ///< Address: 0xC - interrupt disable
volatile u32 intrpt_mask_reg; ///< Address: 0x10 - interrupt mask
volatile u32 en_reg; ///< Address: 0x14 - spi_enable
volatile u32 delay_reg; ///< Address: 0x18 - delay
volatile u32 txd0; ///< Address: 0x1C - transmit data register
volatile u32 rx_data_reg; ///< Address: 0x20 - receive data
volatile u32 slave_idle_count_reg; ///< Address: 0x24 - slave idle threshold
volatile u32 tx_thres_reg; ///< Address: 0x28 - tx_fifo treshold
volatile u32 rx_thres_reg; ///< Address: 0x2C - rx_fifo treshold
volatile u32 gpio; ///< Address: 0x30 - general purpose inputs and outputs
volatile u32 res; ///< Address: 0x34 - reserved
volatile u32 lpbk_dly_adj; ///< Address: 0x38 - loopback master clock delay adjustment
}QspiBank_t;
typedef struct{
volatile u32 txd1; ///< Address: 0x80 - transmit data
volatile u32 txd2; ///< Address: 0x84 - transmit data
volatile u32 txd3; ///< Address: 0x88 - transmit data
}QspiTx_t;
typedef struct{
volatile u32 lqspi_cfg; ///< Address: 0xA0 - configuration reg specially for the linear quad-spi controller
volatile u32 lqspi_sts; ///< Address: 0xA4 - status reg specially for the linear quad-spi controller
}QspiLqspi_t;
typedef struct{
volatile u32 mod_id; ///< Address: 0xFC - module identification
}QspiMod_t;
typedef struct{
QspiBank_t* QSpiBank;
QspiTx_t* QSpiTx;
QspiLqspi_t* QSpiLqspi;
QspiMod_t* QSpiMode;
}Qspi_Typedef;
typedef struct{
Qspi_Typedef *pPort;
XQspiPs* pPortPs;
HAL_QSPI_StateTypeDef_t State;
u32 *pTxBuff;
u8 *pRxBuff;
u32 XferCount;
int NumSector;
int NumBlock;
}Qspi_t;
u32 InitialQspi();
extern Qspi_t pQspi;
extern XQspiPs pQspiPs;
extern XQspiPs *QspiInstancePtr;
#endif //QSPI_DRIVER_ZYNQ
#if defined(ZYNQ_AD)
#include "qspi_driver_zynq.h"
Qspi_t pQspi;
XQspiPs pQspiPs;
XQspiPs *QspiInstancePtr;
u32 FlashReadBaseAddress;
u32 QspiFlashSize;
u32 QspiFlashMake;
u32 FlashReadBaseAddress;
u8 LinearBootDeviceFlag;
/**
* @brief Функция инициализации Qspi системная, встроенная в Vitis (обязательна для работоспособности qspi)
* @return Статус выполнения функции XST_SUCCESS/XST_FAILURE
*/
u32 InitialQspi()
{
XQspiPs_Config *QspiConfig;
int Status;
QspiInstancePtr = &pQspiPs;
/*
* Initialize the QSPI driver so that it's ready to use
*/
QspiConfig = XQspiPs_LookupConfig(QSPI_DEVICE_ID);
if (NULL == QspiConfig) {
return XST_FAILURE;
}
Status = XQspiPs_CfgInitialize(QspiInstancePtr, QspiConfig,
QspiConfig->BaseAddress);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/*
* Set Manual Chip select options and drive HOLD_B pin high.
*/
XQspiPs_SetOptions(QspiInstancePtr, XQSPIPS_FORCE_SSELECT_OPTION |
XQSPIPS_HOLD_B_DRIVE_OPTION);
/*
* Set the prescaler for QSPI clock
*/
XQspiPs_SetClkPrescaler(QspiInstancePtr, XQSPIPS_CLK_PRESCALE_8);
/*
* Assert the FLASH chip select.
*/
XQspiPs_SetSlaveSelect(QspiInstancePtr);
/*
* Read Flash ID and extract Manufacture and Size information
*/
Status = FlashReadID();
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
return XST_SUCCESS;
}
#endif //defined(ZYNQ_AD)
В заголовочный файл добавлено ничего необычного. Взяты адреса по конкретному мк, вывел все макросы нужные и, которые мне показались тогда нужными, из файлов самой ПЛИС, объявлены структуры регистров и второстепенных вещей, таких как указателей на буфера, номера секторов и блоков и так далее.
В исходный файл ничего мудреного тоже нет, выведена функция встроенная в ПЛИС, которая инициализирует QSPI. В вашем случае создаете два файлика и Ctrl+C, Ctrl+V.
Функция инициализации выглядит следующим образом и вызвана она в мейн или в другом работающем файле или функции, который(ая) у вас отведен(на) для инициализации.
extern Qspi_t pQspi;
extern XQspiPs pQspiPs;
Qspi_t* Qspi;
/**
* @brief Функция инициализация перифирии QSPI для работы с flash memory
* @param[in] pQspi - указатель на структуру периферии qspi
* @param[in] pQspiPs - указатель на системный qspi
* @param[in] pRxBuff - указатель на буфер
*/
static void QspiDefaultInit(Qspi_t* pQspi, XQspiPs* pQspiPs, void* pRxBuff)
{
pQspi->pPortPs = pQspiPs;
pQspi->pRxBuff = (u8*)pRxBuff;
pQspi->XferCount = 0;
pQspi->State = HAL_SPIQF_STATE_RESET;
// инициализация указателей регистров qspi
pQspi->pPort= malloc(sizeof(Qspi_Typedef));
pQspi->pPort->QSpiBank = QSPI_BANK;
pQspi->pPort->QSpiTx = QSPI_TX;
pQspi->pPort->QSpiLqspi = QSPI_LQSPI;
pQspi->pPort->QSpiMode = QSPI_MOD;
InitialQspi();
}
То есть эту функцию мы колим где угодно перед использованием qspi. Как видно в конце вызывается функция встроенная InitialQspi(), которая запустит qspi.
2. Flash WINBOND W25Q128JV
Настало время нашей заветной флешки. Связь у нас выглядит таким образом, что если нужно выполнить какое-либо действие с флешкой, то нужно отправить ей инструкцию по qspi, которые представлены в даташите. Прикрепляю полный список инструкций, которые можно отправить флешке.


Нам интересны на самом деле только несколько: проверка работоспособности инструкции, очистка сектора/блока, чтение по адресу, запись по адресу, чтение и запись в регистры флешки. Все это будет описано ниже по этапам, чтобы все было понятно и просто.
Есть очень хорошая функция, которая отправляет данные по qspi. Она изначальна написана китайцами, будем пользоваться ею - XQspiPs_PolledTransfer. Сначала опишу заголовочный файл, что как и почему.
#if defined(ZYNQ_AD)
#include "w25q128jvsiq_driver.h"
u8 WriteBuffer[10];
u8 ReadStatusCmd[] = {READ_ENABLE_CMD, 0}; // должно быть 2 байта
u8 pFlashStatus[2];
u8 ReadBuffer[SECTOR_BYTES + OVERHEAD_SIZE + DUMMY_SIZE];
Flash_t* pFlash;
Flash_t Flash;
static void WriteEnableCmd();
static bool CheckCmd();
static u8 ReadReg1(Flash_t* pFlash);
static u8 ReadReg2(Flash_t* pFlash);
static u8 ReadReg3(Flash_t* pFlash);
static void WriteReg1(Flash_t* pFlash, u8 Register, bool Num);
static void WriteReg2(Flash_t* pFlash, u8 Register, bool Num);
static void WriteReg3(Flash_t* pFlash, u8 Register, bool Num);
Обычное дело - инициализация буферов, колим указатели на структуры, прототипы статик функций.
WriteEnableCmd - функция, отправляющая инструкцию 0х06, которая является сигнализатором флешке, что сейчас будет отправлена инструкция,
ReadReg - функция чтения регистров флешки,
WriteReg - функция записи бит в регистр флешки,
CheckCmd - функция считывающая все регистры флешки.
Я добавил к каждой функции порядковый номер, который сигнализирует о том, к какому регистру мы обращаемся или куда записываем биты.
void CheckAllReg()
{
ReadReg1(pFlash);
ReadReg2(pFlash);
ReadReg3(pFlash);
}
/**
* @brief Функция отправки команды 0х06
*/
static void WriteEnableCmd()
{
u8 WriteEnableCmd = {WRITE_ENABLE_CMD};
XQspiPs_PolledTransfer(QspiInstancePtr, &WriteEnableCmd, NULL, sizeof(WriteEnableCmd));
CheckCmd();
}
/**
* @brief Функция чтения регистров 1
* @param[in] Указатель на основную структуру интерфейса Flash_t
* @return Возвращает регистр 1
*/
static u8 ReadReg1(Flash_t* pFlash)
{
WriteBuffer[0] = READ_REG_1_CMD;
WriteBuffer[1] = 0x00;
XQspiPs_PolledTransfer(QspiInstancePtr, WriteBuffer, ReadBuffer, 1);
CheckCmd();
pFlash->Reg1 = ReadBuffer[1];
return ReadBuffer[1];
}
/**
* @brief Функция чтения регистров 2
* @param[in] Указатель на основную структуру интерфейса Flash_t
* @return Возвращает регистр 2
*/
static u8 ReadReg2(Flash_t* pFlash)
{
WriteBuffer[0] = READ_REG_2_CMD;
WriteBuffer[1] = 0x00;
XQspiPs_PolledTransfer(QspiInstancePtr, WriteBuffer, ReadBuffer, 1);
CheckCmd();
pFlash->Reg2 = ReadBuffer[1];
return ReadBuffer[1];
}
/**
* @brief Функция чтения регистров 3
* @param[in] pFlash - Указатель на основную структуру интерфейса Flash_t
* @return Возвращает регистр 3
*/
static u8 ReadReg3(Flash_t* pFlash)
{
WriteBuffer[0] = READ_REG_3_CMD;
WriteBuffer[1] = 0x00;
XQspiPs_PolledTransfer(QspiInstancePtr, WriteBuffer, ReadBuffer, 1);
CheckCmd();
pFlash->Reg3 = ReadBuffer[1];
return ReadBuffer[1];
}
/**
* @brief Функция записи регистров 1
* @param[in] pFlash - указатель на основную структуру интерфейса Flash
* @param[in] Register - регистр, который нобходимо изменить
* @param[in] Num - параметр, который записывается 1/0
*/
static void WriteReg1(Flash_t* pFlash, u8 Register, bool Num)
{
bool go = 0;
pFlash->TempReg = ReadReg1(pFlash); // получаем номера бит, который требуется заменить
// если уже установлен нужный бит
if(((pFlash->TempReg & Register) == 1)&&(Num == 1))
return;
if(((pFlash->TempReg & Register) == 0)&&(Num == 0))
return;
// если не установлен нужный бит
if((((pFlash->TempReg & Register) == 0)&&(Num == 1))||(((pFlash->TempReg & Register) == 1)&&(Num == 0)))
{
if(Num == 1)
pFlash->TempReg |= Register;
else
pFlash->TempReg &= ~(Register);
// флаг, что требуется поменять регистр
go = 1;
}
if(go == 1)
{
WriteEnableCmd(QspiInstancePtr);
WriteBuffer[0] = WRITE_REG_1_CMD;
WriteBuffer[1] = pFlash->TempReg;
XQspiPs_PolledTransfer(QspiInstancePtr, WriteBuffer, NULL, 2);
if(CheckCmd())
pFlash->Reg1 = pFlash->TempReg;
}
}
/**
* @brief Функция записи регистров 2
* @param[in] pFlash - указатель на основную структуру интерфейса Flash
* @param[in] Register - регистр, который нобходимо изменить
* @param[in] Num - параметр, который записывается 1/0
*/
static void WriteReg2(Flash_t* pFlash, u8 Register, bool Num)
{
bool go = 0;
pFlash->TempReg = ReadReg2(pFlash); // получаем номера бит, который требуется заменить
usleep(100);
// если уже установлен нужный бит
if(((pFlash->TempReg & Register) == Register)&&(Num == 1))
return;
if(((pFlash->TempReg & Register) == 0x00)&&(Num == 0))
return;
// если не установлен нужный бит
if((((pFlash->TempReg & Register) == 0x00)&&(Num == 1))||(((pFlash->TempReg & Register) == Register)&&(Num == 0)))
{
if(Num == 1)
pFlash->TempReg |= Register;
else
pFlash->TempReg &= ~(Register);
// флаг, что требуется поменять регистр
go = 1;
}
if(go == 1)
{
WriteEnableCmd(QspiInstancePtr);
WriteBuffer[0] = WRITE_REG_2_CMD;
WriteBuffer[1] = pFlash->TempReg;
XQspiPs_PolledTransfer(QspiInstancePtr, WriteBuffer, NULL, 2);
CheckCmd();
pFlash->Reg2 = pFlash->TempReg;
}
usleep(100);
}
/**
* @brief Функция записи регистров 3
* @param[in] pFlash - указатель на основную структуру интерфейса Flash
* @param[in] Register - регистр, который нобходимо изменить
* @param[in] Num - параметр, который записывается 1/0
*/
static void WriteReg3(Flash_t* pFlash, u8 Register, bool Num)
{
bool go = 0;
pFlash->TempReg = ReadReg3(pFlash); // получаем номера бит, который требуется заменить
// если уже установлен нужный бит
if(((pFlash->TempReg & Register) == 1)&&(Num == 1))
return;
if(((pFlash->TempReg & Register) == 0)&&(Num == 0))
return;
// если не установлен нужный бит
if((((pFlash->TempReg & Register) == 0)&&(Num == 1))||(((pFlash->TempReg & Register) == 1)&&(Num == 0)))
{
if(Num == 1)
pFlash->TempReg |= Register;
else
pFlash->TempReg &= ~(Register);
// флаг, что требуется поменять регистр
bool go = 1;
}
if(go == 1)
{
WriteEnableCmd(QspiInstancePtr);
WriteBuffer[0] = WRITE_REG_3_CMD;
WriteBuffer[1] = pFlash->TempReg;
XQspiPs_PolledTransfer(QspiInstancePtr, WriteBuffer, NULL, 2);
if(CheckCmd())
pFlash->Reg3 = pFlash->TempReg;
}
}
Теперь стоит перейти к самым вкусным функциям для флеш. Основные функции, без которых ничего не заиграет в будущем.
/**
* @brief Функция отправки команды по адресу любая
* @param[in] Command - команда, которая предусмотрена в datasheet для w25q128jvsiq
* @param[in] Address - по какому адресу требуется отправить команду во flash
* @param[in] ByteCnt - количество байт, которое нужно передать во flash
*/
void FlashSendCmd(u8 Command, u32 Address, u32 ByteCnt)
{
// установка команды записи с указанными адресом и данными
WriteBuffer[0] = Command;
WriteBuffer[1] = (u8)((Address & 0xFF0000) >> 16);
WriteBuffer[2] = (u8)((Address & 0xFF00) >> 8);
WriteBuffer[3] = (u8)(Address & 0xFF);
if(Command == PAGE_PROGRAM_CMD)
{
XQspiPs_PolledTransfer(QspiInstancePtr, WriteBuffer, NULL, ByteCnt + OVERHEAD_SIZE);
CheckCmd();
return;
}
if((Command == READ_REG_1_CMD)||(Command == READ_REG_2_CMD)||(Command == READ_REG_3_CMD))
{
XQspiPs_PolledTransfer(QspiInstancePtr, WriteBuffer, ReadBuffer, 2);
CheckCmd();
return;
}
if((Command == READ_DATA_CMD)||(Command == FAST_READ_CMD)||(Command == FAST_READ_DUAL_OUTPUT_CMD)||(Command == FAST_READ_QUAD_OUTPUT_CMD)||(Command == FAST_READ_DUAL_IO_CMD)||(Command == FAST_READ_QUAD_IO_CMD))
{
XQspiPs_PolledTransfer(QspiInstancePtr, WriteBuffer, ReadBuffer, ByteCnt + OVERHEAD_SIZE);
CheckCmd();
return;
}
// отправление команды разрешения записи на флеш, чтобы в нее можно было записывать данные
XQspiPs_PolledTransfer(QspiInstancePtr, WriteBuffer, NULL, ByteCnt + OVERHEAD_SIZE);
CheckCmd();
}
/**
* @brief Функция очистки сектора, предварительно требуемая перед записью
* @param[in] Sector - номер сектора (0-15)
* @param[in] Block - номер блока (0-255)
*/
void FlashEraseSector(int Sector, int Block)
{
u32 Address;
// записываем команду WRITE_ENABLE_CMD для того, чтобы потом отправить нужную нам команду
WriteEnableCmd();
u32 AddressSector = (u32)(Sector * (int)SECTOR_SIZE) & 0x0000FFFF; // адрес сектора
u32 AddressBlock = (u32)(Block * (int)BLOCK_SIZE) & 0x00FF0000; // адрес блока
Address = (u32)(AddressSector | AddressBlock) & 0x00FFFFFF;
WriteBuffer[0] = SECTOR_ERASE_CMD;
WriteBuffer[1] = (u8)((Address & 0xFF0000) >> 16);
WriteBuffer[2] = (u8)((Address & 0xFF00) >> 8);
WriteBuffer[3] = (u8)(Address & 0xFF);
XQspiPs_PolledTransfer(QspiInstancePtr, WriteBuffer, NULL, 4);
CheckCmd();
}
/**
* @brief Функция очистки блока
* @param[in] Block - номер блока записи (0-255)
*/
void FlashEraseBlock(int Block)
{
ReadReg1(pFlash);
ReadReg2(pFlash);
ReadReg3(pFlash);
u32 Address;
// записываем команду WRITE_ENABLE_CMD для того, чтобы потом отправить нужную нам команду
WriteEnableCmd();
Address = (u32)(Block * BLOCK_SIZE) & 0x00FF0000; // адрес блока
WriteBuffer[0] = BLOCK_ERASE_CMD;
WriteBuffer[1] = (u8)((Address & 0xFF0000)>>16);
WriteBuffer[2] = (u8)((Address & 0xFF00)>>8);
WriteBuffer[3] = (u8)(Address & 0xFF);
XQspiPs_PolledTransfer(QspiInstancePtr, WriteBuffer, NULL, 4);
CheckCmd();
}
/**
* @brief Функция очистки по адресу, предварительно требуемая перед записью
* @param[in] u32 Address - адрес с которого начинается очистка до конца сектора
*/
void FlashEraseAddress(u32 Address)
{
// записываем команду WRITE_ENABLE_CMD для того, чтобы потом отправить нужную нам команду
WriteEnableCmd();
WriteBuffer[0] = SECTOR_ERASE_CMD;
WriteBuffer[1] = (u8)((Address & 0xFF0000) >> 16);
WriteBuffer[2] = (u8)((Address & 0xFF00) >> 8);
WriteBuffer[3] = (u8)(Address & 0xFF);
XQspiPs_PolledTransfer(QspiInstancePtr, WriteBuffer, NULL, 4);
CheckCmd();
}
/**
* @brief Функция программирования страницы в адрес
* @param[in] Address -адрес записи
* @param[in] Massive - массив, который нужно записать
* @info Массив должен быть больше на 4 байта, так как сначала отправляется вместе с массивом инструкция записи
*/
void FlashPageProgaAddress(u32 Address, u8* Massive, int Bytes)
{
//debug
WriteEnableCmd();
*Massive = PAGE_PROGRAM_CMD;
*(Massive + 1) = (u8)((Address & 0xFF0000) >> 16);
*(Massive + 2) = (u8)((Address & 0xFF00) >> 8);
*(Massive + 3) = (u8)(Address & 0xFF);
XQspiPs_PolledTransfer(QspiInstancePtr, Massive, NULL, Bytes + 4);
CheckCmd();
}
/**
* @brief Функция чтения ID Flash памяти
*/
int FlashReadID()
{
u32 Status;
// чтение ID автоматический режим
WriteBuffer[0] = 0x9F;
WriteBuffer[1] = 0x00;
WriteBuffer[2] = 0x00;
WriteBuffer[3] = 0x00;
Status = XQspiPs_PolledTransfer(QspiInstancePtr, WriteBuffer, ReadBuffer, 4);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
xil_printf("Single pFlash Information\r\n");
xil_printf("pFlashID=0x%x 0x%x 0x%x\r\n", ReadBuffer[1], ReadBuffer[2], ReadBuffer[3]);
return XST_SUCCESS;
}
/**
* @brief Функция проверки выполненности команды
* @return 1
*/
static bool CheckCmd()
{
while(1)
{
XQspiPs_PolledTransfer(QspiInstancePtr, ReadStatusCmd, pFlashStatus, sizeof(ReadStatusCmd));
if((pFlashStatus[1] & 0x01) == 0x0)
break;
}
return 1;
}
/**
* @brief Функция чтения по адресу в Flash памяти
*/
void FlashReadAddress(u32 ByteCount, u32 Address, u8 Cmd)
{
if(Cmd == READ_DATA_CMD)
WriteReg2(pFlash, REGISTER_QE, 0);
FlashSendCmd(Cmd, Address, ByteCount);
CheckCmd();
}
Разберем по порядку, чтобы не осталось ни у кого никаких вопросов.
Функция FlashSendCmd универсальная, то есть она отправляет фактически любые описанные выше инструкции в флешку, с помощью встроенной функции XQspiPs_PolledTransfer. Входные параметры у нее простые - указатель на структуру qspi, сгенерированную самим vitis; буфер отправки; буфер приемки; количество байт отправки (для инструкции). Присутствуют проверки на байт инструкций, ничего сложного.
Функция FlashEraseBlock, FlashEraseSector очень важны для нас, так как без них не получится дальнейшая запись данных во флеш. Все это связано с тем, что запись возможно только на заранее очищенную память, то есть должно быть записано 0xFF. Нам нужно отправлять 0x20, 0xD8 соответственно. Я чаще пользуюсь функцией очистки блока, так как моя задача работы с прошивкой, мне не нужно хранить какие-то временные переменные и так далее во флеш, памяти хватает, это вам не stm32f103rb nucleo например с флешкой в 128 кбайт, в которой даже с трудом похранишь дополнительную прошивку, размер которой 53 кило. Хотя алгоритм записи во флеш идентичен. Но сейчас не об этом...
Ниже представлю схему флешки по распределению памяти.

Ознакомившись с рисуночком, теперь можно перейти к функциям чтения и записи по адресу.
Как видно с рисунка, наша память в 16 кило делится на блоки по 64 кбайт каждый, а блоки в свою очередь делятся на сектора по 4 кбайт, а сектора делятся на страницы в 256 байт.
Теперь могу поподробнее рассказать про оставшиеся функции.
Функция чтения по адресу имеет следующие входные параметры: количество байт, которые мы хотим прочитать из флешки (ограничено только объявленным буфером, в моем случае 4096 байт); адрес во флеш откуда мы хотим считывать и байт инструкции, в каком режиме мы будем считывать. Мой алгоритм основан на чтении 0х03, так как каждый режим чтения отличается выводом данных по шинам, тем самым различным типов собирания полученных данных. Это очень важный момент, так как иначе данные в буфере у вас будут лежать некорректные. Также стоит обратить внимание, что буфер для чтения и записи, которые вы хотите использовать, нужно подсовывать в функцию поллинга непосредственно в функциях флеши. Формирование буфера отправки как видно по коду, включает в себя предварительную отправку инструкции 0x06, далее номер инструкции, которая нужна и далее 3 байта адреса. Адрес в нашем случае представляет из себя 0xFCxxxxxx, то есть старший байт опускаем и записываем только нужные 3, далее qspi сам решит как и куда записывать, так как у него есть свой алгоритм распределения данных по четным и нечетным битам. С записью все идентично, но есть нюанс, так как у нас теперь используется две флеши, то адреса у нас делятся пополам. То есть если вы записали страницу по нулевому адресу, то сдвиг на следующую запись будет - 0x80, половина от 256 байт в hex. То есть фактически берете любой адрес куда хотите записать данные и делите на 2 в hex, это и будет нужный вам адрес.
Изюминкой на тортике будет функция инициализации флешки, которая вызвана следом за инициализацией qspi.
/**
* @brief Функция инициализации flash memory
* @detail Если нужно включить вторую флеш, то нужно обязательно прописать макрос включения
* DUAL_FLASH_WINBOND, иначе не будет работать
* @param[in] Flash - указатель на структуру флешки
* @return None
*/
static void FlashDefaultInit(Flash_t* Flash)
{
Flash->Reg1 = 0x00;
Flash->Reg2 = 0x00;
Flash->Reg3 = 0x00;
Flash->TempReg = 0x00;
Flash->FlashState = 0x03;
CheckAllReg();
// настройка для второй флеш, если она подключена
#if defined (DUAL_FLASH_WINBOND)
XQspiPs_WriteReg(0xE000D000, 0xA0, 0x60000000);
uint8_t Rec[10] = {};
uint8_t num1 = 0x02;
XQspiPs_PolledTransfer(&pQspiPs, (uint8_t[]){0x06}, NULL, 1);
XQspiPs_PolledTransfer(&pQspiPs, (uint8_t[]){0x31, num1}, Rec, 2);
uint32_t num = XQspiPs_ReadReg(QSPI0_BASE, 0x00);
num |= (((3 << 2) << 4) << 8);
XQspiPs_WriteReg(0xE000D000, 0x00, num);
#endif
}
Здесь также ничего сложного, самое главное определение через макрос. Внутри вписаны записи в регистры и отправки команд для настройки второй флешки, вы все можете сверить сами с регистрами выше и удостовериться в этом.
Ну вот и пришла статья к концу. Спасибо, что дочитали до конца. Если остались какие-либо вопросы - пишите в комментариях, с радостью помогу!