/** * 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 operation 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 nybble address byte spi_flash_Send((SectorAddr & 0xFF0000) >> 16); // Send SectorAddr medium nybble address byte spi_flash_Send((SectorAddr & 0xFF00) >> 8); // Send SectorAddr low nybble 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 nybble address byte spi_flash_Send((BlockAddr & 0xFF0000) >> 16); // Send SectorAddr medium nybble address byte spi_flash_Send((BlockAddr & 0xFF00) >> 8); // Send SectorAddr low nybble 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 nybble address byte to write to spi_flash_Send((WriteAddr & 0xFF0000) >> 16); // Send WriteAddr medium nybble address byte to write to spi_flash_Send((WriteAddr & 0xFF00) >> 8); // Send WriteAddr low nybble 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 nybble address byte to read from spi_flash_Send((ReadAddr & 0xFF0000) >> 16); // Send ReadAddr medium nybble address byte to read from spi_flash_Send((ReadAddr & 0xFF00) >> 8); // Send ReadAddr low nybble 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