/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include "../inc/MarlinConfig.h"
#if HAS_SPI_FLASH
#include "W25Qxx.h"
W25QXXFlash W25QXX;
#ifndef SPI_FLASH_MISO_PIN
#define SPI_FLASH_MISO_PIN W25QXX_MISO_PIN
#endif
#ifndef SPI_FLASH_MOSI_PIN
#define SPI_FLASH_MOSI_PIN W25QXX_MOSI_PIN
#endif
#ifndef SPI_FLASH_SCK_PIN
#define SPI_FLASH_SCK_PIN W25QXX_SCK_PIN
#endif
#ifndef SPI_FLASH_CS_PIN
#define SPI_FLASH_CS_PIN W25QXX_CS_PIN
#endif
#ifndef NC
#define NC -1
#endif
MarlinSPI W25QXXFlash::mySPI(SPI_FLASH_MOSI_PIN, SPI_FLASH_MISO_PIN, SPI_FLASH_SCK_PIN, NC);
#define W25QXX_CS_H OUT_WRITE(SPI_FLASH_CS_PIN, HIGH)
#define W25QXX_CS_L OUT_WRITE(SPI_FLASH_CS_PIN, LOW)
bool flash_dma_mode = true;
void W25QXXFlash::init(uint8_t spiRate) {
OUT_WRITE(SPI_FLASH_CS_PIN, HIGH);
/**
* STM32F1 APB2 = 72MHz, APB1 = 36MHz, max SPI speed of this MCU if 18Mhz
* STM32F1 has 3 SPI ports, SPI1 in APB2, SPI2/SPI3 in APB1
* so the minimum prescale of SPI1 is DIV4, SPI2/SPI3 is DIV2
*/
#if SPI_DEVICE == 1
#define SPI_CLOCK_MAX SPI_CLOCK_DIV4
#else
#define SPI_CLOCK_MAX SPI_CLOCK_DIV2
#endif
uint8_t clock;
switch (spiRate) {
case SPI_FULL_SPEED: clock = SPI_CLOCK_MAX; break;
case SPI_HALF_SPEED: clock = SPI_CLOCK_DIV4; break;
case SPI_QUARTER_SPEED: clock = SPI_CLOCK_DIV8; break;
case SPI_EIGHTH_SPEED: clock = SPI_CLOCK_DIV16; break;
case SPI_SPEED_5: clock = SPI_CLOCK_DIV32; break;
case SPI_SPEED_6: clock = SPI_CLOCK_DIV64; break;
default: clock = SPI_CLOCK_DIV2;// Default from the SPI library
}
mySPI.setClockDivider(clock);
mySPI.setBitOrder(MSBFIRST);
mySPI.setDataMode(SPI_MODE0);
mySPI.begin();
}
/**
* @brief Receive a single byte from the SPI port.
*
* @return Byte received
*/
uint8_t W25QXXFlash::spi_flash_Rec() {
const uint8_t returnByte = mySPI.transfer(0xFF);
return returnByte;
}
uint8_t W25QXXFlash::spi_flash_read_write_byte(uint8_t data) {
const uint8_t returnByte = mySPI.transfer(data);
return returnByte;
}
/**
* @brief Receive a number of bytes from the SPI port to a buffer
*
* @param buf Pointer to starting address of buffer to write to.
* @param nbyte Number of bytes to receive.
* @return Nothing
*
* @details Uses DMA
*/
void W25QXXFlash::spi_flash_Read(uint8_t* buf, uint16_t nbyte) {
mySPI.dmaTransfer(0, const_cast(buf), nbyte);
}
/**
* @brief Send a single byte on SPI port
*
* @param b Byte to send
*
* @details
*/
void W25QXXFlash::spi_flash_Send(uint8_t b) { mySPI.transfer(b); }
/**
* @brief Write token and then write from 512 byte buffer to SPI (for SD card)
*
* @param buf Pointer with buffer start address
* @return Nothing
*
* @details Use DMA
*/
void W25QXXFlash::spi_flash_SendBlock(uint8_t token, const uint8_t* buf) {
mySPI.transfer(token);
mySPI.dmaSend(const_cast(buf), 512);
}
uint16_t W25QXXFlash::W25QXX_ReadID(void) {
uint16_t Temp = 0;
W25QXX_CS_L;
spi_flash_Send(0x90);
spi_flash_Send(0x00);
spi_flash_Send(0x00);
spi_flash_Send(0x00);
Temp |= spi_flash_Rec() << 8;
Temp |= spi_flash_Rec();
W25QXX_CS_H;
return Temp;
}
void W25QXXFlash::SPI_FLASH_WriteEnable(void) {
// Select the FLASH: Chip Select low
W25QXX_CS_L;
// Send "Write Enable" instruction
spi_flash_Send(W25X_WriteEnable);
// Deselect the FLASH: Chip Select high
W25QXX_CS_H;
}
/*******************************************************************************
* Function Name : SPI_FLASH_WaitForWriteEnd
* Description : Polls the status of the Write In Progress (WIP) flag in the
* FLASH's status register and loop until write opertaion
* has completed.
* Input : None
* Output : None
* Return : None
*******************************************************************************/
void W25QXXFlash::SPI_FLASH_WaitForWriteEnd(void) {
uint8_t FLASH_Status = 0;
// Select the FLASH: Chip Select low
W25QXX_CS_L;
// Send "Read Status Register" instruction
spi_flash_Send(W25X_ReadStatusReg);
// Loop as long as the memory is busy with a write cycle
do
/* Send a dummy byte to generate the clock needed by the FLASH
and put the value of the status register in FLASH_Status variable */
FLASH_Status = spi_flash_Rec();
while ((FLASH_Status & WIP_Flag) == 0x01); // Write in progress
// Deselect the FLASH: Chip Select high
W25QXX_CS_H;
}
void W25QXXFlash::SPI_FLASH_SectorErase(uint32_t SectorAddr) {
// Send write enable instruction
SPI_FLASH_WriteEnable();
// Sector Erase
// Select the FLASH: Chip Select low
W25QXX_CS_L;
// Send Sector Erase instruction
spi_flash_Send(W25X_SectorErase);
// Send SectorAddr high nibble address byte
spi_flash_Send((SectorAddr & 0xFF0000) >> 16);
// Send SectorAddr medium nibble address byte
spi_flash_Send((SectorAddr & 0xFF00) >> 8);
// Send SectorAddr low nibble address byte
spi_flash_Send(SectorAddr & 0xFF);
// Deselect the FLASH: Chip Select high
W25QXX_CS_H;
// Wait the end of Flash writing
SPI_FLASH_WaitForWriteEnd();
}
void W25QXXFlash::SPI_FLASH_BlockErase(uint32_t BlockAddr) {
SPI_FLASH_WriteEnable();
W25QXX_CS_L;
// Send Sector Erase instruction
spi_flash_Send(W25X_BlockErase);
// Send SectorAddr high nibble address byte
spi_flash_Send((BlockAddr & 0xFF0000) >> 16);
// Send SectorAddr medium nibble address byte
spi_flash_Send((BlockAddr & 0xFF00) >> 8);
// Send SectorAddr low nibble address byte
spi_flash_Send(BlockAddr & 0xFF);
W25QXX_CS_H;
SPI_FLASH_WaitForWriteEnd();
}
/*******************************************************************************
* Function Name : SPI_FLASH_BulkErase
* Description : Erases the entire FLASH.
* Input : None
* Output : None
* Return : None
*******************************************************************************/
void W25QXXFlash::SPI_FLASH_BulkErase(void) {
// Send write enable instruction
SPI_FLASH_WriteEnable();
// Bulk Erase
// Select the FLASH: Chip Select low
W25QXX_CS_L;
// Send Bulk Erase instruction
spi_flash_Send(W25X_ChipErase);
// Deselect the FLASH: Chip Select high
W25QXX_CS_H;
// Wait the end of Flash writing
SPI_FLASH_WaitForWriteEnd();
}
/*******************************************************************************
* Function Name : SPI_FLASH_PageWrite
* Description : Writes more than one byte to the FLASH with a single WRITE
* cycle(Page WRITE sequence). The number of byte can't exceed
* the FLASH page size.
* Input : - pBuffer : pointer to the buffer containing the data to be
* written to the FLASH.
* - WriteAddr : FLASH's internal address to write to.
* - NumByteToWrite : number of bytes to write to the FLASH,
* must be equal or less than "SPI_FLASH_PageSize" value.
* Output : None
* Return : None
*******************************************************************************/
void W25QXXFlash::SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite) {
// Enable the write access to the FLASH
SPI_FLASH_WriteEnable();
// Select the FLASH: Chip Select low
W25QXX_CS_L;
// Send "Write to Memory " instruction
spi_flash_Send(W25X_PageProgram);
// Send WriteAddr high nibble address byte to write to
spi_flash_Send((WriteAddr & 0xFF0000) >> 16);
// Send WriteAddr medium nibble address byte to write to
spi_flash_Send((WriteAddr & 0xFF00) >> 8);
// Send WriteAddr low nibble address byte to write to
spi_flash_Send(WriteAddr & 0xFF);
NOMORE(NumByteToWrite, SPI_FLASH_PerWritePageSize);
// While there is data to be written on the FLASH
while (NumByteToWrite--) {
// Send the current byte
spi_flash_Send(*pBuffer);
// Point on the next byte to be written
pBuffer++;
}
// Deselect the FLASH: Chip Select high
W25QXX_CS_H;
// Wait the end of Flash writing
SPI_FLASH_WaitForWriteEnd();
}
/*******************************************************************************
* Function Name : SPI_FLASH_BufferWrite
* Description : Writes block of data to the FLASH. In this function, the
* number of WRITE cycles are reduced, using Page WRITE sequence.
* Input : - pBuffer : pointer to the buffer containing the data to be
* written to the FLASH.
* - WriteAddr : FLASH's internal address to write to.
* - NumByteToWrite : number of bytes to write to the FLASH.
* Output : None
* Return : None
*******************************************************************************/
void W25QXXFlash::SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite) {
uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
Addr = WriteAddr % SPI_FLASH_PageSize;
count = SPI_FLASH_PageSize - Addr;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
if (Addr == 0) { // WriteAddr is SPI_FLASH_PageSize aligned
if (NumOfPage == 0) { // NumByteToWrite < SPI_FLASH_PageSize
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
else { // NumByteToWrite > SPI_FLASH_PageSize
while (NumOfPage--) {
SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
else { // WriteAddr is not SPI_FLASH_PageSize aligned
if (NumOfPage == 0) { // NumByteToWrite < SPI_FLASH_PageSize
if (NumOfSingle > count) { // (NumByteToWrite + WriteAddr) > SPI_FLASH_PageSize
temp = NumOfSingle - count;
SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
}
else
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
else { // NumByteToWrite > SPI_FLASH_PageSize
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
while (NumOfPage--) {
SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
if (NumOfSingle != 0)
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
}
/*******************************************************************************
* Function Name : SPI_FLASH_BufferRead
* Description : Reads a block of data from the FLASH.
* Input : - pBuffer : pointer to the buffer that receives the data read
* from the FLASH.
* - ReadAddr : FLASH's internal address to read from.
* - NumByteToRead : number of bytes to read from the FLASH.
* Output : None
* Return : None
*******************************************************************************/
void W25QXXFlash::SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead) {
// Select the FLASH: Chip Select low
W25QXX_CS_L;
// Send "Read from Memory " instruction
spi_flash_Send(W25X_ReadData);
// Send ReadAddr high nibble address byte to read from
spi_flash_Send((ReadAddr & 0xFF0000) >> 16);
// Send ReadAddr medium nibble address byte to read from
spi_flash_Send((ReadAddr & 0xFF00) >> 8);
// Send ReadAddr low nibble address byte to read from
spi_flash_Send(ReadAddr & 0xFF);
if (NumByteToRead <= 32 || !flash_dma_mode) {
while (NumByteToRead--) { // While there is data to be read
// Read a byte from the FLASH
*pBuffer = spi_flash_Rec();
// Point to the next location where the byte read will be saved
pBuffer++;
}
}
else
spi_flash_Read(pBuffer, NumByteToRead);
W25QXX_CS_H;
}
#endif // HAS_SPI_FLASH