/********************************SPI MEMORY*********************************
Данный файл содержит инклюды и дефайны для общения с FLASH/EEPROM по SPI.
***************************************************************************/
#ifndef __SPI_MEMORY_H_
#define __SPI_MEMORY_H_

#include "stm32f4xx_hal.h"
#include "gpio_general.h"

/////////////////////////---USER SETTINGS---/////////////////////////
#define EXT_FLASH
//#define EXT_EEPROM

#if defined(EXT_FLASH) & defined(EXT_EEPROM)
	#error Choose only one memory
#endif

//#define SEPARATED_STATUS_REGISTER



/////////////////////////////////////////////////////////////////////
////////////////////////////---DEFINES---////////////////////////////
/** 
	* @brief 	SPI Transmit.
	* @param 	_hmemspi_ - указатель на хендл внешней памяти.
	* @param 	_data_ 		- указатель на данные для отправки.
	* @param 	_size_ 		- размер данных для отправки.
	* @param 	_timeout_	- время, за которое должна быть осуществлена отправка.
	* @note 	Здесь вызывается только функция HAL, и ничего больше.
	*/
#define MEMSPI_SPI_Transmit(_hmemspi_, _data_, _size_, _timeout_)  HAL_SPI_Transmit(&_hmemspi_->hspi, _data_, _size_, _timeout_)
/** 
	* @brief 	SPI Receive.
	* @param 	_hmemspi_ - указатель на хендл внешней памяти.
	* @param 	_data_ 		- указатель на буффер для прниема данных.
	* @param 	_size_ 		- размер данных для приема.
	* @param 	_timeout_	- время, за которое должен быть осуществлен прием.
	* @note 	Здесь вызывается только функция HAL, и ничего больше.
	*/
#define MEMSPI_SPI_Receive(_hmemspi_, _data_, _size_, _timeout_)  	HAL_SPI_Receive(&_hmemspi_->hspi, _data_, _size_, _timeout_)

/**  
	* @brief 	Defines for MEMORY chip.
	*/
#define 		MEMSPI_Select(_hmemspi_) 		(_hmemspi_->GPIOs.CS_GPIOx->BSRR = _hmemspi_->GPIOs.CS_PIN << 16)
#define 		MEMSPI_Deselect(_hmemspi_) 	(_hmemspi_->GPIOs.CS_GPIOx->BSRR = _hmemspi_->GPIOs.CS_PIN)
#define 		MEMSPI_SECTOR_SIZE					(0x1000)
#define 		MEMSPI_PAGE_SIZE						(0x100)
/**  
	* @brief 	Defines for CMD.
	*/
#define    	MEMSPI_READ_JEDEC_ID  			0x9F
#define    	MEMSPI_READ_UNIQUE_ID 			0x4B
#define    	MEMSPI_WRITE_ENABLE  				0x06
#define    	MEMSPI_WRITE_DISABLE  			0x04
#define    	MEMSPI_WRITE_STATUS_REG			0x01

#define    	MEMSPI_ERASE_SECTOR  				0x20
#define    	MEMSPI_PAGE_PROGRAM  				0x02
#define    	MEMSPI_WRITE_EEPROM  				MEMSPI_PAGE_PROGRAM
#define    	MEMSPI_READ_STATUS_REG			0x05
#if defined(SEPARATED_STATUS_REGISTER)
	#define    	MEMSPI_READ_STATUS_REG_2		0x35
#endif
#define    	MEMSPI_READ_DATA  					0x03
/**  
	* @brief 	Defines for Status Register.
	*/
#define 		MEMSPI_Get_Flag(_hmemspi_,_flag_)		(((MEMSPI_StatusRegisterTypeDef)(_hmemspi_->SR))._flag_)

// FLASH SR
#define 		MEMSPI_SR_SUS								(1<<15)
#define 		MEMSPI_SR_QE								(1<<9)
#define 		MEMSPI_SR_SRP1							(1<<8)
#define 		MEMSPI_SR_SRP0							(1<<7)
#define 		MEMSPI_SR_SEC								(1<<6)
#define 		MEMSPI_SR_TB								(1<<5)
#define 		MEMSPI_SR_BP2								(1<<4)
#define 		MEMSPI_SR_BP1								(1<<3)
#define 		MEMSPI_SR_BP0								(1<<2)
#define 		MEMSPI_SR_WEL								(1<<1)
#define 		MEMSPI_SR_BUSY							(1<<0)

