Всем привет!

Я хочу поделиться с вами, как однажды мне пришлось оживлять вторую флешку на микроконтроллере (далее «мк»), ну кому не нравится, то назовем это «программируемая система на кристалле». Наверное кто только зашел сюда и хочет более подробно разобраться и все понять, то вам сюда, так как я планирую все подробно рассказать от А до Я.

В данной статье будут подняты такие вопросы:

  1. Инициализация QSPI (кто не знает, специальная периферия, позволяющая работать с флешкой на прямую).

  2. Инициализация 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
}

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

Ну вот и пришла статья к концу. Спасибо, что дочитали до конца. Если остались какие-либо вопросы - пишите в комментариях, с радостью помогу!

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