367 lines
12 KiB
C++
367 lines
12 KiB
C++
/*
|
|
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 <Arduino.h>
|
|
|
|
#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 <SPI.h>
|
|
|
|
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 |