// Exclusive (only by name) EEPROM SR bits
#define 		MEMSPI_SR_SRWD							MEMSPI_SR_SRP0
#define 		MEMSPI_SR_WIP								MEMSPI_SR_WEL
/**
	* @brief	Calc dividing including remainder (divide and ceil)
	*	@param 	_val_		- делимое.
	*	@param 	_div_		- делитель.
	*	@note 	Если результат деления без остатка: он возвращается как есть
	*					Если с остатком - округляется вверх
	*/
//#define Divide_Up(_val_, _div_)												(((_val_)%(_div_))? (_val_)/(_div_)+1 : (_val_)/_div_)	/* через тернарный оператор */
#define Divide_Up(_val_, _div_)												((_val_ - 1) / _div_) + 1																	/* через мат выражение */	

////////////////////////////---DEFINES---////////////////////////////


/////////////////////////////////////////////////////////////////////
///////////////////////---STRUCTURES & ENUMS---//////////////////////
typedef struct
{
	uint8_t 	*pDataPtr;
	uint32_t 	Data_Address;
	uint32_t 	Data_Size;
	
	uint32_t 	Sector_Address;
	uint32_t 	Sector_Size;
	unsigned 	fSavePrevoisData:1;
}MEMSPI_WriteInitTypeDef;

typedef struct
{
	uint16_t SR;
	SPI_HandleTypeDef hspi;
	GPIO_TypeDef 	*CS_GPIOx;
	uint32_t			CS_PIN;
	GPIO_TypeDef 	*CLK_GPIOx;
	uint32_t			CLK_PIN;
	GPIO_TypeDef 	*MISO_GPIOx;
	uint32_t			MISO_PIN;
	GPIO_TypeDef 	*MOSI_GPIOx;
	uint32_t			MOSI_PIN;
}MEMSPI_GPIOTypeDef;

typedef struct
{
	uint16_t SR;
	SPI_HandleTypeDef 		hspi;
	MEMSPI_GPIOTypeDef		GPIOs;
	uint32_t							hNextAddr;
	uint16_t							hNextPage;
	uint16_t							hNextSector;
}MEMSPI_HandleTypeDef;
extern MEMSPI_HandleTypeDef hmemspi;

///////////////////////---STRUCTURES & ENUMS---//////////////////////

/////////////////////////////////////////////////////////////////////
///////////////////////---FUNCTIONS FOR USER---//////////////////////
/**  
	* @brief 	Initialize SPI and GPIO for MEMSPI FLASH.
	* @param 	hmemspi - указатель на структуру с настройками SPI и GPIO портов.
	*/
void MEMSPI_Base_Init(MEMSPI_HandleTypeDef *hmemspi);

/** 
	* @brief 	Read external FLASH.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	FLASH_Address - адресс откуда начинать считывание.
	* @param 	pBuff	 				- куда записывать данные из FLASH.
	* @param 	Size	 				- сколько байтов считывать.
	* @param 	Timeout	 			- время, за которое должно быть осуществлено чтение.
	* @return HAL status.
	* @note		Включает в себя проверку на доступность памяти (флаг BUSY)
	*/
HAL_StatusTypeDef MEMSPI_Read_Memory(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint8_t *pBuff, uint16_t Size, uint32_t Timeout);

/** 
	* @brief 	Write external EEPROM.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	FLASH_Address - адресс куда начинать записывать.
	* @param 	pData	 				- откуда брать данные для записи в EEPROM.
	* @param 	Size	 				- сколько байтов записать.
	* @param 	Timeout	 			- время, за которое должна быть осуществлена запись.
	* @param 	WaitForEnd	 	- ожидание, пока память не выполненит операцию.
	* @return HAL status.
	* @note		Позволяет записать участок памяти. Можно записывать несколько страниц.
	*/
HAL_StatusTypeDef MEMSPI_EEPROM_Write(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint8_t *pData, uint16_t Size, uint32_t Timeout, uint8_t WaitForEnd);

