/* spiram-fast - Fast, hardcoded interface for SPI-based RAMs, allowing DIO mode to be used and speeding up individual SPI operations significantly. Copyright (c) 2020 Earle F. Philhower, III All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef spiram_fast_h #define spiram_fast_h #include #ifndef ESP32 class ESP8266SPIRAM { private: typedef struct { volatile uint32_t spi_cmd; // The SPI can change this behind our backs, so volatile! uint32_t spi_addr; uint32_t spi_ctrl; uint32_t spi_ctrl1; // undocumented? Not shown in the reg map uint32_t spi_rd_status; uint32_t spi_ctrl2; uint32_t spi_clock; uint32_t spi_user; uint32_t spi_user1; uint32_t spi_user2; uint32_t spi_wr_status; uint32_t spi_pin; uint32_t spi_slave; uint32_t spi_slave1; uint32_t spi_slave2; uint32_t spi_slave3; uint32_t spi_w[16]; // NOTE: You need a memory barrier before reading these after a read xaction uint32_t spi_ext3; } spi_regs; spi_regs *spi1 = (spi_regs*)&SPI1CMD; // The standard HSPI bus pins are used static constexpr uint8_t cs = 15; static constexpr uint8_t miso = 12; static constexpr uint8_t mosi = 13; static constexpr uint8_t sck = 14; uint32_t spi_clkval; uint32_t csPin; bool swCS; typedef enum { sio = 0, dio = 1 } iotype; static constexpr iotype hspi_mode = sio; static constexpr int read_delay = (hspi_mode == dio) ? 4-1 : 0; void spi_init() { pinMode(sck, SPECIAL); pinMode(miso, SPECIAL); pinMode(mosi, SPECIAL); pinMode(csPin, swCS ? OUTPUT : SPECIAL ); spi1->spi_cmd = 0; GPMUX &= ~(1 << 9); spi1->spi_clock = spi_clkval; spi1->spi_ctrl = 0 ; // MSB first + plain SPI mode spi1->spi_ctrl1 = 0; // undocumented, clear for safety? spi1->spi_ctrl2 = 0; // No add'l delays on signals spi1->spi_user2 = 0; // No insn or insn_bits to set } // The SPI hardware cannot make the "command" portion dual or quad, only the addr and data // So using the command portion of the cycle will not work. Concatenate the address // and command into a single 32-bit chunk "address" which will be sent across both bits. void spi_writetransaction(int addr, int addr_bits, int dummy_bits, int data_bits, iotype dual) { // Ensure no writes are still ongoing while (spi1->spi_cmd & SPIBUSY) { delay(0); } spi1->spi_addr = addr; spi1->spi_user = (addr_bits? SPIUADDR : 0) | (dummy_bits ? SPIUDUMMY : 0) | (data_bits ? SPIUMOSI : 0) | (dual ? SPIUFWDIO : 0); spi1->spi_user1 = (addr_bits << 26) | (data_bits << 17) | dummy_bits; // No need to set spi_user2, insn field never used __asm ( "" ::: "memory" ); if (swCS) { pinMode(csPin, OUTPUT); digitalWrite(csPin, LOW); spi1->spi_cmd = SPIBUSY; while (spi1->spi_cmd & SPIBUSY) { delay(0); } digitalWrite(csPin, HIGH); } else { spi1->spi_cmd = SPIBUSY; // The write may continue on in the background } } uint32_t spi_readtransaction(int addr, int addr_bits, int dummy_bits, int data_bits, iotype dual) { // Ensure no writes are still ongoing while (spi1->spi_cmd & SPIBUSY) { delay(0); } spi1->spi_addr = addr; spi1->spi_user = (addr_bits? SPIUADDR : 0) | (dummy_bits ? SPIUDUMMY : 0) | SPIUMISO | (dual ? SPIUFWDIO : 0); spi1->spi_user1 = (addr_bits << 26) | (data_bits << 8) | dummy_bits; // No need to set spi_user2, insn field never used __asm ( "" ::: "memory" ); if (swCS) { pinMode(csPin, OUTPUT); digitalWrite(csPin, LOW); } spi1->spi_cmd = SPIBUSY; while (spi1->spi_cmd & SPIBUSY) { delay(0); } __asm ( "" ::: "memory" ); if (swCS) { digitalWrite(csPin, HIGH); } return spi1->spi_w[0]; } public: ESP8266SPIRAM() { /* noop */ } ~ESP8266SPIRAM() { end(); } void readBytes(uint32_t addr, void *destV, int count) { uint8_t *dest = (uint8_t*)destV; while (count > 0) { int toRead = std::min(count, 64); spi_readtransaction((0x03 << 24) | addr, 32-1, read_delay, toRead * 8 - 1, hspi_mode); __asm ( "" ::: "memory" ); uint32_t work[16]; // FIFO image in RAM that we can byte address int toCopy = (toRead + 3) / 4; // make sure all 32b values updated for (auto i = 0; i < toCopy; i++) { work[i] = spi1->spi_w[i]; } memcpy(dest, work, toRead); count -= toRead; dest += toRead; addr += toRead; } } void writeBytes(uint32_t addr, const void *srcV, int count) { const uint8_t *src = (const uint8_t *)srcV; while (count > 0) { uint32_t work[16]; // FIFO image in RAM that we can byte address int toWrite = std::min(count, 64); memcpy((void *)work, src, toWrite); int toCopy = (toWrite + 3) / 4; // make sure all 32b values updated // Ensure the last SPI is done so we don't overwrite unsent data while (spi1->spi_cmd & SPIBUSY) { delay(0); } for (auto i = 0; i < toCopy; i++) { spi1->spi_w[i] = work[i]; } spi_writetransaction((0x02 << 24) | addr, 32 - 1, 0, toWrite * 8 - 1, hspi_mode); count -= toWrite; src += toWrite; addr += toWrite; } } void begin(int freqMHz, int cs_pin) { if (freqMHz >= 40) { spi_clkval = 0x00001001; } else if (freqMHz >= 30) { spi_clkval = 0x00002001; } else if (freqMHz >= 20) { spi_clkval = 0x00041001; } else if (freqMHz >= 10) { spi_clkval = 0x000c1001; } else if (freqMHz >= 5) { spi_clkval = 0x001c1001; } else { spi_clkval = 0x009c1001; } csPin = cs_pin; swCS = csPin != cs; // Manually reset chip from DIO to SIO mode (HW SPI has issues with <8 bits/clocks total output) digitalWrite(csPin, HIGH); digitalWrite(mosi, HIGH); digitalWrite(miso, HIGH); digitalWrite(sck, LOW); pinMode(csPin, OUTPUT); pinMode(miso, OUTPUT); pinMode(mosi, OUTPUT); pinMode(sck, OUTPUT); digitalWrite(csPin, HIGH); delay(100); digitalWrite(csPin, LOW); delay(1); for (int i = 0; i < 4; i++) { digitalWrite(sck, HIGH); delay(1); digitalWrite(sck, LOW); delay(1); } digitalWrite(csPin, HIGH); // Set up the SPI regs spi_init(); // Enable streaming read/write mode spi1->spi_w[0] = 0x40; spi_writetransaction(0x01<<24, 8-1, 0, 8-1, sio); if (hspi_mode == dio) { // Ramp up to DIO mode spi_writetransaction(0x3b<<24, 8-1, 0, 0, sio); spi1->spi_ctrl |= SPICDIO | SPICFASTRD; } } void end() { pinMode(csPin, INPUT); pinMode(miso, INPUT); pinMode(mosi, INPUT); pinMode(sck, INPUT); } }; #else #include class ESP8266SPIRAM { private: uint8_t csPin; uint32_t freq; // The standard HSPI bus pins are used static constexpr uint8_t miso = 12; static constexpr uint8_t mosi = 13; static constexpr uint8_t sck = 14; public: ESP8266SPIRAM() { /* noop */ } ~ESP8266SPIRAM() { end(); } void readBytes(uint32_t addr, void *destV, int count) { uint8_t *dest = (uint8_t *)destV; SPI.beginTransaction(SPISettings(freq, MSBFIRST, SPI_MODE0)); pinMode(csPin, OUTPUT); digitalWrite(csPin, LOW); uint32_t cmd = (0x03 << 24) | (addr & ((1<<24)-1)); SPI.transfer((uint8_t)(cmd >> 24)); SPI.transfer((uint8_t)(cmd >> 16)); SPI.transfer((uint8_t)(cmd >> 8)); SPI.transfer((uint8_t)(cmd >> 0)); while (count--) { *(dest++) = SPI.transfer((uint8_t)0); } pinMode(csPin, OUTPUT); digitalWrite(csPin, HIGH); SPI.endTransaction(); } void writeBytes(uint32_t addr, const void *srcV, int count) { const uint8_t *src = (const uint8_t *)srcV; SPI.beginTransaction(SPISettings(freq, MSBFIRST, SPI_MODE0)); pinMode(csPin, OUTPUT); digitalWrite(csPin, LOW); uint32_t cmd = (0x02 << 24) | (addr & ((1<<24)-1)); SPI.transfer((uint8_t)(cmd >> 24)); SPI.transfer((uint8_t)(cmd >> 16)); SPI.transfer((uint8_t)(cmd >> 8)); SPI.transfer((uint8_t)(cmd >> 0)); while (count--) { SPI.transfer((uint8_t)*(src++)); } pinMode(csPin, OUTPUT); digitalWrite(csPin, HIGH); SPI.endTransaction(); } void begin(int freqMHz, int cs_pin) { csPin = cs_pin; freq = freqMHz * 1000000; SPI.begin(); // Manually reset chip from DIO to SIO mode (HW SPI has issues with <8 bits/clocks total output) digitalWrite(csPin, HIGH); digitalWrite(mosi, HIGH); digitalWrite(miso, HIGH); digitalWrite(sck, LOW); pinMode(csPin, OUTPUT); pinMode(miso, OUTPUT); pinMode(mosi, OUTPUT); pinMode(sck, OUTPUT); digitalWrite(csPin, HIGH); delay(100); digitalWrite(csPin, LOW); delay(1); for (int i = 0; i < 4; i++) { digitalWrite(sck, HIGH); delay(1); digitalWrite(sck, LOW); delay(1); } digitalWrite(csPin, HIGH); pinMode(sck, SPECIAL); pinMode(miso, SPECIAL); pinMode(mosi, SPECIAL); pinMode(csPin, OUTPUT); // Enable streaming read/write mode SPI.beginTransaction(SPISettings(freq, MSBFIRST, SPI_MODE0)); pinMode(csPin, OUTPUT); digitalWrite(csPin, LOW); SPI.transfer((uint8_t) 0x01); SPI.transfer((uint8_t) 0x40); pinMode(csPin, OUTPUT); digitalWrite(csPin, HIGH); SPI.endTransaction(); pinMode(csPin, OUTPUT); digitalWrite(csPin, HIGH); } void end() { pinMode(csPin, INPUT); pinMode(miso, INPUT); pinMode(mosi, INPUT); pinMode(sck, INPUT); SPI.end(); } }; #endif // ESP32 #endif