diff --git a/README.md b/README.md index 1c309f7c..8bb2f501 100644 --- a/README.md +++ b/README.md @@ -30,29 +30,16 @@ A neat MQTT-driven ESP8266 light-and-sound device (alarm? toy? who can say!) was A very interesting "linear clock" with a stepper motor, NTP time keeping, and configurable recorded chimes with schematics, 3D printer plans, and source code, is now available http://home.kpn.nl/bderogee1980/projects/linear_clock/linear_clock.html ## Prerequisites -First, make sure you are running the 2.4 or GIT head version of the Arduino libraries for ESP8266, or the latest ESP32 SDK from Espressif. +First, make sure you are running the 2.6.3/later or GIT head version of the Arduino libraries for ESP8266, or the latest ESP32 SDK from Espressif. You can use GIT to pull right from GitHub: see [this README](https://github.com/esp8266/Arduino/blob/master/README.md#using-git-version) for detailed instructions. -## ESP-32 SPIFFS Errors -The latest official release of the ESP32-Arduino seems to have broken SPIFFS, but a patch has just been committed to git head. If you want to run SPIFFS, please follow the directions below, courtesy of @rfestag: -```sh -cd ~/Arduino/hardware/espressif/esp32 # Or wherever you have it installed -git pull # Update to the latest -cd tools -python get.py # On my system, I have python3 installed by default, so I had to run python2.7 get.py -# Re-upload files using the new mkspiffs that is installed -# Then reload your sketch -``` -Be sure to use the [ESP32 SPIFFS](https://github.com/me-no-dev/arduino-esp32fs-plugin) upload plugin before running your sketch to upload the data files once the fixed IDE is set up. - ## Installation -Install the library and the SPI driver library in your ~/Arduino/libraries +Install the library in your ~/Arduino/libraries ```sh mkdir -p ~/Arduino/libraries cd ~/Arduino/libraries git clone https://github.com/earlephilhower/ESP8266Audio -git clone https://github.com/Gianbacchio/ESP8266_Spiram ``` When in the IDE please select the following options on the ESP8266: @@ -185,7 +172,7 @@ There are many other variants out there, and they should all work reasonably wel ## Software I2S Delta-Sigma DAC (i.e. playing music with a single transistor and speaker) For the best fidelity, and stereo to boot, spend the money on a real I2S DAC. Adafruit makes a great mono one with amplifier, and you can find stereo unamplified ones on eBay or elsewhere quite cheaply. However, thanks to the software delta-sigma DAC with 32x oversampling (up to 128x if the audio rate is low enough) you can still have pretty good sound! -Use the AudioOutputI2S*No*DAC object instead of the AudioOutputI2S in your code, and the following schematic to drive a 2-3W speaker using a single $0.05 NPN 2N3904 transistor: +Use the `AudioOutputI2S*No*DAC` object instead of the `AudioOutputI2S` in your code, and the following schematic to drive a 2-3W speaker using a single $0.05 NPN 2N3904 transistor and ~1K resistor: ``` 2N3904 (NPN) @@ -197,7 +184,7 @@ Use the AudioOutputI2S*No*DAC object instead of the AudioOutputI2S in your code, | | | A| ESP8266-GND ------------------+ | +------+ K| | | | E| -ESP8266-I2SOUT (Rx) -------------+ | \ R| +ESP8266-I2SOUT (Rx) -----/\/\/\--+ | \ R| | +-| USB 5V -----------------------------+ @@ -207,15 +194,15 @@ If you don't have a 5V source available on your ESP model, you can use the 5V fr Connections are as a follows: ``` -ESP8266-RX(I2S tx) -- 2N3904 Base +ESP8266-RX(I2S tx) -- Resistor (~1K ohm, not critical) -- 2N3904 Base ESP8266-GND -- 2N3904 Emitter USB-5V -- Speaker + Terminal 2N3904-Collector -- Speaker - Terminal ``` -Basically the transistor acts as a switch and requires only a drive of 1/beta (~1/1000 for the transistor specified) times the speaker current. As shown you've got a max current of (5-0.7)/8=540mA and a power of 0.54^2 * 8 = ~2.3W into the speaker. +*NOTE*: A prior version of this schematic had a direct connection from the ESP8266 to the base of the transistor. While this does provide the maximum amplitude, it also can draw more current from the 8266 than is safe, and can also cause the transistor to overheat. -When using the software delta-sigma DAC, even though our playback circuit is not using the LRCLK or BCLK pins, the ESP8266 internal hardware *will* be driving them. So these pins cannot be used as outputs in your application. However, you can use the LRCLK and BCLK pins as *inputs*. Simply start playback, then use the standard pinMode(xxx, INPUT/INPUT_PULLUP) Arduino commands and you can, for example, use those two pins to read a button or sensor. +As of the latest ESP8266Audio release, with the software delta-sigma DAC the LRCLK and BCLK pins *can* be used by an application. Simply use normal `pinMode` and `dicitalWrite` or `digitalRead` as desired. ### High pitched buzzing with the 1-T circuit The 1-T amp can _NOT_ drive any sort of amplified speaker. If there is a power or USB input to the speaker, or it has lights or Bluetooth or a battery, it can _NOT_ be used with this circuit. @@ -251,8 +238,9 @@ Ground ---------------------+ For ESP8266 with red LED (~1.9Vf drop) you need minimum 150Ohm resistor (12mA max per pin), and output pin is fixed (GPIO3/RX0).On ESP32 it is confgurable with `AudioOutputSPDIF(gpio_num)`. ## Using external SPI RAM to increase buffer -A class allows you to use a 23lc1024 SPI RAM from Microchip as input buffer. This chip connects to ESP8266 HSPI port and uses an [external SPI RAM library](https://github.com/Gianbacchio/ESP8266_Spiram). -You need to choose another pin than GPIO15 for Cs as this pin is already used by the I2S port. Here is an example with the Cs pin plugged to GPIO00 on NodeMCU board. +A class allows you to use a 23lc1024 SPI RAM from Microchip as input buffer. This chip connects to ESP8266 HSPI port and provides a large buffer to help avoid hiccus in playback of web streams. + +The current version allows for using the standard hardware CS (GPIO15) or any other pin via software at slightly less performance. The following schematic shows one example: ![Example of SPIRAM Schematic](examples/StreamMP3FromHTTP_SPIRAM/Schema_Spiram.png) diff --git a/examples/StreamMP3FromHTTP_SPIRAM/StreamMP3FromHTTP_SPIRAM.ino b/examples/StreamMP3FromHTTP_SPIRAM/StreamMP3FromHTTP_SPIRAM.ino index 7b24414c..792e2185 100644 --- a/examples/StreamMP3FromHTTP_SPIRAM/StreamMP3FromHTTP_SPIRAM.ino +++ b/examples/StreamMP3FromHTTP_SPIRAM/StreamMP3FromHTTP_SPIRAM.ino @@ -16,7 +16,7 @@ const char *SSID = "....."; const char *PASSWORD = "....."; // Randomly picked URL -const char *URL="http://streaming.shoutcast.com/80sPlanet?lang=en-US"; +const char *URL="http://kvbstreams.dyndns.org:8000/wkvi-am"; AudioGeneratorMP3 *mp3; AudioFileSourceICYStream *file; @@ -29,25 +29,24 @@ void MDCallback(void *cbData, const char *type, bool isUnicode, const char *stri const char *ptr = reinterpret_cast(cbData); (void) isUnicode; // Punt this ball for now // Note that the type and string may be in PROGMEM, so copy them to RAM for printf - char s1[32], s2[64]; - strncpy_P(s1, type, sizeof(s1)); - s1[sizeof(s1)-1]=0; - strncpy_P(s2, string, sizeof(s2)); - s2[sizeof(s2)-1]=0; - Serial.printf("METADATA(%s) '%s' = '%s'\n", ptr, s1, s2); + Serial.printf_P(PSTR("METADATA(%s) '%s' = '%s'\n"), ptr, type, string); Serial.flush(); } + // Called when there's a warning or error (like a buffer underflow or decode hiccup) void StatusCallback(void *cbData, int code, const char *string) { const char *ptr = reinterpret_cast(cbData); - // Note that the string may be in PROGMEM, so copy it to RAM for printf - char s1[64]; - strncpy_P(s1, string, sizeof(s1)); - s1[sizeof(s1)-1]=0; - Serial.printf("STATUS(%s) '%d' = '%s'\n", ptr, code, s1); - Serial.flush(); + static uint32_t lastTime = 0; + static int lastCode = -99999; + uint32_t now = millis(); + if ((lastCode != code) || (now - lastTime > 1000)) { + Serial.printf_P(PSTR("STATUS(%s) '%d' = '%s'\n"), ptr, code, string); + Serial.flush(); + lastTime = now; + lastCode = code; + } } void setup() @@ -73,7 +72,7 @@ void setup() file = new AudioFileSourceICYStream(URL); file->RegisterMetadataCB(MDCallback, (void*)"ICY"); // Initialize 23LC1024 SPI RAM buffer with chip select ion GPIO4 and ram size of 128KByte - buff = new AudioFileSourceSPIRAMBuffer(file, 4, 131072); + buff = new AudioFileSourceSPIRAMBuffer(file, 4, 128*1024); buff->RegisterStatusCB(StatusCallback, (void*)"buffer"); out = new AudioOutputI2SNoDAC(); mp3 = new AudioGeneratorMP3(); diff --git a/src/AudioFileSourceSPIRAMBuffer.cpp b/src/AudioFileSourceSPIRAMBuffer.cpp index dde5e683..99586a3b 100644 --- a/src/AudioFileSourceSPIRAMBuffer.cpp +++ b/src/AudioFileSourceSPIRAMBuffer.cpp @@ -5,6 +5,9 @@ Copyright (C) 2017 Sebastien Decourriere Based on AudioFileSourceBuffer class from Earle F. Philhower, III + Copyright (C) 2020 Earle F. Philhower, III + Rewritten for speed and functionality + 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 @@ -26,131 +29,139 @@ AudioFileSourceSPIRAMBuffer::AudioFileSourceSPIRAMBuffer(AudioFileSource *source, uint8_t csPin, uint32_t buffSizeBytes) { - Spiram = new ESP8266Spiram(csPin, 40e6); - Spiram->begin(); - Spiram->setSeqMode(); - ramSize = buffSizeBytes; - writePtr = 0; - readPtr = 0; - src = source; - length = 0; - filled = false; - bytesAvailable = 0; - audioLogger->printf_P(PSTR("SPI RAM buffer size: %u Bytes\n"), ramSize); + ram.begin(40, csPin); + ramSize = buffSizeBytes; + writePtr = 0; + readPtr = 0; + filled = false; + src = source; + audioLogger->printf_P(PSTR("SPI RAM buffer size: %u Bytes\n"), ramSize); } AudioFileSourceSPIRAMBuffer::~AudioFileSourceSPIRAMBuffer() { + ram.end(); } bool AudioFileSourceSPIRAMBuffer::seek(int32_t pos, int dir) { - // Invalidate - readPtr = 0; - writePtr = 0; - length = 0; - return src->seek(pos, dir); + // Invalidate + readPtr = 0; + writePtr = 0; + filled = false; + return src->seek(pos, dir); } bool AudioFileSourceSPIRAMBuffer::close() { - return src->close(); + return src->close(); } bool AudioFileSourceSPIRAMBuffer::isOpen() { - return src->isOpen(); + return src->isOpen(); } uint32_t AudioFileSourceSPIRAMBuffer::getSize() { - return src->getSize(); + return src->getSize(); } uint32_t AudioFileSourceSPIRAMBuffer::getPos() { - return src->getPos(); + return src->getPos() - (writePtr - readPtr); } uint32_t AudioFileSourceSPIRAMBuffer::read(void *data, uint32_t len) { - uint32_t bytes = 0; - // Check if the buffer isn't empty, otherwise we try to fill completely - if (!filled) { - uint8_t buffer[256]; - writePtr = readPtr = 0; - uint16_t toRead = sizeof(buffer); - // Fill up completely before returning any data at all - audioLogger->printf_P(PSTR("Buffering...\n")); - while (bytesAvailable!=ramSize) { - length = src->read(buffer, toRead); - if(length>0) { - Spiram->write(writePtr, buffer, length); - bytesAvailable+=length; - writePtr = bytesAvailable % ramSize; - if ((ramSize-bytesAvailable)printf_P(PSTR("Filling Done !\n")); - } - -// audioLogger->printf("Buffer: %u%\n", bytesAvailable*100/ramSize); - - uint8_t *ptr = reinterpret_cast(data); - uint32_t toReadFromBuffer = (len < bytesAvailable) ? len : bytesAvailable; - if (toReadFromBuffer>0) { - // Pull from buffer until we've got none left or we've satisfied the request - Spiram->read(readPtr, ptr, toReadFromBuffer); - readPtr = (readPtr+toReadFromBuffer) % ramSize; - bytes = toReadFromBuffer; - bytesAvailable-=toReadFromBuffer; - len-=toReadFromBuffer; - ptr += toReadFromBuffer; - } - - // If len>O there is no data left in buffer and we try to read more directly from source. - // Then, we trigger a complete buffer refill - if (len) { - bytes += src->read(ptr, len); - bytesAvailable = 0; - filled = false; - } - return bytes; + uint32_t bytes = 0; + + // Check if the buffer isn't empty, otherwise we try to fill completely + if (!filled) { + cb.st(999, PSTR("Filling buffer...")); + uint8_t buffer[256]; + writePtr = 0; + readPtr = 0; + // Fill up completely before returning any data at all + do { + int toRead = std::min(ramSize - (writePtr - readPtr), sizeof(buffer)); + int length = src->read(buffer, toRead); + if (length > 0) { +#ifdef FAKERAM + for (size_t i=0; i(data); + if (toReadFromBuffer > 0) { +#ifdef FAKERAM + for (size_t i=0; i0 there is no data left in buffer and we try to read more directly from source. + // Then, we trigger a complete buffer refill + if (len) { + bytes += src->read(data, len); + filled = false; + } + return bytes; } void AudioFileSourceSPIRAMBuffer::fill() { - // Make sure the buffer is pre-filled before make partial fill. - if (!filled) return; - - // Now trying to refill SPI RAM Buffer - uint8_t buffer[128]; - // Make sure there is at least buffer size free in RAM - if ((ramSize - bytesAvailable)readNonBlock(buffer, sizeof(buffer)); - if (cnt) { - Spiram->write(writePtr, buffer, cnt); - bytesAvailable+=cnt; - writePtr = (writePtr + cnt) % ramSize; -#ifdef SPIBUF_DEBUG - audioLogger->printf_P(PSTR("SockRead: %u | RamAvail: %u\n"), cnt, bytesAvailable); + // Make sure the buffer is pre-filled before make partial fill. + if (!filled) return; + + for (auto i=0; i<5; i++) { + // Make sure there is at least buffer size free in RAM + uint8_t buffer[128]; + if ((ramSize - (writePtr - readPtr)) < sizeof(buffer)) { + return; + } + + int cnt = src->readNonBlock(buffer, sizeof(buffer)); + if (cnt) { +#ifdef FAKERAM + for (size_t i=0; iloop()) return false; - fill(); - return true; + static uint32_t last = 0; + if (!src->loop()) return false; + fill(); + if ((ESP.getCycleCount() - last) > microsecondsToClockCycles(1000000)) { + last = ESP.getCycleCount(); + char str[65]; + memset(str, '#', 64); + str[64] = 0; + str[((writePtr - readPtr) * 64)/ramSize] = 0; + cb.st(((writePtr - readPtr) * 100)/ramSize, str); + } + return true; } diff --git a/src/AudioFileSourceSPIRAMBuffer.h b/src/AudioFileSourceSPIRAMBuffer.h index 2802444b..f192e899 100644 --- a/src/AudioFileSourceSPIRAMBuffer.h +++ b/src/AudioFileSourceSPIRAMBuffer.h @@ -24,14 +24,18 @@ #include "AudioFileSource.h" #include -#include // https://github.com/Gianbacchio/ESP8266_Spiram - +#include "spiram-fast.h" +//#define FAKERAM // #define SPIBUF_DEBUG class AudioFileSourceSPIRAMBuffer : public AudioFileSource { public: - AudioFileSourceSPIRAMBuffer(AudioFileSource *in, uint8_t csPin, uint32_t bufferBytes); +#ifdef FAKERAM + AudioFileSourceSPIRAMBuffer(AudioFileSource *in, uint8_t csPin = 15, uint32_t bufferBytes = 2048); +#else + AudioFileSourceSPIRAMBuffer(AudioFileSource *in, uint8_t csPin = 15, uint32_t bufferBytes = 128*1024); +#endif virtual ~AudioFileSourceSPIRAMBuffer() override; virtual uint32_t read(void *data, uint32_t len) override; @@ -47,13 +51,15 @@ class AudioFileSourceSPIRAMBuffer : public AudioFileSource private: AudioFileSource *src; - ESP8266Spiram *Spiram; - uint32_t ramSize; - uint32_t writePtr; - uint32_t readPtr; - uint16_t length; - uint32_t bytesAvailable; - bool filled; + ESP8266SPIRAM ram; + size_t ramSize; + size_t writePtr; + size_t readPtr; + bool filled; + +#ifdef FAKERAM + char fakeRAM[2048]; +#endif }; diff --git a/src/spiram-fast.h b/src/spiram-fast.h new file mode 100644 index 00000000..1317bd64 --- /dev/null +++ b/src/spiram-fast.h @@ -0,0 +1,236 @@ +/* + 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 + +*/ + +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); + } + +};