/** 
	* @brief 	Write external FLASH.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	WriteInit 		- указатель на структуру, определяющую участок памяти для записи.
	* @param 	Timeout	 			- время, за которое должно быть осуществлено чтение.
	* @param 	WaitForEnd	 	- ожидание, пока память не выполненит операцию.
	* @return HAL status.
	* @note		Позволяет перепрограммировать участок памяти. Можно записывать несколько страниц.
	*					Данные в сектора участка, но за пределами участка не сохраняются.
	*/
HAL_StatusTypeDef MEMSPI_FLASH_Write(MEMSPI_HandleTypeDef *hmemspi, MEMSPI_WriteInitTypeDef *WriteInit, uint32_t Timeout, uint8_t WaitForEnd);

/** 
	* @brief 	Program external FLASH.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	FLASH_Address - адресс куда начинать записывать.
	* @param 	pData	 				- откуда брать данные для записи в FLASH.
	* @param 	Size	 				- сколько байтов записать.
	* @param 	Timeout	 			- время, за которое должно быть осуществлено чтение.
	* @param 	WaitForEnd	 	- ожидание, пока память не выполненит операцию.
	* @return HAL status.
	* @note		Программирование участка памяти, без ограничений на кол-во байт
	*/
HAL_StatusTypeDef MEMSPI_FLASH_Program(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint8_t *pData, uint16_t Size, uint32_t Timeout, uint8_t WaitForEnd);

/** 
	* @brief 	Erase external FLASH.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	FLASH_Address - адресс где надо данные стереть.
	* @param 	Size	 				- сколько байтов стереть.
	* @param 	Timeout	 			- время, за которое должно быть осуществлена очистка.
	* @param 	WaitForEnd	 	- ожидание, пока память не выполненит операцию.
	* @return HAL status.
	* @note		Т.к. очитска происходит по секторам, Size нужен, чтобы определить сколько секторов очистить
	*					И если начальны адресс будет на Sector 0, а последний байт на Sector 1, то произойдет очистка Sector 0 и Sector 1
	*/
HAL_StatusTypeDef MEMSPI_FLASH_Erase(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint16_t Size, uint32_t Timeout, uint8_t WaitForEnd);
///////////////////////---FUNCTIONS FOR USER---//////////////////////

/////////////////////////////////////////////////////////////////////
////////////////////////---SERVICE FUNCTIONS---//////////////////////
/** 
	* @brief 	Write page in external EEPROM.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	FLASH_Address - адресс куда начинать записывать.
	* @param 	pData	 				- откуда брать данные для записи в EEPROM.
	* @param 	Size	 				- сколько байтов записать.
	* @param 	Timeout	 			- время, за которое должна быть осуществлена запись.
	* @param 	tickstart	 		- время, относительно которого надо отсчитывать таймаут.
	* @param 	WaitForEnd	 	- ожидание, пока память не выполненит операцию.
	* @return HAL status.
	* @note		Позволяет записывать только байты в пределах одной страницы.
						Для более гибкой записи есть функция MEMSPI_EEPROM_Write, которая программирует участки любой длины (в теории).
	*/
HAL_StatusTypeDef MEMSPI_EEPROM_Write_Page(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint8_t *pData, uint16_t Size, uint32_t *Timeout, uint32_t *tickstart, uint8_t WaitForEnd);
/** 
	* @brief 	Erase external FLASH Sector.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	FLASH_Address - адресс где надо данные стереть.
	* @param 	Timeout	 			- время, за которое должно быть осуществлена очистка.
	* @param 	tickstart	 		- время, относительно которого надо отсчитывать таймаут.
	* @param 	WaitForEnd	 	- ожидание, пока память не выполненит операцию.
	* @return HAL status.
	* @note		При Timeout = 0, функция не будет ожидать окончания очистки (выставления в 0 флагов BUSY и WEL)
	* @note		Микросхема вроде сама высчитывает какой сектор ерейзнуть, в соответствии с заданным адресом.
	*/
HAL_StatusTypeDef MEMSPI_FLASH_Erase_Sector(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint32_t *Timeout, uint32_t *tickstart, uint8_t WaitForEnd);

/** 
	* @brief 	Program page in external FLASH.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	FLASH_Address - адресс куда начинать записывать.
	* @param 	pData	 				- откуда брать данные для записи в FLASH.
	* @param 	Size	 				- сколько байтов записать.
	* @param 	Timeout	 			- время, за которое должна быть осуществлена запись.
	* @param 	tickstart	 		- время, относительно которого надо отсчитывать таймаут.
	* @param 	WaitForEnd	 	- ожидание, пока память не выполненит операцию.
	* @return HAL status.
	* @note		Позволяет перепрограммировать только байты в прелелах одной страницы.
						Для более гибкого программирования есть функция MEMSPI_FLASH_Program, которая программирует участки любой длины (в теории).
	*/
HAL_StatusTypeDef MEMSPI_FLASH_Program_Page(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint8_t *pData, uint16_t Size, uint32_t *Timeout, uint32_t *tickstart, uint8_t WaitForEnd);

/** 
	* @brief 	Setting WEL bit until it setted or until timeout.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	Timeout	 			- время, за которое должно быть осуществлено чтение.
	* @param 	tickstart	 		- время, относительно которого надо отсчитывать таймаут.
	* @return HAL status.
	* @note		Подает команду на разрешение записи до тех пор, пока она запись не разрешиться или до тех пор, пока таймаут не истечет.
	*/
HAL_StatusTypeDef MEMSPI_WriteEnablingUntilTimeout(MEMSPI_HandleTypeDef *hmemspi, uint32_t *Timeout, uint32_t *tickstart);

/** 
	* @brief 	Wait for flag until timeout.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	FlagMask	 		- маска для флагов, какие флаги считывать.
	* @param 	FlagStatus	 	- какое состояние должно быть у выбранных флагов.
	* @param 	Timeout	 			- время, за которое должно быть осуществлено чтение.
	* @param 	tickstart	 		- время, относительно которого надо отсчитывать таймаут.
	* @return HAL status.
	* @note		Считывает флаги до тех пор, пока они не будут в состоянии FlagStatus или до тех пор, пока таймаут не истечет.
	*/
HAL_StatusTypeDef MEMSPI_WaitOnFlagsUntilTimeout(MEMSPI_HandleTypeDef *hmemspi, uint16_t FlagMask, uint16_t FlagStatus, uint32_t *Timeout, uint32_t *tickstart);
/** 
	* @brief 	Update Timeout variables.
	* @param 	Timeout 		- указатель на переменную содержащию значение таймаута
	* @param 	tickstart 	- указатель на переменную содержащию начальное кол-во тиков.
	*/
void MEMSPI_Update_Timeout_Variables(uint32_t *Timeout, uint32_t *tickstart);
////////////////////////---SERVICE FUNCTIONS---//////////////////////

/////////////////////////////////////////////////////////////////////
//////////////////////---FUNCTION FOR COMMAND---/////////////////////
/** 
	* @brief 	Send command to read Status Register.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	RequestedBits - какие биты запросить.
	* @param 	Timeout	 			- время, за которое должно быть осуществлено чтение.
	* @return Заполняет Status Register в hmemspi.
	* @note		Всего есть две комманды: на запрос верхнего или нижниго байта. 
	*					Функция в соответствии с RequestedBits определяет какой байт запросить, или два байта сразу.
	*/
HAL_StatusTypeDef MEMSPI_CMD_Read_Status_Register(MEMSPI_HandleTypeDef *hmemspi, uint16_t RequestedBits, uint8_t EndCMD, uint32_t Timeout);

/** 
	* @brief 	Send command to write bits in Status Register.
	* @param 	hmemspi 		- указатель на хендл внешней памяти.
	* @param 	WrittenBits - какие биты запросить.
	* @param 	Timeout	 		- время, за которое должна быть осуществлена запись.
	* @note		Данная команда посылает биты, как сдвинутые на 2 вправо. Т.е. 0-й бит в комманде - 2-й бит BP0.
						Но биты указываются в также как они расположены и регистре. Функция сама выполняет сдвиг. 
	*/
HAL_StatusTypeDef MEMSPI_CMD_Write_Status_Register(MEMSPI_HandleTypeDef *hmemspi, uint16_t WrittenBits, uint32_t Timeout);

/** 
	* @brief 	Send command to set Write Enable Latch (WEL) in Status Register.
	* @param 	hmemspi - указатель на хендл внешней памяти.
	* @param 	Timeout	- время, за которое должна быть осуществлена запись.
	* @note 	Разрешает запись в FLASH, путем высталения WEL в Status Register
	*/
HAL_StatusTypeDef MEMSPI_CMD_Write_Enable(MEMSPI_HandleTypeDef *hmemspi, uint32_t Timeout);

/** 
	* @brief 	Send command to read data from FLASH/EEPROM.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	FLASH_Address - адресс откуда начинать считывание.
	* @param 	pBuff	 				- куда записывать данные из FLASH.
	* @param 	Size	 				- сколько байтов считывать.
	* @param 	Timeout	 			- время, за которое должно быть осуществлено чтение.
	* @return pBuff.
	*/
HAL_StatusTypeDef MEMSPI_CMD_Read_Data(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint8_t *pBuff, uint16_t Size, uint32_t Timeout);
/** 
	* @brief 	Send command to write eeprom.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	FLASH_Address - адресс куда начинать записывать.
	* @param 	pData	 				- откуда брать данные для записи в FLASH.
	* @param 	Size	 				- сколько байтов записать.
	* @param 	Timeout				- время, за которое должна быть осуществлена запись.
	* @note		Рзамер данных для записи в EEPROM без ограничений. 
	*/
HAL_StatusTypeDef MEMSPI_CMD_EEPROM_Write(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint8_t *pData, uint16_t Size, uint32_t Timeout);
/** 
	* @brief 	Send command to page program in FLASH.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	FLASH_Address - адресс куда начинать записывать.
	* @param 	pData	 				- откуда брать данные для записи в FLASH.
	* @param 	Size	 				- сколько байтов записать.
	* @param 	Timeout				- время, за которое должна быть осуществлена запись.
	* @note		Программирование FLASH только в пределах одной страницы. 
	*					Т.е. если запись с 0x0, то не больше 256 байт. Если с 0ч40, то не больше 192 байт.
	*/
HAL_StatusTypeDef MEMSPI_CMD_FLASH_Page_Program(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint8_t *pData, uint16_t Size, uint32_t Timeout);

/** 
	* @brief 	Send command to erase sector of FLASH.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	FLASH_Address - адресс где надо данные стереть.
	* @param 	Timeout				- время, за которое должна быть осуществлена очистка.
	* @note		Микросхема вроде сама высчитывает какой сектор ерейзнуть, в соответствии с заданным адресом.
	*/
HAL_StatusTypeDef MEMSPI_CMD_FLASH_Erase_Sector(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint32_t Timeout);

/** 
	* @brief 	Send command to read JEDEC ID.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	Timeout	 			- время, за которое должно быть осуществлено чтение.
	* @return	JEDEC ID микросхемы.
	*/
uint32_t MEMSPI_CMD_Read_JEDEC_ID(MEMSPI_HandleTypeDef *hmemspi, uint32_t Timeout);

/** 
	* @brief 	Send command to read JEDEC ID.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	Timeout	 			- время, за которое должно быть осуществлено чтение.
	* @return	Device ID микросхемы.
	*/
uint64_t MEMSPI_CMD_Read_Device_ID(MEMSPI_HandleTypeDef *hmemspi, uint32_t Timeout);

/** 
	* @brief 	Send command to fast read data from FLASH.
	* @param 	hmemspi 			- указатель на хендл внешней памяти.
	* @param 	FLASH_Address - адресс откуда начинать считывание.
	* @param 	pBuff	 				- куда записывать данные из FLASH.
	* @param 	Size	 				- сколько байтов считывать.
	* @param 	Timeout	 			- время, за которое должно быть осуществлено чтение.
	* @note		Данная функция предполагает отправку одного dummy байта после адресса, но у меня поч не работает пока :(
	*/
HAL_StatusTypeDef MEMSPI_CMD_Fast_Read(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint8_t *pBuff, uint16_t Size, uint32_t Timeout);
//////////////////////---FUNCTION FOR COMMAND---/////////////////////


#endif // __SPI_MEMORY_H_