From eb18de31b3ecd414bf9dacc415f1964b2f7ff7f1 Mon Sep 17 00:00:00 2001 From: per1234 Date: Fri, 9 Feb 2018 15:47:36 -0800 Subject: [PATCH 01/26] Use correct separator in keywords.txt The Arduino IDE currently requires the use of a tab separator between the name and identifier. Without this tab the keyword is not highlighted. Reference: https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification#keywords --- keywords.txt | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/keywords.txt b/keywords.txt index f10adaa..90ff8d8 100755 --- a/keywords.txt +++ b/keywords.txt @@ -12,12 +12,12 @@ SPIFlash KEYWORD1 # Methods and Functions (KEYWORD2) ####################################### begin KEYWORD2 -setClock KEYWORD2 -libver KEYWORD2 -error KEYWORD2 +setClock KEYWORD2 +libver KEYWORD2 +error KEYWORD2 getManID KEYWORD2 getJEDECID KEYWORD2 -getUniqueID KEYWORD2 +getUniqueID KEYWORD2 getAddress KEYWORD2 sizeofStr KEYWORD2 getCapacity KEYWORD2 @@ -57,16 +57,16 @@ powerDown KEYWORD2 ####################################### # Constants (LITERAL1) ####################################### -WINBOND_MANID LITERAL1 -MICROCHIP_MANID LITERAL1 -CYPRESS_MANID LITERAL1 -ADESTO_MANID LITERAL1 -MICRON_MANID LITERAL1 -NULLBYTE LITERAL1 -NULLINT LITERAL1 -BYTE LITERAL1 -KiB LITERAL1 -MiB LITERAL1 +WINBOND_MANID LITERAL1 +MICROCHIP_MANID LITERAL1 +CYPRESS_MANID LITERAL1 +ADESTO_MANID LITERAL1 +MICRON_MANID LITERAL1 +NULLBYTE LITERAL1 +NULLINT LITERAL1 +BYTE LITERAL1 +KiB LITERAL1 +MiB LITERAL1 ####################################### # Built-in variables (LITERAL2) From cfde3117e92a5f596cfff8d42e55b9f806e8361f Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Sat, 24 Feb 2018 20:38:07 +1000 Subject: [PATCH 02/26] --> An error with how _addressCheck() works with data that spans the memory boundary - when rolling over from address '_chip.capacity' to address '0x00' - has been fixed. In previous versions this caused issues with writing complex data structures across the memory boundary and led to many _writeErrorCheck() failures. Fixes issue #112 --> A new function - 'flash.eraseSection(address, size)' - has been intrduced in this version. When a user requires a large and variable (between writes) amount of data to be written to the flash memory on the fly and to have the correct amount of space erased to fit the data, this function will automatically calculate and erase the right amount of space to fit the data. Please note that if the the amount of data being written is consistently the same size, the pre-existing 'flash.eraseSector()', 'flash.eraseBlock32K()' and 'flash.eraseBlock64K()' functions will operate a lot faster. --- LICENSE | 0 README.md | 5 + .../Diagnostics_functions.ino | 8 +- examples/Struct_writer/Struct_writer.ino | 22 +++- extras/Changes.log | 14 +++ extras/DMASAMD.cpp | 0 extras/Library speed comparisons.xlsx | Bin extras/SPI pinouts.md | 0 ...nbond Flash Instructions - Comparison.xlsx | Bin keywords.txt | 1 + library.properties | 2 +- src/DMASAMD.h | 0 src/FLASHIO.cpp | 32 ++++-- src/SAM_DMASPI.cpp | 0 src/SPIFlash.cpp | 108 +++++++++++++++++- src/SPIFlash.h | 28 +++-- src/defines.h | 4 +- src/troubleshoot.cpp | 0 18 files changed, 196 insertions(+), 28 deletions(-) mode change 100755 => 100644 LICENSE mode change 100755 => 100644 README.md mode change 100755 => 100644 examples/Struct_writer/Struct_writer.ino mode change 100755 => 100644 extras/Changes.log mode change 100755 => 100644 extras/DMASAMD.cpp mode change 100755 => 100644 extras/Library speed comparisons.xlsx mode change 100755 => 100644 extras/SPI pinouts.md mode change 100755 => 100644 extras/Winbond Flash Instructions - Comparison.xlsx mode change 100755 => 100644 keywords.txt mode change 100755 => 100644 library.properties mode change 100755 => 100644 src/DMASAMD.h mode change 100755 => 100644 src/FLASHIO.cpp mode change 100755 => 100644 src/SAM_DMASPI.cpp mode change 100755 => 100644 src/SPIFlash.cpp mode change 100755 => 100644 src/SPIFlash.h mode change 100755 => 100644 src/defines.h mode change 100755 => 100644 src/troubleshoot.cpp diff --git a/LICENSE b/LICENSE old mode 100755 new mode 100644 diff --git a/README.md b/README.md old mode 100755 new mode 100644 index f058204..9c97e61 --- a/README.md +++ b/README.md @@ -258,6 +258,11 @@ Erases one 64KB block - 256 pages - containing the address to be erased. The blo ###### eraseChip() Erases entire chip. Use with care. + +###### eraseSection(address, sizeOfData) +Erases the specific number of blocks/sectors to fit data (size defined by the sizeOfData arguement) into. When a user requires a large and variable (between writes) amount of data to be written to the flash memory on the fly and to have the correct amount of space erased to fit the data, this function will automatically calculate and erase the right amount of space to fit the data. + +Please note that if the the amount of data being written is consistently the same size, the pre-existing 'flash.eraseSector()', 'flash.eraseBlock32K()' and 'flash.eraseBlock64K()' functions will operate a lot faster.
##### Suspend/Resume commands diff --git a/examples/FlashDiagnostics/Diagnostics_functions.ino b/examples/FlashDiagnostics/Diagnostics_functions.ino index 3a4be5d..495afc1 100644 --- a/examples/FlashDiagnostics/Diagnostics_functions.ino +++ b/examples/FlashDiagnostics/Diagnostics_functions.ino @@ -377,6 +377,10 @@ void structTest() { int32_t s3; bool s4; uint8_t s5; + struct structOfStruct { + uint8_t b1; + float f2; + } structofstruct; }; Test _d; Test _data; @@ -386,6 +390,8 @@ void structTest() { _d.s3 = 880932; _d.s4 = true; _d.s5 = 5; + _d.structofstruct.b1 = 234; + _d.structofstruct.f2 = 6.28; uint32_t wTime = 0; uint32_t addr, rTime; @@ -402,7 +408,7 @@ void structTest() { Serial.print ("\t\t\tStruct: \t"); - if ((_d.s1 == _data.s1) && (_d.s2 == _data.s2) && (_d.s3 == _data.s3) && (_d.s4 == _data.s4) && (_d.s5 == _data.s5)) { + if ((_d.s1 == _data.s1) && (_d.s2 == _data.s2) && (_d.s3 == _data.s3) && (_d.s4 == _data.s4) && (_d.s5 == _data.s5) && (_d.structofstruct.b1 == _data.structofstruct.b1) && (_d.structofstruct.b1 == _data.structofstruct.b1)) { pass(TRUE); } else { diff --git a/examples/Struct_writer/Struct_writer.ino b/examples/Struct_writer/Struct_writer.ino old mode 100755 new mode 100644 index 23fd488..39451e8 --- a/examples/Struct_writer/Struct_writer.ino +++ b/examples/Struct_writer/Struct_writer.ino @@ -49,7 +49,7 @@ SPIFlash flash; -struct Configuration { +/* struct Configuration { float lux; float vOut; // Voltage ouput from potential divider to Analog input float RLDR; // Resistance calculation of potential divider with LDR @@ -57,7 +57,25 @@ struct Configuration { uint8_t adc; uint8_t arr[8]; }; -Configuration configuration; +Configuration configuration; */ + +struct CONFIGURATION { + struct MISC { + byte tempHigh = 30; + byte tempLow = 20; + bool parkingMode = false; + bool allowDataToBeSent = false; + } misc; + struct NETWORK { + char ssid[50] = "§"; + char pwd[50] = "§"; + char userid[50] = "§"; + } network; + struct CHARGING_INFO { + byte interval = 5; + byte highChargingDefault = 80; + } charging; +} configuration; void setup() { Serial.begin(BAUD_RATE); diff --git a/extras/Changes.log b/extras/Changes.log old mode 100755 new mode 100644 index 9a2cd47..aa8710c --- a/extras/Changes.log +++ b/extras/Changes.log @@ -2,6 +2,20 @@ // SPIFlash Library // // Changes log // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// +// Version 3.1.0 // +// Author: Prajwal Bhattaram // +// 24.02.2018 // +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// +Bugs squashed +--> An error with how _addressCheck() works with data that spans the memory boundary - when rolling over from address '_chip.capacity' to address '0x00' - has been fixed. In previous versions this caused issues with writing complex data structures across the memory boundary and led to many _writeErrorCheck() failures. Fixes issue #112 + +Enhancements: +--> A new function - 'flash.eraseSection(address, size)' - has been intrduced in this version. When a user requires a large and variable (between writes) amount of data to be written to the flash memory on the fly and to have the correct amount of space erased to fit the data, this function will automatically calculate and erase the right amount of space to fit the data. Please note that if the the amount of data being written is consistently the same size, the pre-existing 'flash.eraseSector()', 'flash.eraseBlock32K()' and 'flash.eraseBlock64K()' functions will operate a lot faster. + + +New flash memory chips supported: +--> S25FL127S +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// // Version 3.0.1 // // Author: Prajwal Bhattaram // // 10.12.2017 // diff --git a/extras/DMASAMD.cpp b/extras/DMASAMD.cpp old mode 100755 new mode 100644 diff --git a/extras/Library speed comparisons.xlsx b/extras/Library speed comparisons.xlsx old mode 100755 new mode 100644 diff --git a/extras/SPI pinouts.md b/extras/SPI pinouts.md old mode 100755 new mode 100644 diff --git a/extras/Winbond Flash Instructions - Comparison.xlsx b/extras/Winbond Flash Instructions - Comparison.xlsx old mode 100755 new mode 100644 diff --git a/keywords.txt b/keywords.txt old mode 100755 new mode 100644 index f10adaa..157d194 --- a/keywords.txt +++ b/keywords.txt @@ -45,6 +45,7 @@ writeULong KEYWORD2 writeFloat KEYWORD2 writeStr KEYWORD2 writeAnything KEYWORD2 +eraseSection KEYWORD2 eraseSector KEYWORD2 eraseBlock32K KEYWORD2 eraseBlock64K KEYWORD2 diff --git a/library.properties b/library.properties old mode 100755 new mode 100644 index 9688e9b..88e9e82 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SPIFlash -version=3.0.1 +version=3.1.0 author=Prajwal Bhattaram maintainer=Prajwal Bhattaram sentence=SPI Flash library for Arduino. diff --git a/src/DMASAMD.h b/src/DMASAMD.h old mode 100755 new mode 100644 diff --git a/src/FLASHIO.cpp b/src/FLASHIO.cpp old mode 100755 new mode 100644 index 12644ed..62bc0f8 --- a/src/FLASHIO.cpp +++ b/src/FLASHIO.cpp @@ -1,6 +1,6 @@ -/* Arduino SPIFlash Library v.3.0.0 +/* Arduino SPIFlash Library v.3.1.0 * Copyright (C) 2017 by Prajwal Bhattaram - * Created by Prajwal Bhattaram - 04/11/2017 + * Created by Prajwal Bhattaram - 24/02/2018 * * This file is part of the Arduino SPIFlash Library. This library is for * Winbond NOR flash memory modules. In its current form it enables reading @@ -32,6 +32,7 @@ //Checks to see if page overflow is permitted and assists with determining next address to read/write. //Sets the global address variable bool SPIFlash::_addressCheck(uint32_t _addr, uint32_t size) { + uint32_t _submittedAddress = _addr; if (errorcode == UNKNOWNCAP || errorcode == NORESPONSE) { return false; } @@ -40,19 +41,30 @@ bool SPIFlash::_addressCheck(uint32_t _addr, uint32_t size) { return false; } - //for (uint32_t i = 0; i < size; i++) { - if (_addr + size >= _chip.capacity) { + //Serial.print("_chip.capacity: "); + //Serial.println(_chip.capacity, HEX); + + if (_submittedAddress + size >= _chip.capacity) { + //Serial.print("_submittedAddress + size: "); + //Serial.println(_submittedAddress + size, HEX); #ifdef DISABLEOVERFLOW _troubleshoot(OUTOFBOUNDS); return false; // At end of memory - (!pageOverflow) #else - _currentAddress = 0x00; + _addressOverflow = ((_submittedAddress + size) - _chip.capacity); + _currentAddress = _addr; + //Serial.print("_addressOverflow: "); + //Serial.println(_addressOverflow, HEX); return true; // At end of memory - (pageOverflow) #endif } - //} - _currentAddress = _addr; - return true; // Not at end of memory if (address < _chip.capacity) + else { + _addressOverflow = false; + _currentAddress = _addr; + return true; // Not at end of memory if (address < _chip.capacity) + } + //Serial.print("_currentAddress: "); + //Serial.println(_currentAddress, HEX); } // Checks to see if the block of memory has been previously written to @@ -62,6 +74,7 @@ bool SPIFlash::_notPrevWritten(uint32_t _addr, uint32_t size) { for (uint32_t i = 0; i < size; i++) { if (_nextByte(READ) != 0xFF) { CHIP_DESELECT; + _troubleshoot(PREVWRITTEN); return false; } } @@ -95,8 +108,7 @@ bool SPIFlash::_prep(uint8_t opcode, uint32_t _addr, uint32_t size) { break; case ERASEFUNC: - _currentAddress = _addr; - if(!_notBusy()||!_writeEnable()) { + if(!_addressCheck(_addr, size) || !_notBusy() || !_writeEnable()) { return false; } return true; diff --git a/src/SAM_DMASPI.cpp b/src/SAM_DMASPI.cpp old mode 100755 new mode 100644 diff --git a/src/SPIFlash.cpp b/src/SPIFlash.cpp old mode 100755 new mode 100644 index c388b6e..832a0a0 --- a/src/SPIFlash.cpp +++ b/src/SPIFlash.cpp @@ -1,8 +1,8 @@ -/* Arduino SPIFlash Library v.3.0.0 +/* Arduino SPIFlash Library v.3.1.0 * Copyright (C) 2017 by Prajwal Bhattaram * Created by Prajwal Bhattaram - 19/05/2015 * Modified by @boseji - 02/03/2017 - * Modified by Prajwal Bhattaram - 17/11/2017 + * Modified by Prajwal Bhattaram - 24/02/2018 * * This file is part of the Arduino SPIFlash Library. This library is for * Winbond NOR flash memory modules. In its current form it enables reading @@ -724,13 +724,111 @@ bool SPIFlash::writeStr(uint32_t _addr, String &data, bool errorCheck) { #endif } +// Erases a number of sectors or blocks as needed by the data being input. +// Takes an address and the size of the data being input as the arguments and erases the block/s of memory containing the address. +bool SPIFlash::eraseSection(uint32_t _addr, uint32_t _sz) { + #ifdef RUNDIAGNOSTIC + _spifuncruntime = micros(); + #endif + + if (!_prep(ERASEFUNC, _addr, _sz)) { + return false; + } + + // If size of data is > 4KB more than one sector needs to be erased. So the number of erase sessions is determined by the quotient of _sz/KB(4). If the _sz is not perfectly divisible by KB(4), then an additional sector has to be erased. + uint32_t noOfEraseRunsB4Boundary, noOf4KBEraseRuns, EraseFunc, KB64Blocks, KB32Blocks, KB4Blocks, totalBlocks; + + if (_sz/KB(4)) { + noOf4KBEraseRuns = _sz/KB(4); + } + else { + noOf4KBEraseRuns = 1; + } + KB64Blocks = noOf4KBEraseRuns/16; + KB32Blocks = (noOf4KBEraseRuns % 16) / 8; + KB4Blocks = (noOf4KBEraseRuns % 8); + totalBlocks = KB64Blocks + KB32Blocks + KB4Blocks; + //Serial.print("noOf4KBEraseRuns: "); + //Serial.println(noOf4KBEraseRuns); + //Serial.print("totalBlocks: "); + //Serial.println(totalBlocks); + + uint16_t _eraseFuncOrder[totalBlocks]; + + if (KB64Blocks) { + for (uint32_t i = 0; i < KB64Blocks; i++) { + _eraseFuncOrder[i] = BLOCK64ERASE; + } + } + if (KB32Blocks) { + for (uint32_t i = KB64Blocks; i < (KB64Blocks + KB32Blocks); i++) { + _eraseFuncOrder[i] = BLOCK32ERASE; + } + } + if (KB4Blocks) { + for (uint32_t i = (KB64Blocks + KB32Blocks); i < totalBlocks; i++) { + _eraseFuncOrder[i] = SECTORERASE; + } + } + +// Now that the number of blocks to be erased have been calculated and the information saved, the erase function is carried out. + if (_addressOverflow) { + noOfEraseRunsB4Boundary = (_sz - _addressOverflow)/16; + noOfEraseRunsB4Boundary += ((_sz - _addressOverflow) % 16) / 8; + noOfEraseRunsB4Boundary += ((_sz - _addressOverflow) % 8); + //Serial.print("noOfEraseRunsB4Boundary: "); + //Serial.println(noOfEraseRunsB4Boundary); + } + if (!_addressOverflow) { + for (uint32_t j = 0; j < totalBlocks; j++) { + _beginSPI(_eraseFuncOrder[j]); //The address is transferred as a part of this function + _endSPI(); + + + //Serial.print("_eraseFuncOrder: 0x"); + //Serial.println(_eraseFuncOrder[j], HEX); + + uint16_t _timeFactor; + switch (_eraseFuncOrder[j]) { + case BLOCK64ERASE: + _timeFactor = 1200; + break; + + case BLOCK32ERASE: + _timeFactor = 1000; + break; + + case SECTORERASE: + _timeFactor = 500; + break; + + } + if(!_notBusy(_timeFactor * 1000L)) { + return false; //Datasheet says erasing a sector takes 400ms max + } + if (j == noOfEraseRunsB4Boundary) { + if (!_prep(ERASEFUNC, (_addr + (_sz - _addressOverflow)), _sz)) { + return false; + } + //Serial.print("Overflow triggered"); + } + } + } + //_writeDisable(); + #ifdef RUNDIAGNOSTIC + _spifuncruntime = micros() - _spifuncruntime; + #endif + + return true; +} + // Erases one 4k sector. // Takes an address as the argument and erases the block containing the address. bool SPIFlash::eraseSector(uint32_t _addr) { #ifdef RUNDIAGNOSTIC _spifuncruntime = micros(); #endif - if (!_prep(ERASEFUNC, _addr)) { + if (!_prep(ERASEFUNC, _addr, KB(4))) { return false; } _beginSPI(SECTORERASE); //The address is transferred as a part of this function @@ -753,7 +851,7 @@ bool SPIFlash::eraseBlock32K(uint32_t _addr) { #ifdef RUNDIAGNOSTIC _spifuncruntime = micros(); #endif - if (!_prep(ERASEFUNC, _addr)) { + if (!_prep(ERASEFUNC, _addr, KB(32))) { return false; } _beginSPI(BLOCK32ERASE); @@ -776,7 +874,7 @@ bool SPIFlash::eraseBlock64K(uint32_t _addr) { #ifdef RUNDIAGNOSTIC _spifuncruntime = micros(); #endif - if (!_prep(ERASEFUNC, _addr)) { + if (!_prep(ERASEFUNC, _addr, KB(64))) { return false; } diff --git a/src/SPIFlash.h b/src/SPIFlash.h old mode 100755 new mode 100644 index f824e2a..b88f9db --- a/src/SPIFlash.h +++ b/src/SPIFlash.h @@ -1,8 +1,8 @@ -/* Arduino SPIFlash Library v.3.0.0 +/* Arduino SPIFlash Library v.3.1.0 * Copyright (C) 2017 by Prajwal Bhattaram * Created by Prajwal Bhattaram - 19/05/2015 * Modified by @boseji - 02/03/2017 - * Modified by Prajwal Bhattaram - 04/11/2017 + * Modified by Prajwal Bhattaram - 24/02/2018 * * This file is part of the Arduino SPIFlash Library. This library is for * Winbond NOR flash memory modules. In its current form it enables reading @@ -138,7 +138,7 @@ #endif #define LIBVER 3 -#define LIBSUBVER 0 +#define LIBSUBVER 1 #define BUGFIXVER 0 class SPIFlash { @@ -200,6 +200,7 @@ class SPIFlash { template bool writeAnything(uint32_t _addr, const T& data, bool errorCheck = true); template bool readAnything(uint32_t _addr, T& data, bool fastRead = false); //-------------------------------- Erase functions ------------------------------------// + bool eraseSection(uint32_t _addr, uint32_t _sz); bool eraseSector(uint32_t _addr); bool eraseBlock32K(uint32_t _addr); bool eraseBlock64K(uint32_t _addr); @@ -301,6 +302,7 @@ class SPIFlash { }; chipID _chip; uint32_t currentAddress, _currentAddress = 0; + uint32_t _addressOverflow = false; uint8_t _uniqueID[8]; const uint8_t _capID[14] = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x43, 0x4B, 0x00, 0x01}; @@ -370,9 +372,13 @@ template bool SPIFlash::_writeErrorCheck(uint32_t _addr, const T& valu // Use the eraseSector()/eraseBlock32K/eraseBlock64K commands to first clear memory (write 0xFFs) template bool SPIFlash::_write(uint32_t _addr, const T& value, uint32_t _sz, bool errorCheck) { - if (!_prep(PAGEPROG, _addr, _sz)) { + uint32_t _addrIn = _addr; + if (!_prep(PAGEPROG, _addrIn, _sz)) { return false; } + _addrIn = _currentAddress; + //Serial.print("_addrIn: "); + //Serial.println(_addrIn, HEX); const uint8_t* p = ((const uint8_t*)(const void*)&value); //Serial.print(F("Address being written to: ")); //Serial.println(_addr); @@ -389,7 +395,7 @@ template bool SPIFlash::_write(uint32_t _addr, const T& value, uint32_ } else { //If data is longer than one byte (8 bits) uint32_t length = _sz; - uint16_t maxBytes = SPI_PAGESIZE-(_addr % SPI_PAGESIZE); // Force the first set of bytes to stay within the first page + uint16_t maxBytes = SPI_PAGESIZE-(_addrIn % SPI_PAGESIZE); // Force the first set of bytes to stay within the first page if (maxBytes > length) { for (uint16_t i = 0; i < length; ++i) { @@ -408,7 +414,15 @@ template bool SPIFlash::_write(uint32_t _addr, const T& value, uint32_ _nextByte(WRITE, *p++); } CHIP_DESELECT - _currentAddress += writeBufSz; + if (!_addressOverflow) { + _currentAddress += writeBufSz; + } + else { + if (data_offset >= _addressOverflow) { + _currentAddress = 0x00; + _addressOverflow = false; + } + } data_offset += writeBufSz; length -= writeBufSz; maxBytes = 256; // Now we can do up to 256 bytes per loop @@ -425,7 +439,7 @@ template bool SPIFlash::_write(uint32_t _addr, const T& value, uint32_ else { //Serial.print(F("Address sent to error check: ")); //Serial.println(_addr); - return _writeErrorCheck(_addr, value, _sz); + return _writeErrorCheck(_addrIn, value, _sz); } } diff --git a/src/defines.h b/src/defines.h old mode 100755 new mode 100644 index ce05bbf..6244a12 --- a/src/defines.h +++ b/src/defines.h @@ -1,7 +1,7 @@ -/* Arduino SPIFlash Library v.3.0.0 +/* Arduino SPIFlash Library v.3.1.0 * Copyright (C) 2017 by Prajwal Bhattaram * Created by Prajwal Bhattaram - 19/05/2015 - * Modified by Prajwal Bhattaram - 04/11/2017 + * Modified by Prajwal Bhattaram - 24/02/2018 * * This file is part of the Arduino SPIFlash Library. This library is for * Winbond NOR flash memory modules. In its current form it enables reading diff --git a/src/troubleshoot.cpp b/src/troubleshoot.cpp old mode 100755 new mode 100644 From ffaacbb49e9ee807cf34ec49f3445bf7044058df Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Sat, 24 Feb 2018 20:40:36 +1000 Subject: [PATCH 03/26] Updated Struct_writer.ino to show how the library can be used to write complex structs that contain nested structs --- examples/Struct_writer/Struct_writer.ino | 251 ++++++++++++----------- 1 file changed, 135 insertions(+), 116 deletions(-) diff --git a/examples/Struct_writer/Struct_writer.ino b/examples/Struct_writer/Struct_writer.ino index 39451e8..0a172a8 100644 --- a/examples/Struct_writer/Struct_writer.ino +++ b/examples/Struct_writer/Struct_writer.ino @@ -5,36 +5,23 @@ | v 2.6.0 | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| | Marzogh | - | 16.04.2017 | + | 24.02.2018 | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| | | | This program writes a struct to a random location on your flash memory chip and reads it back. | - | Uncomment #define SENSOR below to get real world readings. Real world readings require a Light dependant resistor hooked up to A0. | - | For information on how to hook up an LDR to an Arduino, please refer to Adafruit's excellent tutorial at | - | https://learn.adafruit.com/photocells/using-a-photocell | | | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| */ #include +//#define PRINTDETAIL + #if defined(ARDUINO_SAMD_ZERO) && defined(SERIAL_PORT_USBVIRTUAL) // Required for Serial on Zero based boards #define Serial SERIAL_PORT_USBVIRTUAL #endif -#if defined (SIMBLEE) -#define BAUD_RATE 250000 -#else -#define BAUD_RATE 115200 -#endif - -/* - Uncomment the #define below if you would like real world readings. - For real world readings, hook up a light dependant resistor to A1. - -*/ -//#define SENSOR #if defined (SIMBLEE) #define BAUD_RATE 250000 #define LDR 1 @@ -48,34 +35,56 @@ //SPIFlash flash(SS1, &SPI1); //Use this constructor if using an SPI bus other than the default SPI. Only works with chips with more than one hardware SPI bus SPIFlash flash; +struct ConfigurationIn { + float lux = 3.24; + float vOut = 4.45; // Voltage ouput from potential divider to Analog input + float RLDR = 1.234; // Resistance calculation of potential divider with LDR + bool light = true; + uint8_t adc = 45; + uint8_t arr[8] = {0, 1, 2, 3, 4, 5, 6, 7}; + struct MISC { + byte tempHigh = 30; + byte tempLow = 20; + bool parkingMode = false; + bool allowDataToBeSent = false; + } misc; + struct NETWORK { + char ssid[5] = "ssid"; + char pwd[4] = "pwd"; + char userid[7] = "userid"; + } network; + struct CHARGING_INFO { + byte interval = 5; + byte highChargingDefault = 80; + } charging; +}; +ConfigurationIn configurationIn; -/* struct Configuration { +struct ConfigurationOut { float lux; float vOut; // Voltage ouput from potential divider to Analog input float RLDR; // Resistance calculation of potential divider with LDR bool light; uint8_t adc; uint8_t arr[8]; + struct MISC { + byte tempHigh; + byte tempLow; + bool parkingMode; + bool allowDataToBeSent; + } misc; + struct NETWORK { + char ssid[5]; + char pwd[4]; + char userid[7]; + } network; + struct CHARGING_INFO { + byte interval; + byte highChargingDefault; + } charging; }; -Configuration configuration; */ +ConfigurationOut configurationOut; -struct CONFIGURATION { - struct MISC { - byte tempHigh = 30; - byte tempLow = 20; - bool parkingMode = false; - bool allowDataToBeSent = false; - } misc; - struct NETWORK { - char ssid[50] = "§"; - char pwd[50] = "§"; - char userid[50] = "§"; - } network; - struct CHARGING_INFO { - byte interval = 5; - byte highChargingDefault = 80; - } charging; -} configuration; void setup() { Serial.begin(BAUD_RATE); @@ -92,91 +101,101 @@ void setup() { Serial.println(); flash.begin(); - - uint32_t _addr = random(0, 1677215); - -#ifndef SENSOR - configuration.lux = 98.43; - configuration.vOut = 4.84; - configuration.RLDR = 889.32; - configuration.light = true; - configuration.adc = 5; - for (uint8_t i = 0; i < 8; i++) { - configuration.arr[i] = i; - } + for (uint8_t x = 1; x <= 20; x++) { + //uint32_t _addr = random(0, 1677215); + uint32_t _addr = random(0, flash.getCapacity()); + Serial.print("Size of array: "); + Serial.println(sizeof(configurationIn)); + + if (flash.eraseSection(_addr, sizeof(configurationIn))) { + Serial.println("Section has been erased"); + } + /*if (flash.eraseSector(_addr)) { + Serial.println("Sector has been erased"); + }*/ + if (flash.writeAnything(_addr, configurationIn)) { +#ifdef PRINTDETAIL + Serial.println(configurationIn.lux); + Serial.println(configurationIn.vOut); + Serial.println(configurationIn.RLDR); + Serial.println(configurationIn.light); + Serial.println(configurationIn.adc); + for (uint8_t i = 0; i < 8; i++) { + Serial.print(configurationIn.arr[i]); + Serial.print(","); + } + Serial.println(); + Serial.println(configurationIn.misc.tempHigh); + Serial.println(configurationIn.misc.tempLow); + Serial.println(configurationIn.misc.parkingMode); + Serial.println(configurationIn.misc.allowDataToBeSent); + for (uint8_t i = 0; i < 4; i++) { + Serial.print(configurationIn.network.ssid[i]); + Serial.print(","); + } + Serial.println(); + for (uint8_t i = 0; i < 3; i++) { + Serial.print(configurationIn.network.pwd[i]); + Serial.print(","); + } + Serial.println(); + for (uint8_t i = 0; i < 6; i++) { + Serial.print(configurationIn.network.userid[i]); + Serial.print(","); + } + Serial.println(); + Serial.println(configurationIn.charging.interval); + Serial.println(configurationIn.charging.highChargingDefault); #endif - -#ifdef SENSOR - readLDR(); + Serial.print ("Data write "); + Serial.print(x); + Serial.println(" successful"); + } + else { + Serial.print ("Data write "); + Serial.print(x); + Serial.println(" failed"); + } + Serial.println(); + + if (flash.readAnything(_addr, configurationOut)) { +#ifdef PRINTDETAIL + Serial.println(configurationOut.lux); + Serial.println(configurationOut.vOut); + Serial.println(configurationOut.RLDR); + Serial.println(configurationOut.light); + Serial.println(configurationOut.adc); + for (uint8_t i = 0; i < 8; i++) { + Serial.print(configurationIn.arr[i]); + Serial.print(","); + } + Serial.println(); + Serial.println(configurationOut.misc.tempHigh); + Serial.println(configurationOut.misc.tempLow); + Serial.println(configurationOut.misc.parkingMode); + Serial.println(configurationOut.misc.allowDataToBeSent); + for (uint8_t i = 0; i < 4; i++) { + Serial.print(configurationOut.network.ssid[i]); + Serial.print(","); + } + Serial.println(); + for (uint8_t i = 0; i < 3; i++) { + Serial.print(configurationOut.network.pwd[i]); + Serial.print(","); + } + Serial.println(); + for (uint8_t i = 0; i < 6; i++) { + Serial.print(configurationOut.network.userid[i]); + Serial.print(","); + } + Serial.println(); + Serial.println(configurationOut.charging.interval); + Serial.println(configurationOut.charging.highChargingDefault); #endif - if (flash.eraseChip()) { - Serial.println("Chip has been erased"); - } - if (flash.writeAnything(_addr, configuration)) - Serial.println ("Data write successful"); - else - Serial.println ("Data write failed"); - - Serial.println(configuration.lux); - Serial.println(configuration.vOut); - Serial.println(configuration.RLDR); - Serial.println(configuration.light); - Serial.println(configuration.adc); - for (uint8_t i = 0; i < 8; i++) { - Serial.print(configuration.arr[i]); - Serial.print(", "); - } - Serial.println(); - Serial.println("Saved!"); - configuration.lux = 0; - configuration.vOut = 0; - configuration.RLDR = 0; - configuration.light = 0; - configuration.adc = 0; - for (uint8_t i = 0; i < 8; i++) { - configuration.arr[i] = 0; - } - Serial.println(); - Serial.println("Local values set to 0"); - Serial.println(configuration.lux); - Serial.println(configuration.vOut); - Serial.println(configuration.RLDR); - Serial.println(configuration.light); - Serial.println(configuration.adc); - for (uint8_t i = 0; i < 8; i++) { - Serial.print(configuration.arr[i]); - Serial.print(", "); - } - Serial.println(); - Serial.println(); - flash.readAnything(_addr, configuration); - flash.eraseSector(_addr); - - Serial.println("After reading"); - Serial.println(configuration.lux); - Serial.println(configuration.vOut); - Serial.println(configuration.RLDR); - Serial.println(configuration.light); - Serial.println(configuration.adc); - for (uint8_t i = 0; i < 8; i++) { - Serial.print(configuration.arr[i]); - Serial.print(", "); + } } - Serial.println(); - -} - -void loop() { delay(1000); } -#ifdef SENSOR -void readLDR() -{ - configuration.adc = analogRead(LDR); - configuration.vOut = (configuration.adc * 0.0048828125); // vOut = Output voltage from potential Divider. [vOut = ADC * (Vin / 1024)] - configuration.RLDR = (10.0 * (5 - configuration.vOut)) / configuration.vOut; // Equation to calculate Resistance of LDR, [R-LDR =(R1 (Vin - vOut))/ vOut]. R1 is in KOhms - // R1 = 10 KOhms , Vin = 5.0 Vdc. - configuration.lux = (500 / configuration.RLDR); +void loop() { } -#endif From e8c4ae1e5d971f542aeb0600dc4a1d5b8775843f Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Sat, 24 Feb 2018 21:15:26 +1000 Subject: [PATCH 04/26] Fixed spacing on keywords added or modified after #111 to bring the keywords.txt file i line with the Arduino Library standards --- keywords.txt | 106 +++++++++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/keywords.txt b/keywords.txt index 47d948f..d9fab47 100644 --- a/keywords.txt +++ b/keywords.txt @@ -6,68 +6,68 @@ # Class (KEYWORD1) ####################################### -SPIFlash KEYWORD1 +SPIFlash KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) ####################################### -begin KEYWORD2 -setClock KEYWORD2 -libver KEYWORD2 -error KEYWORD2 -getManID KEYWORD2 -getJEDECID KEYWORD2 -getUniqueID KEYWORD2 -getAddress KEYWORD2 -sizeofStr KEYWORD2 -getCapacity KEYWORD2 -getMaxPage KEYWORD2 +begin KEYWORD2 +setClock KEYWORD2 +libver KEYWORD2 +error KEYWORD2 +getManID KEYWORD2 +getJEDECID KEYWORD2 +getUniqueID KEYWORD2 +getAddress KEYWORD2 +sizeofStr KEYWORD2 +getCapacity KEYWORD2 +getMaxPage KEYWORD2 functionRunTime KEYWORD2 -readByte KEYWORD2 -readByteArray KEYWORD2 -readChar KEYWORD2 -readCharArray KEYWORD2 -readWord KEYWORD2 -readShort KEYWORD2 -readLong KEYWORD2 -readULong KEYWORD2 -readFloat KEYWORD2 -readStr KEYWORD2 -readAnything KEYWORD2 -writeByte KEYWORD2 -writeByteArray KEYWORD2 -writeChar KEYWORD2 -writeCharArray KEYWORD2 -writeWord KEYWORD2 -writeShort KEYWORD2 -writeLong KEYWORD2 -writeULong KEYWORD2 -writeFloat KEYWORD2 -writeStr KEYWORD2 -writeAnything KEYWORD2 -eraseSection KEYWORD2 -eraseSector KEYWORD2 -eraseBlock32K KEYWORD2 -eraseBlock64K KEYWORD2 -eraseChip KEYWORD2 -suspendProg KEYWORD2 -resumeProg KEYWORD2 -powerUp KEYWORD2 -powerDown KEYWORD2 +readByte KEYWORD2 +readByteArray KEYWORD2 +readChar KEYWORD2 +readCharArray KEYWORD2 +readWord KEYWORD2 +readShort KEYWORD2 +readLong KEYWORD2 +readULong KEYWORD2 +readFloat KEYWORD2 +readStr KEYWORD2 +readAnything KEYWORD2 +writeByte KEYWORD2 +writeByteArray KEYWORD2 +writeChar KEYWORD2 +writeCharArray KEYWORD2 +writeWord KEYWORD2 +writeShort KEYWORD2 +writeLong KEYWORD2 +writeULong KEYWORD2 +writeFloat KEYWORD2 +writeStr KEYWORD2 +writeAnything KEYWORD2 +eraseSection KEYWORD2 +eraseSector KEYWORD2 +eraseBlock32K KEYWORD2 +eraseBlock64K KEYWORD2 +eraseChip KEYWORD2 +suspendProg KEYWORD2 +resumeProg KEYWORD2 +powerUp KEYWORD2 +powerDown KEYWORD2 ####################################### # Constants (LITERAL1) ####################################### -WINBOND_MANID LITERAL1 -MICROCHIP_MANID LITERAL1 -CYPRESS_MANID LITERAL1 -ADESTO_MANID LITERAL1 -MICRON_MANID LITERAL1 -NULLBYTE LITERAL1 -NULLINT LITERAL1 -BYTE LITERAL1 -KiB LITERAL1 -MiB LITERAL1 +WINBOND_MANID LITERAL1 +MICROCHIP_MANID LITERAL1 +CYPRESS_MANID LITERAL1 +ADESTO_MANID LITERAL1 +MICRON_MANID LITERAL1 +NULLBYTE LITERAL1 +NULLINT LITERAL1 +BYTE LITERAL1 +KiB LITERAL1 +MiB LITERAL1 ####################################### # Built-in variables (LITERAL2) From d82b4b409e038062f32b0667bd5348b1d4a6fc46 Mon Sep 17 00:00:00 2001 From: per1234 Date: Sat, 24 Feb 2018 04:48:06 -0800 Subject: [PATCH 05/26] Use correct separator in keywords.txt The Arduino IDE currently requires the use of a tab separator between the name and identifier. Without this tab the keyword is not highlighted. Reference: https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification#keywords All tabs appear to have been inadvertently changed to spaces by https://github.com/Marzogh/SPIFlash/commit/e8c4ae1e5d971f542aeb0600dc4a1d5b8775843f --- keywords.txt | 108 +++++++++++++++++++++++++-------------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/keywords.txt b/keywords.txt index d9fab47..bff372a 100644 --- a/keywords.txt +++ b/keywords.txt @@ -6,68 +6,68 @@ # Class (KEYWORD1) ####################################### -SPIFlash KEYWORD1 +SPIFlash KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) ####################################### -begin KEYWORD2 -setClock KEYWORD2 -libver KEYWORD2 -error KEYWORD2 -getManID KEYWORD2 -getJEDECID KEYWORD2 -getUniqueID KEYWORD2 -getAddress KEYWORD2 -sizeofStr KEYWORD2 -getCapacity KEYWORD2 -getMaxPage KEYWORD2 -functionRunTime KEYWORD2 -readByte KEYWORD2 -readByteArray KEYWORD2 -readChar KEYWORD2 -readCharArray KEYWORD2 -readWord KEYWORD2 -readShort KEYWORD2 -readLong KEYWORD2 -readULong KEYWORD2 -readFloat KEYWORD2 -readStr KEYWORD2 -readAnything KEYWORD2 -writeByte KEYWORD2 -writeByteArray KEYWORD2 -writeChar KEYWORD2 -writeCharArray KEYWORD2 -writeWord KEYWORD2 -writeShort KEYWORD2 -writeLong KEYWORD2 -writeULong KEYWORD2 -writeFloat KEYWORD2 -writeStr KEYWORD2 -writeAnything KEYWORD2 -eraseSection KEYWORD2 -eraseSector KEYWORD2 -eraseBlock32K KEYWORD2 -eraseBlock64K KEYWORD2 -eraseChip KEYWORD2 -suspendProg KEYWORD2 -resumeProg KEYWORD2 -powerUp KEYWORD2 -powerDown KEYWORD2 +begin KEYWORD2 +setClock KEYWORD2 +libver KEYWORD2 +error KEYWORD2 +getManID KEYWORD2 +getJEDECID KEYWORD2 +getUniqueID KEYWORD2 +getAddress KEYWORD2 +sizeofStr KEYWORD2 +getCapacity KEYWORD2 +getMaxPage KEYWORD2 +functionRunTime KEYWORD2 +readByte KEYWORD2 +readByteArray KEYWORD2 +readChar KEYWORD2 +readCharArray KEYWORD2 +readWord KEYWORD2 +readShort KEYWORD2 +readLong KEYWORD2 +readULong KEYWORD2 +readFloat KEYWORD2 +readStr KEYWORD2 +readAnything KEYWORD2 +writeByte KEYWORD2 +writeByteArray KEYWORD2 +writeChar KEYWORD2 +writeCharArray KEYWORD2 +writeWord KEYWORD2 +writeShort KEYWORD2 +writeLong KEYWORD2 +writeULong KEYWORD2 +writeFloat KEYWORD2 +writeStr KEYWORD2 +writeAnything KEYWORD2 +eraseSection KEYWORD2 +eraseSector KEYWORD2 +eraseBlock32K KEYWORD2 +eraseBlock64K KEYWORD2 +eraseChip KEYWORD2 +suspendProg KEYWORD2 +resumeProg KEYWORD2 +powerUp KEYWORD2 +powerDown KEYWORD2 ####################################### # Constants (LITERAL1) ####################################### -WINBOND_MANID LITERAL1 -MICROCHIP_MANID LITERAL1 -CYPRESS_MANID LITERAL1 -ADESTO_MANID LITERAL1 -MICRON_MANID LITERAL1 -NULLBYTE LITERAL1 -NULLINT LITERAL1 -BYTE LITERAL1 -KiB LITERAL1 -MiB LITERAL1 +WINBOND_MANID LITERAL1 +MICROCHIP_MANID LITERAL1 +CYPRESS_MANID LITERAL1 +ADESTO_MANID LITERAL1 +MICRON_MANID LITERAL1 +NULLBYTE LITERAL1 +NULLINT LITERAL1 +BYTE LITERAL1 +KiB LITERAL1 +MiB LITERAL1 ####################################### # Built-in variables (LITERAL2) From 03e01e79496c4486c8d06acfbad0ff89d74619da Mon Sep 17 00:00:00 2001 From: Marzogh Date: Wed, 28 Feb 2018 13:39:53 +1000 Subject: [PATCH 06/26] Update ISSUE_TEMPLATE.md Update ISSUE_TEMPLATE to include details of how to troubleshoot with FlashDiagnostics.ino. Also included warning in the beginning that all issues must be submitted according to the template --- .github/ISSUE_TEMPLATE.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 6065e76..e6f5582 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,7 +1,10 @@ Hey there! Thanks for using the SPIFlash library for Arduino. +### Please note that starting 01.03.2018 any issue raised here MUST be submitted according to this template or it will be flagged with 'Not enough information'. No action will be taken till all the prerequisite information is provided. If no information is provided for over a month, the issue will be closed. **Note: For support questions, please use the [Arduino Forums](http://forum.arduino.cc/index.php?topic=324009.0). This repository's issues are reserved for feature requests and bug reports.** +# Issue submission template + **I'm submitting a ...** - [ ] bug report - [ ] feature request @@ -27,8 +30,21 @@ When opening an issue please include the following details: -------------------------- ###### Bug reports only - -- [ ] If this is a bug report - Provide a **minimal code snippet** example that reproduces the bug. Please make sure you wrap any code in the proper code blocks like below +- If this is a bug report - + +- [ ] Make sure you have run FlashDiagnostics.ino with ``` #define RUNDIAGNOSTICS ``` uncommented in SPIFlash.h. **List any error codes** that pop up in your Serial output when you run FlashDiagnostics.ino.here: + Error codes + ------------ + - + - + - + - + - +- [ ] If you have a problem with a particular function, call the flash.error() function (after you have made sure you have started up your Serial port with a ``` Serial.begin(BAUD) ``` ). Provide details of the function, the data given to/ expected from the function and the error code here: (**Please repeat this for every function you have an error with***) + Function - + Data - + Error code - +- [ ] Provide a **minimal code snippet** example that reproduces the bug. Please make sure you wrap any code in the proper code blocks like below ``` ```CODE HERE``` ``` From a4c17e148bbe1cda11f0a09358a7e1195404009a Mon Sep 17 00:00:00 2001 From: Marzogh Date: Wed, 28 Feb 2018 13:42:35 +1000 Subject: [PATCH 07/26] Update ISSUE_TEMPLATE.md --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index e6f5582..14f8c13 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -3,7 +3,7 @@ Hey there! Thanks for using the SPIFlash library for Arduino. **Note: For support questions, please use the [Arduino Forums](http://forum.arduino.cc/index.php?topic=324009.0). This repository's issues are reserved for feature requests and bug reports.** -# Issue submission template +# Issue details **I'm submitting a ...** - [ ] bug report From 0746cc82dd7681e7fee6dc6f288b152e5578b7d1 Mon Sep 17 00:00:00 2001 From: Marzogh Date: Wed, 28 Feb 2018 13:43:31 +1000 Subject: [PATCH 08/26] Update PULL_REQUEST_TEMPLATE.md Update PULL_REQUEST_TEMPLATE to warning in the beginning that all PRs must be submitted according to the template --- .github/PULL_REQUEST_TEMPLATE.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index eb18044..7e59ed7 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,8 @@ +Hey there! Thanks for using the SPIFlash library for Arduino. +### Please note that starting 01.03.2018 any Pull request raised here MUST be submitted according to this template or it will be flagged with 'Not enough information'. No action will be taken till all the prerequisite information is provided. If no information is provided for over a month, the pull request will be closed. + +# Pull request details + * **Please check if the PR fulfills these requirements** - [ ] The commit message/s explain/s all the changes clearly - [ ] Tests for the changes have been added (for bug fixes / features) @@ -5,7 +10,10 @@ * **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) - +- [ ] Bug fix +- [ ] Added feature +- [ ] Documentation update +- [ ] Other - Please explain here: * **What is the current behavior?** (You can also link to an open issue here) From 95b43f4253a9bdbfa6181caf4b2cc4034ef1cbbc Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Fri, 2 Mar 2018 19:54:43 +1000 Subject: [PATCH 09/26] Fixes #110 --- src/FLASHIO.cpp | 19 +++++++++++++++---- src/SPIFlash.cpp | 6 +++++- src/SPIFlash.h | 5 +++-- src/defines.h | 1 + src/troubleshoot.cpp | 4 ++++ 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/FLASHIO.cpp b/src/FLASHIO.cpp index 62bc0f8..7c4e318 100644 --- a/src/FLASHIO.cpp +++ b/src/FLASHIO.cpp @@ -96,11 +96,11 @@ bool SPIFlash::_prep(uint8_t opcode, uint32_t _addr, uint32_t size) { //Serial.print(F("Address being prepped: ")); //Serial.println(_addr); #ifndef HIGHSPEED - if(!_addressCheck(_addr, size) || !_notPrevWritten(_addr, size) || !_notBusy() || !_writeEnable()) { + if(_isChipPoweredDown() || !_addressCheck(_addr, size) || !_notPrevWritten(_addr, size) || !_notBusy() || !_writeEnable()) { return false; } #else - if (!_addressCheck(_addr, size) || !_notBusy() || !_writeEnable()) { + if (_isChipPoweredDown() || !_addressCheck(_addr, size) || !_notBusy() || !_writeEnable()) { return false; } #endif @@ -108,14 +108,14 @@ bool SPIFlash::_prep(uint8_t opcode, uint32_t _addr, uint32_t size) { break; case ERASEFUNC: - if(!_addressCheck(_addr, size) || !_notBusy() || !_writeEnable()) { + if(_isChipPoweredDown() || !_addressCheck(_addr, size) || !_notBusy() || !_writeEnable()) { return false; } return true; break; default: - if (!_addressCheck(_addr, size) || !_notBusy()) { + if (_isChipPoweredDown() || !_addressCheck(_addr, size) || !_notBusy()) { return false; } #ifdef ENABLEZERODMA @@ -400,6 +400,17 @@ bool SPIFlash::_noSuspend(void) { return true; } +// Checks to see if chip is powered down. If it is, retrns true. If not, returns false. +bool SPIFlash::_isChipPoweredDown(void) { + if (chipPoweredDown) { + _troubleshoot(CHIPISPOWEREDDOWN); + return true; + } + else { + return false; + } +} + // Polls the status register 1 until busy flag is cleared or timeout bool SPIFlash::_notBusy(uint32_t timeout) { _delay_us(WINBOND_WRITE_DELAY); diff --git a/src/SPIFlash.cpp b/src/SPIFlash.cpp index 832a0a0..e628d21 100644 --- a/src/SPIFlash.cpp +++ b/src/SPIFlash.cpp @@ -167,7 +167,7 @@ uint32_t SPIFlash::getJEDECID(void) { // Returns a 64-bit Unique ID that is unique to each flash memory chip uint64_t SPIFlash::getUniqueID(void) { - if(!_notBusy()) { + if(!_notBusy() || _isChipPoweredDown()) { return false; } _beginSPI(UNIQUEID); @@ -985,10 +985,12 @@ bool SPIFlash::powerDown(void) { _delay_us(5); #ifdef RUNDIAGNOSTIC + chipPoweredDown = true; bool _retVal = !_writeEnable(false); _spifuncruntime = micros() - _spifuncruntime; return _retVal; #else + chipPoweredDown = true; return !_writeEnable(false); #endif } @@ -1011,12 +1013,14 @@ bool SPIFlash::powerUp(void) { if (_writeEnable(false)) { _writeDisable(); _spifuncruntime = micros() - _spifuncruntime; + chipPoweredDown = false; return true; } return false; #else if (_writeEnable(false)) { _writeDisable(); + chipPoweredDown = false; return true; } return false; diff --git a/src/SPIFlash.h b/src/SPIFlash.h index b88f9db..e2d7a23 100644 --- a/src/SPIFlash.h +++ b/src/SPIFlash.h @@ -246,6 +246,7 @@ class SPIFlash { void _printSupportLink(void); void _endSPI(void); bool _disableGlobalBlockProtect(void); + bool _isChipPoweredDown(void); bool _prep(uint8_t opcode, uint32_t _addr, uint32_t size = 0); bool _startSPIBus(void); bool _beginSPI(uint8_t opcode); @@ -285,7 +286,7 @@ class SPIFlash { gpio_t csPin; #endif volatile uint8_t *cs_port; - bool pageOverflow, SPIBusState; + bool pageOverflow, SPIBusState, chipPoweredDown; bool address4ByteEnabled = false; uint8_t cs_mask, errorcode, stat1, stat2, stat3, _SPCR, _SPSR, _a0, _a1, _a2; char READ = 'R'; @@ -341,7 +342,7 @@ template bool SPIFlash::readAnything(uint32_t _addr, T& data, bool fas // 2. const T& value --> Variable with the data to be error checked // 3. _sz --> Size of the data variable to be error checked, in bytes (1 byte = 8 bits) template bool SPIFlash::_writeErrorCheck(uint32_t _addr, const T& value, uint32_t _sz) { - if (!_notBusy()) { + if (!_notBusy() || _isChipPoweredDown()) { return false; } //Serial.print(F("Address being error checked: ")); diff --git a/src/defines.h b/src/defines.h index 6244a12..d99df01 100644 --- a/src/defines.h +++ b/src/defines.h @@ -185,6 +185,7 @@ #define UNSUPPORTEDFUNC 0x0C #define UNABLETO4BYTE 0x0D #define UNABLETO3BYTE 0x0E + #define CHIPISPOWEREDDOWN 0x0F #define UNKNOWNERROR 0xFE //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// diff --git a/src/troubleshoot.cpp b/src/troubleshoot.cpp index 0e06d1f..4744ef9 100644 --- a/src/troubleshoot.cpp +++ b/src/troubleshoot.cpp @@ -126,6 +126,10 @@ void SPIFlash::_troubleshoot(uint8_t _code, bool printoverride) { Serial.println("Unable to disable 4-byte addressing."); break; + case CHIPISPOWEREDDOWN: + Serial.println("The Flash chip is currently powered down."); + break; + default: Serial.println("Unknown error"); break; From 9af3d118ec8d331ee6a02ca8da9d3db223a20193 Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Fri, 2 Mar 2018 22:09:36 +1000 Subject: [PATCH 10/26] Made doubly sure that chipPoweredDown instantiates as 'false' and that it is checked during erase functions as well. Updated Changes.log to reflect changes made. --- extras/Changes.log | 4 +++- src/SPIFlash.cpp | 8 ++++---- src/SPIFlash.h | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/extras/Changes.log b/extras/Changes.log index aa8710c..d7c195a 100644 --- a/extras/Changes.log +++ b/extras/Changes.log @@ -10,7 +10,9 @@ Bugs squashed --> An error with how _addressCheck() works with data that spans the memory boundary - when rolling over from address '_chip.capacity' to address '0x00' - has been fixed. In previous versions this caused issues with writing complex data structures across the memory boundary and led to many _writeErrorCheck() failures. Fixes issue #112 Enhancements: ---> A new function - 'flash.eraseSection(address, size)' - has been intrduced in this version. When a user requires a large and variable (between writes) amount of data to be written to the flash memory on the fly and to have the correct amount of space erased to fit the data, this function will automatically calculate and erase the right amount of space to fit the data. Please note that if the the amount of data being written is consistently the same size, the pre-existing 'flash.eraseSector()', 'flash.eraseBlock32K()' and 'flash.eraseBlock64K()' functions will operate a lot faster. +--> A new function - 'flash.eraseSection(address, size)' - has been introduced in this version. When a user requires a large and variable (between writes) amount of data to be written to the flash memory on the fly and to have the correct amount of space erased to fit the data, this function will automatically calculate and erase the right amount of space to fit the data. Please note that if the the amount of data being written is consistently the same size, the pre-existing 'flash.eraseSector()', 'flash.eraseBlock32K()' and 'flash.eraseBlock64K()' functions will operate a lot faster. + +--> All I/O functions now check to see if the flash chip is powered down. If it is, they prevent the function from running and returns an error. A new error code 'CHIPISPOWEREDDOWN' will be returned upon calling flash.error(). New flash memory chips supported: diff --git a/src/SPIFlash.cpp b/src/SPIFlash.cpp index e628d21..493e276 100644 --- a/src/SPIFlash.cpp +++ b/src/SPIFlash.cpp @@ -103,7 +103,7 @@ bool SPIFlash::begin(uint32_t flashChipSize) { if (_chip.manufacturerID == CYPRESS_MANID) { setClock(SPI_CLK/4); // Cypress/Spansion chips appear to perform best at SPI_CLK/4 } - + chipPoweredDown = false; return true; } @@ -895,7 +895,7 @@ bool SPIFlash::eraseChip(void) { #ifdef RUNDIAGNOSTIC _spifuncruntime = micros(); #endif - if(!_notBusy() || !_writeEnable()) { + if(_isChipPoweredDown() || !_notBusy() || !_writeEnable()) { return false; } @@ -922,7 +922,7 @@ bool SPIFlash::suspendProg(void) { #ifdef RUNDIAGNOSTIC _spifuncruntime = micros(); #endif - if(_notBusy()) { + if(_isChipPoweredDown() || _notBusy()) { return false; } @@ -950,7 +950,7 @@ bool SPIFlash::resumeProg(void) { #ifdef RUNDIAGNOSTIC _spifuncruntime = micros(); #endif - if(!_notBusy() || _noSuspend()) { + if(_isChipPoweredDown() || !_notBusy() || _noSuspend()) { return false; } diff --git a/src/SPIFlash.h b/src/SPIFlash.h index e2d7a23..d2d1e8d 100644 --- a/src/SPIFlash.h +++ b/src/SPIFlash.h @@ -286,7 +286,8 @@ class SPIFlash { gpio_t csPin; #endif volatile uint8_t *cs_port; - bool pageOverflow, SPIBusState, chipPoweredDown; + bool pageOverflow, SPIBusState; + bool chipPoweredDown = false; bool address4ByteEnabled = false; uint8_t cs_mask, errorcode, stat1, stat2, stat3, _SPCR, _SPSR, _a0, _a1, _a2; char READ = 'R'; From 6ccc2e0aa66657cccdd784a60a74a2ac7b8ada75 Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Sun, 4 Mar 2018 00:10:18 +1000 Subject: [PATCH 11/26] An error with how _writeErrorCheck() worked has been resolved. Writing structs is now as stable as other functions are. Fixes #106 --- examples/Struct_writer/Struct_writer.ino | 112 +++++++++++++++++++---- extras/Changes.log | 2 + extras/~$Library speed comparisons.xlsx | Bin 0 -> 165 bytes src/SPIFlash.cpp | 72 ++------------- src/SPIFlash.h | 61 ++++++++++-- src/defines.h | 16 ++++ src/troubleshoot.cpp | 2 +- 7 files changed, 173 insertions(+), 92 deletions(-) create mode 100644 extras/~$Library speed comparisons.xlsx diff --git a/examples/Struct_writer/Struct_writer.ino b/examples/Struct_writer/Struct_writer.ino index 0a172a8..763d6ea 100644 --- a/examples/Struct_writer/Struct_writer.ino +++ b/examples/Struct_writer/Struct_writer.ino @@ -2,10 +2,10 @@ |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| | Struct_writer.ino | | SPIFlash library | - | v 2.6.0 | + | v 3.1.0 | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| | Marzogh | - | 24.02.2018 | + | 03.03.2018 | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| | | | This program writes a struct to a random location on your flash memory chip and reads it back. | @@ -16,6 +16,8 @@ #include //#define PRINTDETAIL +//#define PRINTINDIVIDUALRUNS +#define NUMBEROFREPEATS 100 #if defined(ARDUINO_SAMD_ZERO) && defined(SERIAL_PORT_USBVIRTUAL) // Required for Serial on Zero based boards @@ -37,7 +39,7 @@ SPIFlash flash; struct ConfigurationIn { float lux = 3.24; - float vOut = 4.45; // Voltage ouput from potential divider to Analog input + float vOut = 4.45; // Voltage ouput from potential divider to Analog input float RLDR = 1.234; // Resistance calculation of potential divider with LDR bool light = true; uint8_t adc = 45; @@ -84,10 +86,14 @@ struct ConfigurationOut { } charging; }; ConfigurationOut configurationOut; - +uint16_t eraseCount, writeCount, errorCount, readCount; void setup() { Serial.begin(BAUD_RATE); + eraseCount = 0; + writeCount = 0; + errorCount = NUMBEROFREPEATS; + readCount = 0; #if defined (ARDUINO_SAMD_ZERO) || (__AVR_ATmega32U4__) while (!Serial) ; // Wait for Serial monitor to open #endif @@ -98,21 +104,39 @@ void setup() { Serial.print(F(".")); } Serial.println(); - Serial.println(); flash.begin(); + Serial.println(); +#ifdef PRINTINDIVIDUALRUNS + printLine(); + Serial.println(); + Serial.print("\tStruct number\t\t\tSection Erase\t\t\tStruct Write\t\t\tErrorCheck\t\t\tStruct Read\t\t\t"); + Serial.println(); + printLine(); + Serial.println(); +#endif - for (uint8_t x = 1; x <= 20; x++) { + for (uint16_t x = 1; x <= NUMBEROFREPEATS; x++) { + Serial.println(x); //uint32_t _addr = random(0, 1677215); uint32_t _addr = random(0, flash.getCapacity()); - Serial.print("Size of array: "); - Serial.println(sizeof(configurationIn)); +#ifdef PRINTINDIVIDUALRUNS + Serial.print("\t\t"); + Serial.print(x); +#endif if (flash.eraseSection(_addr, sizeof(configurationIn))) { - Serial.println("Section has been erased"); - } + //if (flash.eraseSector(_addr)) { + eraseCount++; +#ifdef PRINTINDIVIDUALRUNS + Serial.print("\t\t\t Done"); +#endif + } + else { + eraseCount--; + } /*if (flash.eraseSector(_addr)) { Serial.println("Sector has been erased"); - }*/ + }*/ if (flash.writeAnything(_addr, configurationIn)) { #ifdef PRINTDETAIL Serial.println(configurationIn.lux); @@ -147,17 +171,18 @@ void setup() { Serial.println(configurationIn.charging.interval); Serial.println(configurationIn.charging.highChargingDefault); #endif - Serial.print ("Data write "); - Serial.print(x); - Serial.println(" successful"); + writeCount++; + errorCount--; +#ifdef PRINTINDIVIDUALRUNS + Serial.print("\t\t\t Done\t\t\t\t Pass"); +#endif } else { - Serial.print ("Data write "); - Serial.print(x); - Serial.println(" failed"); +#ifdef PRINTINDIVIDUALRUNS + Serial.print("\t\t\t Done\t\t\t\t Fail"); +#endif } - Serial.println(); - +delay(50); if (flash.readAnything(_addr, configurationOut)) { #ifdef PRINTDETAIL Serial.println(configurationOut.lux); @@ -192,10 +217,57 @@ void setup() { Serial.println(configurationOut.charging.interval); Serial.println(configurationOut.charging.highChargingDefault); #endif + if (configurationIn.lux == configurationOut.lux || configurationIn.vOut == configurationOut.vOut || configurationIn.RLDR == configurationOut.RLDR || configurationIn.light == configurationOut.light || \ + configurationIn.adc == configurationOut.adc || configurationIn.arr[2] == configurationOut.arr[2] || configurationIn.misc.tempHigh == configurationOut.misc.tempHigh || configurationIn.misc.tempLow == configurationOut.misc.tempLow || \ + configurationIn.misc.parkingMode == configurationOut.misc.parkingMode || configurationIn.misc.allowDataToBeSent == configurationOut.misc.allowDataToBeSent || configurationIn.network.ssid[3] == configurationOut.network.ssid[3] || configurationIn.network.pwd[1] == configurationOut.network.pwd[1] || \ + configurationIn.network.userid[4] == configurationOut.network.userid[4] || configurationIn.charging.interval == configurationOut.charging.interval || configurationIn.charging.highChargingDefault == configurationOut.charging.highChargingDefault) { +#ifdef PRINTINDIVIDUALRUNS + Serial.print("\t\t\t\t Pass"); + Serial.println(); +#endif + readCount++; + } + else { +#ifdef PRINTINDIVIDUALRUNS + Serial.print("\t\t\t\t Fail"); + Serial.println(); +#endif + readCount--; + } } } - delay(1000); +#ifdef PRINTINDIVIDUALRUNS + printLine(); +#endif + Serial.println(); + printLine(); + Serial.println(); + Serial.println("\t\tFinal results"); + printLine(); + Serial.println(); + Serial.print("\t\tNo. of successful erases: "); + Serial.print("\t\t"); + Serial.println(eraseCount); + Serial.print("\t\tNo. of successful writes: "); + Serial.print("\t\t"); + Serial.println(writeCount); + Serial.print("\t\tNo. of errors generated:"); + Serial.print("\t\t"); + Serial.print(errorCount); + Serial.println("\t(errorCheck function failures)"); + Serial.print("\t\tNo. of successful reads: "); + Serial.print("\t\t"); + Serial.println(readCount); + printLine(); + Serial.println(); } void loop() { } + +void printLine() { + for (uint16_t i = 0; i < 160; i++) { + Serial.print("-"); + } +} + diff --git a/extras/Changes.log b/extras/Changes.log index d7c195a..83e192a 100644 --- a/extras/Changes.log +++ b/extras/Changes.log @@ -9,6 +9,8 @@ Bugs squashed --> An error with how _addressCheck() works with data that spans the memory boundary - when rolling over from address '_chip.capacity' to address '0x00' - has been fixed. In previous versions this caused issues with writing complex data structures across the memory boundary and led to many _writeErrorCheck() failures. Fixes issue #112 +--> An error with how _writeErrorCheck() worked has been resolved. Writing structs is now as stable as other functions are. Fixes #106 + Enhancements: --> A new function - 'flash.eraseSection(address, size)' - has been introduced in this version. When a user requires a large and variable (between writes) amount of data to be written to the flash memory on the fly and to have the correct amount of space erased to fit the data, this function will automatically calculate and erase the right amount of space to fit the data. Please note that if the the amount of data being written is consistently the same size, the pre-existing 'flash.eraseSector()', 'flash.eraseBlock32K()' and 'flash.eraseBlock64K()' functions will operate a lot faster. diff --git a/extras/~$Library speed comparisons.xlsx b/extras/~$Library speed comparisons.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2107d658f7008f8b7bcd710ab0dc7e77c612f2f1 GIT binary patch literal 165 zcmWd(C`!yKPs~wp%1A6JNi0gtRUifkG6XObF(fi%F_Z(z90mmjCx#3ls{{xW!7{l( HF={~oWsDYW literal 0 HcmV?d00001 diff --git a/src/SPIFlash.cpp b/src/SPIFlash.cpp index 493e276..1635722 100644 --- a/src/SPIFlash.cpp +++ b/src/SPIFlash.cpp @@ -422,14 +422,7 @@ bool SPIFlash::readStr(uint32_t _addr, String &data, bool fastRead) { // WARNING: You can only write to previously erased memory locations (see datasheet). // Use the eraseSector()/eraseBlock32K/eraseBlock64K commands to first clear memory (write 0xFFs) bool SPIFlash::writeByte(uint32_t _addr, uint8_t data, bool errorCheck) { - #ifdef RUNDIAGNOSTIC - _spifuncruntime = micros(); - bool _retVal = _write(_addr, data, sizeof(data), errorCheck); - _spifuncruntime = micros() - _spifuncruntime; - return _retVal; - #else - return _write(_addr, data, sizeof(data), errorCheck); - #endif + return _write(_addr, data, sizeof(data), errorCheck, _BYTE_); } // Writes a char of data to a specific location in a page. @@ -440,14 +433,7 @@ bool SPIFlash::writeByte(uint32_t _addr, uint8_t data, bool errorCheck) { // WARNING: You can only write to previously erased memory locations (see datasheet). // Use the eraseSector()/eraseBlock32K/eraseBlock64K commands to first clear memory (write 0xFFs) bool SPIFlash::writeChar(uint32_t _addr, int8_t data, bool errorCheck) { - #ifdef RUNDIAGNOSTIC - _spifuncruntime = micros(); - bool _retVal = _write(_addr, data, sizeof(data), errorCheck); - _spifuncruntime = micros() - _spifuncruntime; - return _retVal; - #else - return _write(_addr, data, sizeof(data), errorCheck); - #endif + return _write(_addr, data, sizeof(data), errorCheck, _CHAR_); } // Writes an array of bytes starting from a specific location in a page. @@ -624,14 +610,7 @@ bool SPIFlash::writeCharArray(uint32_t _addr, char *data_buffer, size_t bufferSi // WARNING: You can only write to previously erased memory locations (see datasheet). // Use the eraseSector()/eraseBlock32K/eraseBlock64K commands to first clear memory (write 0xFFs) bool SPIFlash::writeWord(uint32_t _addr, uint16_t data, bool errorCheck) { - #ifdef RUNDIAGNOSTIC - _spifuncruntime = micros(); - bool _retVal = _write(_addr, data, sizeof(data), errorCheck); - _spifuncruntime = micros() - _spifuncruntime; - return _retVal; - #else - return _write(_addr, data, sizeof(data), errorCheck); - #endif + return _write(_addr, data, sizeof(data), errorCheck, _WORD_); } // Writes a signed int as two bytes starting from a specific location in a page @@ -642,14 +621,7 @@ bool SPIFlash::writeWord(uint32_t _addr, uint16_t data, bool errorCheck) { // WARNING: You can only write to previously erased memory locations (see datasheet). // Use the eraseSector()/eraseBlock32K/eraseBlock64K commands to first clear memory (write 0xFFs) bool SPIFlash::writeShort(uint32_t _addr, int16_t data, bool errorCheck) { - #ifdef RUNDIAGNOSTIC - _spifuncruntime = micros(); - bool _retVal = _write(_addr, data, sizeof(data), errorCheck); - _spifuncruntime = micros() - _spifuncruntime; - return _retVal; - #else - return _write(_addr, data, sizeof(data), errorCheck); - #endif + return _write(_addr, data, sizeof(data), errorCheck, _SHORT_); } // Writes an unsigned long as four bytes starting from a specific location in a page. @@ -660,14 +632,7 @@ bool SPIFlash::writeShort(uint32_t _addr, int16_t data, bool errorCheck) { // WARNING: You can only write to previously erased memory locations (see datasheet). // Use the eraseSector()/eraseBlock32K/eraseBlock64K commands to first clear memory (write 0xFFs) bool SPIFlash::writeULong(uint32_t _addr, uint32_t data, bool errorCheck) { - #ifdef RUNDIAGNOSTIC - _spifuncruntime = micros(); - bool _retVal = _write(_addr, data, sizeof(data), errorCheck); - _spifuncruntime = micros() - _spifuncruntime; - return _retVal; - #else - return _write(_addr, data, sizeof(data), errorCheck); - #endif + return _write(_addr, data, sizeof(data), errorCheck, _ULONG_); } // Writes a signed long as four bytes starting from a specific location in a page @@ -678,14 +643,7 @@ bool SPIFlash::writeULong(uint32_t _addr, uint32_t data, bool errorCheck) { // WARNING: You can only write to previously erased memory locations (see datasheet). // Use the eraseSector()/eraseBlock32K/eraseBlock64K commands to first clear memory (write 0xFFs) bool SPIFlash::writeLong(uint32_t _addr, int32_t data, bool errorCheck) { - #ifdef RUNDIAGNOSTIC - _spifuncruntime = micros(); - bool _retVal = _write(_addr, data, sizeof(data), errorCheck); - _spifuncruntime = micros() - _spifuncruntime; - return _retVal; - #else - return _write(_addr, data, sizeof(data), errorCheck); - #endif + return _write(_addr, data, sizeof(data), errorCheck, _LONG_); } // Writes a float as four bytes starting from a specific location in a page @@ -696,14 +654,7 @@ bool SPIFlash::writeLong(uint32_t _addr, int32_t data, bool errorCheck) { // WARNING: You can only write to previously erased memory locations (see datasheet). // Use the eraseSector()/eraseBlock32K/eraseBlock64K commands to first clear memory (write 0xFFs) bool SPIFlash::writeFloat(uint32_t _addr, float data, bool errorCheck) { - #ifdef RUNDIAGNOSTIC - _spifuncruntime = micros(); - bool _retVal = _write(_addr, data, sizeof(data), errorCheck); - _spifuncruntime = micros() - _spifuncruntime; - return _retVal; - #else - return _write(_addr, data, sizeof(data), errorCheck); - #endif + return _write(_addr, data, sizeof(data), errorCheck, _FLOAT_); } // Writes a string to a specific location on a page @@ -714,14 +665,7 @@ bool SPIFlash::writeFloat(uint32_t _addr, float data, bool errorCheck) { // WARNING: You can only write to previously erased memory locations (see datasheet). // Use the eraseSector()/eraseBlock32K/eraseBlock64K commands to first clear memory (write 0xFFs) bool SPIFlash::writeStr(uint32_t _addr, String &data, bool errorCheck) { - #ifdef RUNDIAGNOSTIC - _spifuncruntime = micros(); - bool _retVal = _write(_addr, data, sizeof(data), errorCheck); - _spifuncruntime = micros() - _spifuncruntime; - return _retVal; - #else - return _write(_addr, data, sizeof(data), errorCheck); - #endif + return _write(_addr, data, sizeof(data), errorCheck, _STRING_); } // Erases a number of sectors or blocks as needed by the data being input. diff --git a/src/SPIFlash.h b/src/SPIFlash.h index d2d1e8d..bbc3bdd 100644 --- a/src/SPIFlash.h +++ b/src/SPIFlash.h @@ -269,10 +269,10 @@ class SPIFlash { uint8_t _readStat1(void); uint8_t _readStat2(void); uint8_t _readStat3(void); - template bool _write(uint32_t _addr, const T& value, uint32_t _sz, bool errorCheck); + template bool _write(uint32_t _addr, const T& value, uint32_t _sz, bool errorCheck, uint8_t _dataType); template bool _read(uint32_t _addr, T& value, uint32_t _sz, bool fastRead = false); //template bool _writeErrorCheck(uint32_t _addr, const T& value); - template bool _writeErrorCheck(uint32_t _addr, const T& value, uint32_t _sz); + template bool _writeErrorCheck(uint32_t _addr, const T& value, uint32_t _sz, uint8_t _dataType = 0x00); //-------------------------------- Private variables ----------------------------------// #ifdef SPI_HAS_TRANSACTION SPISettings _settings; @@ -323,7 +323,7 @@ class SPIFlash { // WARNING: You can only write to previously erased memory locations (see datasheet). // Use the eraseSector()/eraseBlock32K/eraseBlock64K commands to first clear memory (write 0xFFs) template bool SPIFlash::writeAnything(uint32_t _addr, const T& data, bool errorCheck) { - return _write(_addr, data, sizeof(data), errorCheck); + return _write(_addr, data, sizeof(data), errorCheck, _STRUCT_); } // Reads any type of data from a specific location in the flash memory. @@ -342,7 +342,7 @@ template bool SPIFlash::readAnything(uint32_t _addr, T& data, bool fas // 1. _addr --> Any address from 0 to maxAddress // 2. const T& value --> Variable with the data to be error checked // 3. _sz --> Size of the data variable to be error checked, in bytes (1 byte = 8 bits) -template bool SPIFlash::_writeErrorCheck(uint32_t _addr, const T& value, uint32_t _sz) { +/*template bool SPIFlash::_writeErrorCheck(uint32_t _addr, const T& value, uint32_t _sz) { if (!_notBusy() || _isChipPoweredDown()) { return false; } @@ -359,9 +359,46 @@ template bool SPIFlash::_writeErrorCheck(uint32_t _addr, const T& valu _endSPI(); return false; } + //_delay_us(5); } _endSPI(); return true; +}*/ +template bool SPIFlash::_writeErrorCheck(uint32_t _addr, const T& value, uint32_t _sz, uint8_t _dataType) { + if (_isChipPoweredDown() || !_addressCheck(_addr, _sz) || !_notBusy()) { + return false; + } + const uint8_t* p = (const uint8_t*)(const void*)&value; + if (_dataType == _STRUCT_) { + uint8_t _inByte[_sz]; + _beginSPI(READDATA); + _nextBuf(READDATA, &(*_inByte), _sz); + _endSPI(); + for (uint16_t i = 0; i < _sz; i++) { + if (*p++ != _inByte[i]) { + _troubleshoot(ERRORCHKFAIL); + return false; + } + else { + return true; + } + } + } + else { + const uint8_t* p = (const uint8_t*)(const void*)&value; + CHIP_SELECT + _nextByte(WRITE, READDATA); + _transferAddress(); + for (uint16_t i = 0; i < _sz; i++) { + if (*p++ != _nextByte(READ)) { + _troubleshoot(ERRORCHKFAIL); + _endSPI(); + return false; + } + } + _endSPI(); + return true; + } } // Writes any type of data to a specific location in the flash memory. @@ -373,7 +410,12 @@ template bool SPIFlash::_writeErrorCheck(uint32_t _addr, const T& valu // WARNING: You can only write to previously erased memory locations (see datasheet). // Use the eraseSector()/eraseBlock32K/eraseBlock64K commands to first clear memory (write 0xFFs) -template bool SPIFlash::_write(uint32_t _addr, const T& value, uint32_t _sz, bool errorCheck) { +template bool SPIFlash::_write(uint32_t _addr, const T& value, uint32_t _sz, bool errorCheck, uint8_t _dataType) { + bool _retVal; +#ifdef RUNDIAGNOSTIC + _spifuncruntime = micros(); +#endif + uint32_t _addrIn = _addr; if (!_prep(PAGEPROG, _addrIn, _sz)) { return false; @@ -427,13 +469,14 @@ template bool SPIFlash::_write(uint32_t _addr, const T& value, uint32_ } data_offset += writeBufSz; length -= writeBufSz; - maxBytes = 256; // Now we can do up to 256 bytes per loop + maxBytes = SPI_PAGESIZE; // Now we can do up to 256 bytes per loop if(!_notBusy() || !_writeEnable()) { return false; } } while (length > 0); } } + if (!errorCheck) { _endSPI(); return true; @@ -441,8 +484,12 @@ template bool SPIFlash::_write(uint32_t _addr, const T& value, uint32_ else { //Serial.print(F("Address sent to error check: ")); //Serial.println(_addr); - return _writeErrorCheck(_addrIn, value, _sz); + _retVal = _writeErrorCheck(_addr, value, _sz, _dataType); } +#ifdef RUNDIAGNOSTIC + _spifuncruntime = micros() - _spifuncruntime; +#endif + return _retVal; } // Reads any type of data from a specific location in the flash memory. diff --git a/src/defines.h b/src/defines.h index d99df01..d81a7d6 100644 --- a/src/defines.h +++ b/src/defines.h @@ -188,6 +188,22 @@ #define CHIPISPOWEREDDOWN 0x0F #define UNKNOWNERROR 0xFE + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// + // List of Supported data types // + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// + + #define _BYTE_ 0x01 + #define _CHAR_ 0x02 + #define _WORD_ 0x03 + #define _SHORT_ 0x04 + #define _ULONG_ 0x05 + #define _LONG_ 0x06 + #define _FLOAT_ 0x07 + #define _STRING_ 0x08 + #define _BYTEARRAY_ 0x09 + #define _CHARARRAY_ 0x0A + #define _STRUCT_ 0x0B + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// // Bit shift macros // // Thanks to @VitorBoss // diff --git a/src/troubleshoot.cpp b/src/troubleshoot.cpp index 4744ef9..e4fce15 100644 --- a/src/troubleshoot.cpp +++ b/src/troubleshoot.cpp @@ -53,7 +53,7 @@ void SPIFlash::_troubleshoot(uint8_t _code, bool printoverride) { #if defined (ARDUINO_ARCH_AVR) _printErrorCode(); #else - Serial.println(); + //Serial.println(); switch (_code) { case SUCCESS: Serial.println("Function executed successfully"); From 9efed03b20fc6a1d1f95711e9917f0d27b64be0e Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Sun, 4 Mar 2018 21:52:28 +1000 Subject: [PATCH 12/26] - Library name change notice inserted into flash.begin(). This notice can be dismissed by commenting out the instance of in SPIFlash.h. Please refer to the Readme file for further details. - All compilation-time, non-critical errors have been fixed. --- .../Diagnostics_functions.ino | 10 +- .../FlashDiagnostics/FlashDiagnostics.ino | 6 +- examples/TestFlash/TestFlash.ino | 11 +- examples/TestFlash/command_list.ino | 97 ++++++++++++++++-- extras/Changes.log | 4 + extras/Library speed comparisons.xlsx | Bin 48504 -> 47585 bytes extras/~$Library speed comparisons.xlsx | Bin 165 -> 0 bytes src/SPIFlash.cpp | 34 ++++-- src/SPIFlash.h | 75 ++++++-------- 9 files changed, 159 insertions(+), 78 deletions(-) delete mode 100644 extras/~$Library speed comparisons.xlsx diff --git a/examples/FlashDiagnostics/Diagnostics_functions.ino b/examples/FlashDiagnostics/Diagnostics_functions.ino index 495afc1..173a4bc 100644 --- a/examples/FlashDiagnostics/Diagnostics_functions.ino +++ b/examples/FlashDiagnostics/Diagnostics_functions.ino @@ -2,14 +2,14 @@ |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| | FlashDiagnostic_functions.ino | | SPIFlash library | - | v 3.0.0 | + | v 3.1.0 | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| | Marzogh | - | 17.11.2017 | + | 04.03.2018 | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| | | | For a full diagnostics rundown - with error codes and details of the errors | - | uncomment #define RUNDIAGNOSTIC in SPIFlash.cpp in the library before compiling | + | uncomment #define RUNDIAGNOSTIC in SPIFlash.h in the library before compiling | | and loading this application onto your Arduino. | | | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| @@ -114,10 +114,6 @@ void getID() { b2 = (JEDEC >> 8); //b3 = (JEDEC >> 0); - - printLine(); - //---------------------------------------------------------------------------------------------// - clearprintBuffer(&printBuffer[1]); #if defined (ARDUINO_ARCH_ESP32) sprintf(printBuffer, "\t\t\tJEDEC ID: %04xh", JEDEC); diff --git a/examples/FlashDiagnostics/FlashDiagnostics.ino b/examples/FlashDiagnostics/FlashDiagnostics.ino index b4a3ad0..beaf51b 100644 --- a/examples/FlashDiagnostics/FlashDiagnostics.ino +++ b/examples/FlashDiagnostics/FlashDiagnostics.ino @@ -2,14 +2,14 @@ |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| | FlashDiagnostics.ino | | SPIFlash library | - | v 3.0.0 | + | v 3.1.0 | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| | Marzogh | - | 17.11.2017 | + | 04.03.2018 | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| | | | For a full diagnostics rundown - with error codes and details of the errors | - | uncomment #define RUNDIAGNOSTIC in SPIFlash.cpp in the library before compiling | + | uncomment #define RUNDIAGNOSTIC in SPIFlash.h in the library before compiling | | and loading this application onto your Arduino. | | | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| diff --git a/examples/TestFlash/TestFlash.ino b/examples/TestFlash/TestFlash.ino index ff4c51b..8817577 100644 --- a/examples/TestFlash/TestFlash.ino +++ b/examples/TestFlash/TestFlash.ino @@ -1,10 +1,10 @@ /* - ---------------------------------------------------------------------------------------------------------------------------------- + ------------------------------------------------------------------------------------------------------------------------------------ | Winbond Flash | - | SPIFlash library test v3.0.1 | + | SPIFlash library test v3.1.0 | |----------------------------------------------------------------------------------------------------------------------------------| | Marzogh | - | 16.11.2016 | + | 04.03.2018 | | Modified: hanyazou | | 19.11.2017 | |----------------------------------------------------------------------------------------------------------------------------------| @@ -530,11 +530,6 @@ void printAllPages(uint8_t outputType) { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Print commands~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// -void printLine() -{ - Serial.println(F("----------------------------------------------------------------------------------------------------------------------------------")); -} - void printSplash() { Serial.println(F(" SPIFlash library test ")); diff --git a/examples/TestFlash/command_list.ino b/examples/TestFlash/command_list.ino index 823d5e6..f853a1f 100644 --- a/examples/TestFlash/command_list.ino +++ b/examples/TestFlash/command_list.ino @@ -1,14 +1,10 @@ void commandList() { - Serial.println(F("-----------------------------------------------------------------------------------------------------------------------------------")); - Serial.println(F(" Winbond Flash ")); - Serial.println(F(" SPIFlash library test v3.0.1 ")); - Serial.println(F(" ----------------------------------------------------------------------------------------------------------------------------------")); - Serial.println(F(" Marzogh ")); - Serial.println(F(" 24.11.2015 ")); - Serial.println(F(" ----------------------------------------------------------------------------------------------------------------------------------")); + printSplash(); + getID(); + printLine(); Serial.println(F(" (Please make sure your Serial monitor is set to 'No Line Ending') ")); - Serial.println(F(" ***************************************************************** ")); - Serial.println(F(" ")); + Serial.println(); + Serial.println(); Serial.println(F(" # Please pick from the following commands and type the command number into the Serial console # ")); Serial.println(F(" For example - to write a byte of data, you would have to use the Write Byte function - so type '3' into the serial console. ")); Serial.println(F(" -------------------------------- ")); @@ -59,5 +55,86 @@ void commandList() { Serial.println(F(" 14. eraseChip")); Serial.print(F("\t\t")); Serial.println(F("'14' erases the entire chip")); - Serial.println(F(" ----------------------------------------------------------------------------------------------------------------------------------")); + printLine(); +} + +void printLine(void) { + //Serial.println(); + for (uint8_t i = 0; i < 130; i++) { + Serial.print(F("-")); + } + Serial.println(); +} + +void clearprintBuffer(char *bufPtr) { + for (uint8_t i = 0; i < 128; i++) { + //printBuffer[i] = 0; + *bufPtr++ = 0; + } +} + +void printUniqueID(void) { + Serial.print("Unique ID: "); + long long _uniqueID = flash.getUniqueID(); + Serial.print(uint32_t(_uniqueID / 1000000L)); + Serial.print(uint32_t(_uniqueID % 1000000L)); + Serial.print(", "); + Serial.print("0x"); + Serial.print(uint32_t(_uniqueID >> 32), HEX); + Serial.println(uint32_t(_uniqueID), HEX); +} + +void getID(void) { + + char printBuffer[128]; + printLine(); + for (uint8_t i = 0; i < 50; i++) { + Serial.print(F(" ")); + } + Serial.print(F("SPIFlash Library version")); +#ifdef LIBVER + uint8_t _ver, _subver, _bugfix; + flash.libver(&_ver, &_subver, &_bugfix); + clearprintBuffer(&printBuffer[1]); + sprintf(printBuffer, ": %d.%d.%d", _ver, _subver, _bugfix); + Serial.println(printBuffer); +#else + Serial.println(F("< 2.5.0")); +#endif + printLine(); + + for (uint8_t i = 0; i < 65; i++) { + Serial.print(F(" ")); + } + Serial.println(F("Get ID")); + printLine(); + uint8_t b1, b2; + //uint16_t b3; + uint32_t JEDEC = flash.getJEDECID(); + uint32_t maxPage = flash.getMaxPage(); + uint32_t capacity = flash.getCapacity(); + b1 = (JEDEC >> 16); + b2 = (JEDEC >> 8); + //b3 = (JEDEC >> 0); + + clearprintBuffer(&printBuffer[1]); +#if defined (ARDUINO_ARCH_ESP32) + sprintf(printBuffer, "\t\t\tJEDEC ID: %04xh", JEDEC); +#else + sprintf(printBuffer, "\t\t\tJEDEC ID: %04lxh", JEDEC); +#endif + Serial.println(printBuffer); + //Serial.print(F("\t\t\tJEDEC ID: ")); + //Serial.print(JEDEC, HEX); + //Serial.println(F("xh")); + clearprintBuffer(&printBuffer[1]); +#if defined (ARDUINO_ARCH_ESP32) + sprintf(printBuffer, "\t\t\tManufacturer ID: %02xh\n\t\t\tMemory Type: %02xh\n\t\t\tCapacity: %u bytes\n\t\t\tMaximum pages: %u", b1, b2, capacity, maxPage); +#else + sprintf(printBuffer, "\t\t\tManufacturer ID: %02xh\n\t\t\tMemory Type: %02xh\n\t\t\tCapacity: %lu bytes\n\t\t\tMaximum pages: %lu", b1, b2, capacity, maxPage); +#endif + Serial.print(printBuffer); + Serial.print("\n\t\t\t"); + printUniqueID(); } + diff --git a/extras/Changes.log b/extras/Changes.log index 83e192a..b914929 100644 --- a/extras/Changes.log +++ b/extras/Changes.log @@ -6,11 +6,15 @@ // Author: Prajwal Bhattaram // // 24.02.2018 // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// +--> Library name change notice inserted into flash.begin(). This notice can be dismissed by commenting out the instance of `#define PRINTNAMECHANGEALERT` in SPIFlash.h. Please refer to the Readme file for further details. + Bugs squashed --> An error with how _addressCheck() works with data that spans the memory boundary - when rolling over from address '_chip.capacity' to address '0x00' - has been fixed. In previous versions this caused issues with writing complex data structures across the memory boundary and led to many _writeErrorCheck() failures. Fixes issue #112 --> An error with how _writeErrorCheck() worked has been resolved. Writing structs is now as stable as other functions are. Fixes #106 +--> All compilation-time, non-critical errors have been fixed. + Enhancements: --> A new function - 'flash.eraseSection(address, size)' - has been introduced in this version. When a user requires a large and variable (between writes) amount of data to be written to the flash memory on the fly and to have the correct amount of space erased to fit the data, this function will automatically calculate and erase the right amount of space to fit the data. Please note that if the the amount of data being written is consistently the same size, the pre-existing 'flash.eraseSector()', 'flash.eraseBlock32K()' and 'flash.eraseBlock64K()' functions will operate a lot faster. diff --git a/extras/Library speed comparisons.xlsx b/extras/Library speed comparisons.xlsx index 4ac6b3d61a97fd11bfc24320e8569b52842d378d..902eda6b9a0cd0c6f6d11a0b246af5b71ef02adc 100644 GIT binary patch literal 47585 zcmeEuWmucr)-9B_lv3QKP@LlKPH+wG6nEF4EyW#*Q{3I%THG}_f#MFui{Esgd(OAJ z+w(m4|9A6)_aS-L%v^JgG3Lz5Tv3#Pdx{D39OeZK3=9bjQDJ6gA}kC{E%aXum={m9 zMeS^zO>CX@RY3M8PPz>4Hr8*lpFW|^gn0tJ|NlS!7e`jltc1`LqBrEr`1Eqwb6+km8(}H^d-Fr2&L@kd?z{yHLesvQFE_4Xp)Y)+bfM-G4fI)B!yZ}I;1&J+yIZ%Jw4eU zOuaV777yG_PVwX0yBf;ysPh%j#zj-U&37k!YTZWNOT4dDB3+1^HXVwq z((ZBAm5W1(QS$Za{bZ$c2=fOP4LHU1!6Z7~eE2tO-HQG3=?H|jW;e;I12K57tO!ot zD?j#RrCsxvj5YE;ydJ=(K2purp0+=7JlfEftl362-SmjDX`r+N&QtGqG|v9m?x8;0 zx9@vn`uN%N1P$Z@;hNwBnOo zBxuxGL*mont;G6AM^gR?C9w@!uw-y==5ZQ0Thz^)Trw0n_{B%XNXRA^9%7U*(Rm`P zj)a>MrBfrd)l*U~bw0ZzvB|`KPI)BCO#+Dr#iBV;>prfJw~WrVxu~)~Nw52{BQ%dl zU+ALq?Nw9JR~D9p4nuriMG;2U(gfollUx4iU(&fL5q5u6pMJH5>$3J{(tMXR4FBnQ z;K`^g5iaEwWsNzTU&ETzsdyTdd#o2`>X#Vn)|f`Qh9yJc0i-y)nZ~w|Z4J}vDAU(m zDRn4Y1{1~yfuaosdIuD3f5%y_%7E=6 z6KWgIjv#6)E%@kJ0$d-WWhQlasgNC;n6@)g` z2#Vvr`C5loELrKcYS@jKuLL~6@N#Q$48+Bg35(k*{)XVQ_FP|H&?kv7&S#0{A{Zu< z31PI9zRw>w!9i*yfCdEX>nbf{xmtMD9hWi%HvX)gjIrWkL|Q~`AI3Cu{8h0f?VVSa z1_j7K8+F9V@T08$fr~~=&k0HZnce00rpH>DGt43#JpXOpxP)sFiF=X zRxPG1ZI0{ZlT1mOQ`Po#8NLcR5YK$b=aq(PGOYyt!qS`#+)ePD2@RbB2H*P}p6}aX zxMaHV>hb-n727xIVFTwXp%H;_-_th}_WiU3)B~O+KfJi>h-I5QlNE*?XMfZ~7|A-S zW0^wmdP^`>Hm{hPCB2P6>Gu4TEH8frnT^WRpvF?hmSZ0)Hwn$BHU)oDIli4?UVleg zlm6L2c6K}+@pEVuP9rZ4L8A&DA|(JBwv|e6(9oPtIp;=e8FH-gv*>ceu(#) zVTNWQ0QcJ7V)wH7sQalz9g>OK-M0p3L_MSiy&cn+??q~|vx^>N3*xCUwNwaA%ZWa* zt8n|rMm@7W=ee*I8ExHN62MV{(_m4dxH_{8Ntjv^Ur z)q}S(ahNCaGoPK(u_WYm7VG(9W81DeGMN#Fl!YJuH#WxoLZ~NX~+&G?C zwFvSZFCRUh*u3~=czfA+dwF+xZ~MLDQSaf*)ZT&1cDda~&(&_Rou`;=2W zAqS5?t{=wwAp5-RCl}Y7*FW55cfPG%I5a;z)Gyk#w~uc>(Bj(=H60!aj^FK{t^2sm z@xNv)YASgEoUpFwLG*w!R~a4rQ&ftH_l6zb%>;aaDOWyx9f{Q; z^4Z~(L%TPKGuN_yezvt1pFUwaU zF-KufNPSE4sN=eDXtc@0_r%3%Zq$wO@$v5P$46VVX3=(=3=Tp4NaWn9iAu=*waM+> zeP2wjGx$hJuH!z<8YRjYH*M3TH1fXDGYf7a1E*qauQzAE?RwvqDK{!FN_5BmPFwd@ zQhg_nj6SbJ)58hG8eS0uX(WZUeIG=7jcaO16zY&+QcI73=IdGaUx2n3}P%s|!1%coBaZ3NQSW zL_i*THB06_R2!_FygXxn%Q7eh31En!NKw zX^Vwfc<&4X03ka-oMtack0o4;(IDpVBP~E5wG1sUm<9059GVwz2M;MQbC{fg1vixq zh0<;xL)IBr%kOk#cFbp9GY3+%QJ&Ia25Fg(^z$bWB7RKS`Q*!3S{f=b z{70Y6TT{SIP6;ST>W1vZSzF<%MrcDC>w)BG7u#w%rfi%(h#?FSt)Aj3kgz4sEj8Kj z1nLkYhvPC0Bd>&a^ujd4l)??HX#EV+CPQq0dgXnv~7D1s{vBZ^gM^5?7id&Gw8YK#^@X9nCNlnD(GNzHuPHl z9nu7|SS_@SrNcOlr0#xZ1YmZInG#+3Q8rE!WkipABVeFxrOH9a{Lm8znQ;}i~^g8*^y5K`t}p81LBg*vh*Hk7w(HOl!gy}=vpJ8jlK9r zu}`fKln_`D91&h3NF&f7SR&vfED(J=*Bf~ue=L=zHNVk|?Lc8Bz7c6d+Bp0gNFC_l zuyI+U@tnCVn_&Eg=2lOL6?^?)&^)|KXhuls4PG>%By8?eCD@`5Y(iRc_N$quPt4qh zhd{iaGF<4XR}MREiQXa9^rx)~X&A-xo&A|15BCh2b+>D2-+k>{$K0Ti(;}u2|wwz%vYR)G(+9)1FD~iTL z$UtW}Mlbz*)SFZ^)~Br+{JwD`UkAvB&Fp2OI^#6Olfz#88y~n5RgG5h()Yb!LADb5 zrW|CP$6ALrX1blS{4iDA7ELV1@rx}I-?9aAX+XN{Ik!vEI*H!<^fcEe49ipxz1#*9!k~?61 zHG$;qY~-J0IjW+rYzuLpozwp^_8Q3Lzb9_!rWQt0s}9QE(V~-2ywUJcu9AJL5zh`` zvy+35GwgWIk3m9d|HgqwvUTN}I=klUM5UZ}J z;SA07Sud3;SwPKT8xW07P)dGj*!!9Kw*&y;_$->gYZlt5&-A@lb6A?zg>;`-7aTTFw@Bw2o+A6s>760Llha%H3+`g5a%Gep5pGn-YdSy!?I4 zSIM@1=CdNl%r&6*6vM6Z2u(U=p>2e%!*2or6Lqp%TJbKx<$Vn#J3+7c_?bq+hBeJos_Uu))yQ&4hNPM>xlCpLy!ny9sfe<$=- zZ5VYk_$7cM`z0`ad zqoFbS+uW4k;MQtVD-T~}a5MXB#ZWBPU$exMvz3}yN#+8b&$5{Y^5}2BpqoPdDR6@N zL$OH`dRedBTLv_DwD_*PRbNn`7>Ua=3Ql9(~V?1fG@^}_7=t4?Zs z)Sl|1c!4+UCc11`Wnu4?N|Q(i(XfK00a)4iz>kF{nD!^7wTi>LM6+V1RIickyk4++!$WobK{+s#3datSEr z{$=MdlUb@h6er-OJAr7+>Hf#&=Ef6Ux_G?2Xqw2)hLF<1l$8t`cDp`<-`(nT+5KJ`u2i5_&ogH7 z$|-#a*Yqq1fz^e~2UY&|)mZ=GS5a=P897ikR{}`5q?#dR=8r4A(`l9$2Nn?>3E;;` zZen0@(gj1glVeeF`U-!*?s9hum0^%DMpY9>Zhz*#1SitG&g(h1fU>OVHek8kLBx(1EP zGhs9cx~&Wn6)~N{Z9SM5aA59D?{wQtuYiS;g_2rddJWY`dbQtl<;hk?yf(h$Xg|ZT zX(0D;0@LvI;AU6ycht8SB<=y5b=ZHMq)9#l!g%WS=mk$G=r#B!T%E(4W~k6rls9 zK#Tnn!>5`tJy%OJzOG7;*>c%g{C8D+!trzLeuu5?et>e5%WUD2(A$oCL%`mJe8#)8lw7E$xX>h{#o8Mi;+WNC7zbP0+RD}1V0{4Fo1 z5vE%11dI41Ph zL6d8aX?g^%jAwi$uw3o}-GoXl#~(!HOK%Oj7zB}A)$V-q>1kFG zu&LsD4IE<ub#_N7_~!uKTyx#p&fD=b`(?QQNZ#_iCh#71VP zL$L&B2_?=jtBM&Q{6<*OvdF^7IAJ}@u=p_}Y&|kq4vr2HJOWqxqLSrHF-!?uDMDq! zJULSxKdo|Zg0H0|@1<+g5EY~NrnMYGWjB-6zDP|-DlYy|nl}lM&ds8tbTG2${#tq8 zRrcBMwZqZTWy_rATVi})4;)B$%91iCE6r`D3DLU{1J~(>aqBgHvyd;n4}5&nk^(Pk zPw$7ILN>oSL@&d@|c=5W{!*F!W!^mxjBj(@T z5)xv(0@0OvM4N_&ic2HBCkvwJ@6(_DhIIT3E7uOr^!Rh&nddY?+ZT!q__6T8c}w)4 zGds{V7vIOTT9-sFA_2bQl_?!;p3CT{(bCGH`KL>m?h<9PtIo$k>SQS%1Xu8kDg}%R z-}f7*ttCMFf`+mBWNb84de1cslQ0sA5!PTqwx(ldo! zDol|(l5|RlCgMG5dVLyBY*fCDf*H;;gtLSOjKp9U{O6u9w( z{{o>-oZ%_-9y91D+*ri(H{@XaCRDm)%b0->Lm^WRP&aevob(KF0i*nufzSy7> z`{Hca1jSy{VstnS5m0K8G|D>!6T5sGsrM9dUCG5NunBaB3f28Fw$7rW>Nr`M8wt{W zIYU2E1QZ%Zqg{H@;-5UKA#@OVv3{IJyiP<2~B ze-LHtp9tw&x4XT+>KPa~dRW}Lw!N~w^*$q|M_HWRZfU#wadTmOb?@SIm3Mr8=gh*- z&o2mS_w?c>64z*LHwi!N>RP|2oL_!)Uv51J64v8XaP#DTgf#RW^Rh0S-D?MWwtL>M z_g(oe*=2MLp=J5pvHh9l< zd|H(c#MnH(d)T-S2|wTMN|d-p0-FeWK={1)+EeJ7YpzcQ3Q+E!-DLEQWbkgUNZhuJ zJ=TF1mX0~~y9?HH?k_#7Kywuw3JS$%x9en^{o99^DAVj%6Geo^vu;56rj`}Khox7i z`%R7)k2mYZzC*$ay*bT~9A@@YEpx3z-tCJvYKsMr-gniHlm#Ri35=;hEyN0*@Ph*u zb2g7Uc0?UMi(%(F4C&2*VT`_1sgSlLDzhK9#gX0}-m!24zESDK(?N>m_Yi&JD6>=A z-Srm>hmUFkvd%%mJhMvI9Wi?CEuZBS%s%bTpG<6z*!rFoM*4U;KN>Zx2&7^h`);l_ z-ZQ2z9UcpKyF(s8{XV6Rq)jvs%_UfzCte`C<}Ixy(yJqM>+XZ$MX>;rqG2p@rnAWQ$KzG`!BB6)c@ zlnC=5aWu#30ECG;VHTNQg?{6;C?6v+%gMqCyT9y;$6;Xo-QF|0xXmxA@f|KQA@SN> zH`xr+lbMVHzdM7O#wAPw%duG`VSZ<;htYhrDL=w%=6OB9*8)dTOP?VosSJ@HMNv&h+T@ z{vGM}`QqN`XmhzZbQ1tKP*??B6~GP@1*5w>pD;tfw9EJ?U3y8(va~a-iP{|A8zIE1 zUsH`=_p#|*8w0{~VvX4L8gL|G?1(@5E(gyW=_}7uix<_X8zj^sih-FggMG9&y$sy{`b+)_k8dANl5rme)-w^)IQDdq>rf=_SL?@Jw#h}cy5k{DitgM8W&JMuQj z$A6q9r-p-4d|NZ{y@^V>$&^LqRZA5OZ{0abS%g)wQg%2s6-}^oc4#4`Ww2JZZL8k7 zECtwZ`CXwoIEZzYwQI>jJRzyQc5?1CzZ-gn8xUOqg8x=iSK9qz-f|6?F zef{nk>lu|i03J|Y1vMUk1(XA$)&bIlmkf(t4q<{^L>Q144oghJNB z6-f^)#*6VUu+LE_U`;A@C6S!$v_4VX$8=v6X~Vx;<2?WD!HJ4`D;`t#%DfDPS`<(9 zx9M~v72R-kK&ti1@>7lG`w53gq2*@Ht15*OzM=eJZ#8xg@js~#Y5Hpwec)`1KB?}% zq9Cik5?L8nq^itFFLVG!#Js)aE*C?@A68Hn5sbz6E`67#H?L*;h3n;zk7PWCdxPdj zUh7E7p7!rsu7(Q*dR08(-dgMzZn^HC0fJVm#%T39F1W>U#lOOH^v#jvG#1|%1Kl&! zpuTUjQB1g&b2OdkU9AW~-wL44@O}<7Q7EadQ4%aPm<{{`LKQdOmVX>%-t)MkE!t0h zJ27_%8Qd{8j2vh-_)3bmq%cmiQWKV$M#S1RAQ-r0an!euxq<@|K}s)>~k z#oFMJI=`C*rd*Bnv)t{v3RaWcAr&X9&$qwGV40vIr?EPjo6vd{S8enUx+xkfdngK< zS`JdkquChC3sXWP0eXm>0ld# z80_izynd*)JmNn?r$kpXrYDeQnY7c9F8V#6HojF|Y-+HR_H;2%emm8e@N*f>@Uo$# zP9V9_jH$Ec$~ODs;twGgR}ITX^rV>tHvKi~=Eoh+)fCNjN*);xV!D4v)lW9`uihr# zCu;mr(y9s}OR!V2v&syuBi=uzdwpb+!*TQ+Xy;r?#p4Melwa>Nx(mStN1V*|Top|j zPo8hP?~s`JVr|2u8}L2lbL8nb zs0BmUNSb}TaV=tCFaA~jZz3Br4^lJ{w>g_$2A>Tx{e=d$Gd?3!LX{6@1_D{o?=067 z{!|2r@$nXdS*G%^$Nn#X)JLvY0AI7VlYQN&TgS?wPN$g}Nwjc0$va5;M^S<~0EJrdm<_8Yj?RailHx4Beuquv#9j>|%aM!&NMR zCWq$l8EZ5%cnV(C+%*5x5Tksv|FhkF)G#P+nW^H3-|c&?yybCbe9yoQZ?1^d>DOJU zFxG;++*#5^zbUoX<6Y*H2lz<CcyysGR5jDP^uZGV+CfE4x4@x21@MQ&aG;x~V^=YCzBkUQ z+~UB`I(#9^I>!3^t1y;3wyn-ND0t9=!?pJ8k9uGRud1_kgO`Z7P5YkAiJz6nZ9O~r zSks+TF$&u~5NGd|5hxwD>`=qVN9do$Ddx#RW@wmn-OZaFlW(h#FTl-q*n#s}Iy@JS z-DZvhS+kg1b&$%|>QtR^D#~8|>ey~en41R8#WyGIZ%yoQ+}5uRikyVjt+6LI(bNDM zcm}He(`YjQ4FZESgYB*sBnS@q2ug7;R48nlD=Jvxac23ejSu#l3tUExo0)mc3LFRw z_Fu?Im~Q+0(L~JaZDEtd2l@oV(c~*8XZJG$6>bzofCdYT)&BQ%SrBW2x`{BX*cE0x zlBK9cxA5;Z)oA8J&4G8A8@K zs}qpSDDMvhp8i2~ADoUm$d59ggi5EW$nu`0^B}CO8LFIOb35Iup(TV5A6b^O^IoxB z4t=*xQ;kSRD+~E&bvOE^mmceQrG4didTqYR7mm$+d$F)O;~H9IlGVuU)R|TX^QtBc zRlYY=ovF!s&ru zGyXW+y0CiOJBLi%Uj}(NHCgKmc;i<2e7wD0D_)s=?geVs=>4(hRTb(0!f0&`(mp`w z9oRrRVfJCyCBL^L-l-@ED{tW5I1|H}(wCFRQ9orMBm%vqA2JVXO|+>Bt=Ey;V$ph> zK&}X?rOTnGwa^sPE~iWkqrGl6Jy>GE88RP|zA4S7SDz{hpRt?ma{82DQfop%Ut&Hq ztrDIn;YhYk2{kq(t)(`RP?FsE21OSj{_`F&B0{~N<5(SpkNmsEF>tc!Pv0bjWA8h$ zW8iG~2wSr`kStWk3j8#rqZ8dJW6WluDXy(HSq=4RWXuG=dsIuBGHl;G42+CTBesbr z`AnEns#I)`5-~o#CQ3yCn?}kk46KMvqZ&1AVF5(o)y_??R@K<$t}KdN&^%(JK0Ip5{Lobm(g5qX>`ph&ou{3-_n(Ci5~2sw7Mp zn0EXBy4}R`uk9vXCsN+nz++Cs$B!Rxt90MPV%R9)4&oLbr_4jF7J4DwRS`Hl0!~}A zcE!>(CY!0ItNDUNZG-6Xu}XtCHJC|g_W>vN9n z+uz>pd*3@0MjXu8mB7{cK02K>xy>wRC@^2OFWjH(pV^HqUMaAgc%RjC->Kpq>a+?* z;Rp)Uw@hDg-asB--jtY`wcMa2`L6IC*WVuQuf`7RP9ycKczL>biX|L%v{~=qBu)8L zX?HBU5cxTPrjDTvSS9k~zIHRdNAn$w$ctm{_Z~^*E6b8g1g;F>@a=XDzPP(biUnl z+w083Gh5;daq&3+bl~vf5XCq7jA-sh3n8+JsS%sOIlDPXodJMJ>fszLt}!L-mucg@C1&~sZ(kvJ>#rU!Qot|v zrZMo{NaQHWgh60!aqCvqF}W=jIl{f`)c|V1`A#%(A}ip(j9h(}9dhBUfLnC}z-hv2yfcygK4m66R!=<~o$A!+JHiCv#FGhhpg*;E+?#)Q&cro;{93Py^6HlY2xLB{z-3w%%FR8Tousw!f#m6r<{ zY5LUnFBGFkV5{L~GjKiX!er)jjd5b3n5dx9u;tqJ6_r)Nm^Q(=*kdX$Rz&6&CsI$8 z78hEE*>jOvXQX#wq$DB61V#wx|7mm@#8+`BXXi{pg43TNO2Y_mbZytRmL--lxTOo?yy;0$dw_b@dUOP0k_T2~oXx!rt})cmDA)hIGDJ!w=x6;wS5; z<0s-*{am%e4m4@~P2n;Z4N-xon=gdAws3Qx99J=_F4(i0a`+SScaYXFniKd{H@I1m zeDc#^%##TV()re3&Fl;=I_X$+cg7<#tCPN@w=DbIOsXa@lWkCUUan?Ylj1(5fNZZG zipD7+r|{!lmSp#j ze6$SNp6hg$7;JrAhcBXf>d!Sw@T=t_#nSoM{af^(wlz`i4ZJ7`-f-7X&kV_oV2*w& z`H~zi7Vho)xP))W19YaNJ1-gRR@ECm4f^d8`2?`4YV2r?YhCu+Td8&x=Ut4uV%?M8 z4v^Kq$I*2x4`JG~EdG%6ETJHn)Y>eZS~!k$@UQ+66Q=}+@l^kwfvh?INy{hO>8|0P z_?kO=^?XRHbH!Te^}US+1l?+1v!{k3u-9rxe$_Ow2G^Lt4KdrVcx2M3|NioN{ou<9P=zxEB#_;&JkYiAd**m9&BG_K*0ZVnL;-y%V&rs>0y_3eB?r)w0cLqyp z>so@}JG9!4^UOE4SU-l(kjs3|izrA7{jrQNlFfHG=jv{<~4@b!R|x zvfJ1W4hOvGeWwCUBuGJ)w!~%DzImM4X?H*$>3x!(Ho^6TfD_idA0)>J%uD4*&-ogR}UJyVn&jvlfN$z=%zuh-MXu~VcL zyC2?jL`fKWl;K}<8*R~?>L%6on89mS#q-=kYyySi?;eIGnlC`!@luF>fiCQi(0-X$H)(vh+lYR zyR{R@EI~`>T)JugVo$B-wGEnEt%S8K%Ep77>Ocu0q8vLWtS2axx-;+>pyW2*ly0Ot zD6>a7sq^;gM$kFCXv3jR?y0dG;uTLwzx6*dXZfWY$l>TVSS;zKnP<<8Vv?3+elGQC zwrNr1|4=?pG_+)Fup?Q!@9Q+fB~H3YHntT1on zIa>wD5&blavW=Dj=U>)T9Q)~9M0p@};V&^V|7Ar6Cm%}vb!Sb=HIUm4=nM*=F+01O z<#sPIRKGUGzW6cOZVR84tpIpF*!bTe!LR{B^`Z%1g4A?9P54KkDj^Xm+;0r&f}lX)}t| zW`?F--9ubA_UiFzo3bMNF1=afhUH=F9-@`xX&RB`>FB_#i^ylLBj}|w3cr(3!hKY_ zGkIE(Jzu+lBjkQO7w90^AGWQWuSC{D%W3n-Q>~5F0>f5Q*r#?ybiysP99&+!akz&l z_e}dz8Gy#|t4BJJt5N9%RcOvR1D#5B&81boM10L^bN$l_q-w%?p58Djj}LV2!j^88 z4fCWW-%s}LZT5@Be{k-|0?n&;uU$z-p+%PHitXpjK=@BuRO|S339LZP!d{9)p893< z#r^6YrwMp+>~V>@3TR?_;L=spzf}d24i}oB#`K}C6XE|GA9l#mK7EVo1e~0Y;%x6; z!TG+KWm)KWl`sU%i$2FL!Y5X43cU8W-AURllUVailg;5|`B9W9 z?p^QHQ2&Bi`BZvTM}5FdmVH{W{8WMG-G-_U$NS#D%d-gw7p<=uhoWinSUPVCTlnq5 zZes<#)ipkuSDEE?vpBszXK<7i2rkV=vqj?k>uhLQR;#)aI|TtTDE^9_O+Z-n-yN;U zZ`%cE$;|W|;M!foZ+~Xn6z(m_;0mkd(?oXgPT(P=j9z3pZb@x~ed2}b_g=1=r#V^} zAxEg!T_6e3A&mcYqU=Ujy9v?~OWdw{94~7j3&#-xBq}#rhIm?UL9h{J~3s`2DS>bnMpGKDg0mAwMVDl)+nGTLRFyMo%u(;kP z6`Z-}-Rr7U zD(72BJYUvNT+4W58BxCQZi{d$wz}*OH{ql+Tb}@$Erx4ZrOhWcteV%0p9a!iyw?R0 zP}Kj*_Tl*?xuadpHfj1a8@qBV?nUM!>mjYtUTQm4db8Qm&4cH!UF!4qhE&?_fyU|d zyBn#QY{2n&PGwKknq1MpeMlhbnKz@@8P={Zj^OibZ!_>R=6$U{sX60X@6`^`wBl|l zpLcZq7|Elpe9_ZsXMi&ATHnf$6XI9yh4fh+WG1@)cd)G;)UY_466QrOx zm1o@tD&okzPCB-B`kHUH@7o_CZ3nu8uUDI}ZxT;IdrtIAl86_Kd3cGV3sN11Z|DK# zNZuJJAy^62bgCNpl{y9iozi{Jr}5h_;`AO%$&K417tnSs569u~ z=W=$eEZg!O?3da!be2i_{5>yaFcWC#lr_pLMGW$c;7q3M)eOhUC5CkzyVP$SvG^=h z4ty8_0+7yZy`96`99&K3zYL}G@!)gqZ7GS>hxlVVINujI0L~v&69{2jOs4x z>kS0~$L4`k%3j^#18rO`UcB%9Sps|2WXhs+geZ@^Y)KOdAB0$9Tqn z9ZFzr@k`eK(P>vRY8i2`C(1zCVcYKIu(3{Y?w)XaE@twv`MqY=ZEJ~_b2G3?s2mzj zx@cb4k@6ue4&x6?IXSwS^6Nmcd_T$Z`Q~^j?~C`RpyPief@t&s6a0$lXG?w7?KhPQ z_rR>1Gp^%(NYvh$=xx~cA>-Kvar4Ku2knoC>QT?vg9fAg6rQh#^cH+PQCUB~Aip6* zqQ~@4qwrRhT1SC6Xxw{b`5r_L6=O&(6DOuTkTenrj^Dzb^kdK@7gC&~FQ2H;gI?Si zm*HcadMOS7=#!NOem*0<4(~zcH&Z|1o}D0DGzxuc$8|uf&ON~p0gz>gKW4V&E zaWckn6Sfh2kTCS!(u%ZB$Eg?<%jK*7$IJ)Eca#wyW~hfad{ktpiCH-oeM&_&@$kvD z5H`nq6)XonOUtgomC+Y==L*I#>zW+Zq7MQi#c<6Ltlve60$U%G?#(DGO^R*uVJ-q7$)pD`#l-Vwg zv{cmbBAmCw$o(;u_nw{Hic)iX?%Yi?glNfk=S}aC;7pKy22MuF*u@cXs_#_dviU$8 z#Ab)rVY=b&h$mwaV$iTaxZ}P)GNfzP;9gY0%%M-z`Y8Cv6Q#U1EHYoAuZTd8>686- zqV!)U`~Nyu`q!T3U+4P$qrQfAGGU5c!99q&dw!Y;cqOXLN!qBo4QF_>@O&{b`U>;j zvxd;*%jTy2*d|BnEng9VDRK-QxcN!Ih7pc=<#*2o`VQeLuDvBp1bV(`gDHmg6gYW# z>5#r?4J^c}03i82ZkJE@d{rbuQMAC;yi^E?Y2Z4HV7#>0(&khjzLrtG?wD@FTm2&s zzmn#|L8Lb^pKVUZsO0IGzZ7@$JArIT?tZbu)AxqL`)-%%R^G@@^KFKQ&Mp!Dw4aO7 zO^4C>{}KVt|NOE6Gy*?Q^aG9q?mo_WlJf>BhEnKDlgv`y!-(lEW?xF1H&9&P?#%g% zM0Oc1GwJ$j;#`CU7?C2D*$R;aumMu&ywU{fD>60skbI>4vCI3%TSjEALc$WY*0@d8 zrbVm`um|jV?UNiXN|nWnjb%+Pl#G&PFIN{8%w6SbI5#WcmeQ18KS$hTs?C(2)k(}~ znd0hwfBg2Y^k)bLC}Q7nXG`+;s~!&dLqi~OndEjuh!DO%3~_S%oa zq^s2v?MSX4CFWR{lR4a0`knLH5gch;TV%sP(Aa_mwc-qi*>+#9g$nuS#s zdn}iWiqc-$G!&EG9}rRAHzQ8B>ieUr4~3w2%BiVB!skjMY%!x)&tA7$>{Xi~Vr>i3^fpZDMyy)kBXXx~qC0ZNALLFV#i3n?Ko-h#QzvBQ}j> zGG>!_FvqySfQ-etZF9ziNu*w7C|Ev2m7x3J+FW|`jJM*rx!BX7!A=-|52z8FcARJv zKR4bN>Bn>TIX6`^`?haY@i-p^ax}`fH2Wn?byk(Jg?GJd#fQkJDdJA3{@G}dkM76| zMgLa|H*dr9$8=xA$#~t-yym1w)SYz=7vk`gMQ*CN`JwJHeqQa3th+q0e|YL}`}y0Z z%c?0q8>O=J_ z`6(T810&y+Y(!@4ZR!l}Wg7L`^-N9< z(_y~O`haot;X1&g+zumG|Fzyu$NfMHV$i zl?=f+Ob&4YhKKfduWMVnXPNPE@ntFS`Vq`S-ey5K$P$rxhcLNpCk@kM{E=W(iW=D{ zvN3I%u+htK2M!acmGNVBxU(0XVHcepU3Par(uZzOdp8u3@wQx_ZhjAs$io1UCvtT& zC1c4hKFQ(wh$g9Tum8+QTZDtU3JEXe!)Jfm2&M0QAt-U!1ZB+fc`QsaY5qrv<0MM< z>%s3}6klCoi%2?l0tR9yCE99aJk5$fQCm_x&;DwP|JmcEm9qSoCyY#1l8M}GiC?fA z2%ftUBn*kHt8C=?wQ~lhX7_K@HDKQ?=eAP;zE+1`D;mA5$6e&vetPgshlj3QjYYYf z&UE2PYPLOlWwJeO#;RM?nF6=YRd8HDkO76#C7_zxfi1C1e&QXUNhr2FRTP=-w`lC- z!CY3yk8X+2w>3YUh#C9slh|S!=X*}R%qV6(4EZ`SbWoibd0o`j$x%#xw(}eG%EJ+7FTCet!=zm2FR21t8%yV9M|zco0@0;)LJ{5FjA$s!cIr$S|i zRX44Exm6m(`|h&Ry}nB~G-zhXNjGi)RO5Ixr7bRtPhG}Cc&o^ z7ce;!-_#L511gVXKCFLCRf+jPLT>dzLj4J`2fUxsDeV%zgtBRIHCpnBgWchYjH;%w zsa8*2muO0kDKfIfA~nW&EVa>17oYWszu)UXsUcyLY$Vh-pKT{iX{rNfKsr5iy-y=L zn*ASgPG+SIvz!6Ue##0lR-cN>B)#O55zE7duzlL2u|MgA5T`hsg1dYYQ*apepMLs~ zc&ZYqj83v`-melgdv5Mhx^ZjqC{j5EZM=}w%d#k@{bDR!|3%`}U1#v_3ykV+BYGke zHq*Dsuz1L^-Ia>?4XiUFO`jTTPT6S5(7V&ZhL||rcvzJckzfx*4*f>L)1F;{oeFjV zQ9^96-a)VgkM0N_vqSPbjLo}`P~HB$H@;T}P?QDjjc@qxKN!LMpWb*b^njHH%g6u2 z*I9s7(XDOR0BI0VIs_!8yGu$^Qo1Chy9JbPq#LBWyQP~=H*D$7O-TQP=d1I<`Mn&j z3*ED3#XHZ;UTeMg<0-NKQEjZFit03yRJFp9+{FG&#cHLUrE0Y@Qg01x;VHB{0&^%% zSNoa$5hcMBBxZ{gx7+R2^OdeO*Ru-OMgl|`5Da<3r0;G__GIy-r~7s3)vmQ=*=(^^ zb3saVal&>BIAQ+hS%Uk`@+fHcGNs-kVc^C~)p1?}rD^xfqP)C(zO8zo&)K2Qb+$pS z2gn`JZ_K`Ja_5ZNYM!~0 z>?f?BB%ivU?QWWN6*h9?jm-63T!Z#4ngL|PLXJO31|)OYTrUDcPKQ!HJ~_F2c>!U% z-fQ+*K6zR_e+?7w?&RzU-m40|lsDch0Lxwtd@%fAI5si>q^KMtjOp+>fw?tN%zex4 z83_{SKW`xqH3K*Yhrwoe{6WPbX|r@o=Wh z-4`s9o7`q2BCBTbW2a-9mk0K~?xZ$y2ai<|R)H#rE+8wb?~)8`Z71C|P((Z@StkzU4$oi^jL2nAu}LZ)Qi^Qru0!oKj~g=T#QG2eTq8EuO~v zN-ScEMYK8I+bto#LK6;ujZUL*PN1kV8xHHAAt*LNr(q4_r;(;X4f6nGhB4xLh&*>8y+0%At)kDKE;W`L-1X16~s6pH!>u;D~Dw3^%@j5 z^7S8#2p$33&`;ifEdu~tvfick z!{_F3HG<|ee=kki<)OF)=GpSZI{PXjo2kBiTflL=)J<36Rb?=b`cZk##3$KXSKdLD z$-bJjtM$8q<6;Vezv7{v(?gB%>db$a#ASFsWJ#(7K{Xy!WwNp%<$D|ZQV85CO1-5AM<^MerS_#dixL_y_3#X;3Vc|&DFML|_U1w)yk)RnB* z>BlGI?d71Ig&d*AGR4p6V8|8>57}uuuHsrw27VeVE&sr=bL0N56>T{T>djxvmVfgZ zY#;YQ#Aw`6_Uy!|xph|@y(lKSArxplKZ`JH36MyJNn&6=p-o`LyjFhq031T2jUBhakDSOI@CX8esRagR#~Nie#J_VAzsisljG38w+(+U zlb%GKG^fPYE7)}n7&~dX_B^KrmR#p-+{2X-01T1C@gkLN?TEIom%{=v!P&iGiW99b zl(c$PRGWjejLM|WD0%pSG82dL4i8{Q?IylRR+G>(CB&%N7M?bh(;8&adQSCMs1ug; z?*2rt4d4@sR1Ra2-Oo#MEq)*M$J*-Ox%vGLV=aqhr~W>Rq}Koug1y~ld^%DsW{<(AG8^DL z<=(& z&ec~hSt*~`q35ye@pD;S;fZ#)>Kv<4Oxb$_(yH&znIU>CoFrUpo6V#vndl&u$Kvl` zrK;{@D?}C0tM){fHnd@^L2}Vpab>_==OwzFhJQ)2a@dJFczUTo_x=#KZWeQ9i8>aQ z)34ORrD-5EcJyQ1HRj^Ao^2)75#ymXDmoexXq81DNMzrt*WJM?7yN5M;7`q^uymi3 zRe8P2&vsQuYi-c8_71ZkeXviI-e9fPPgViExKxJ~EF7ujnDh_|Jh79kD=fIL3gF=-u-p^*pKlfX7t+JZ!v!h?|J+mXcic;U z4HbVaoM>-#^wo#MKjTqgTyC>_(ingUaB1cLrvCeWwqLk`c@L?AJdiJ~jrLiA>Rm^X zIikJU@5o=P0}{5yFXNMghq3l(_=KW1XgHJSNmPmc+!gbu9Ni^rQ*peo8tJ;r^Cl^a z-M$#bQvP78E%)8c&w&5XLo~Z3Jv|&i_Y4H>Kwxd!64h>= zP#a{Ou1H9?wN700u8k^AwS>y#WAn%4huTqcm;*KJhc0Ap_(%6`MP8i5E97tJsER^;J$|JK zsBx!T*M*^7{d$tpJyFMA{46#=h@ur?Z@d+Zxd8u`_t03u+_sHBk4Xa4{Ci~RMf+Sd zCn_IowyWAl3nO?Z()sA@fKFYUZvaXL%xvMS`#>^(=H^CDgIxCBzRXk zLUSQCW0ypxkq)>2N#=j3h&%NN5aEbVfu=ZYQM~c^RFYq6;I+A9 zn*YYSzuEtiQ;GuuuXJk(EZAerP;rdh7_=|HNf^Mhx5G3@+gWsJ z_w(2-4|DXwRHyCa)#&1Txg3LggCmYOut<(Ds2O9+di$1JT^m$c4rychQ0=i5f>-nD z3VNNvx5mZ`&M5aWNU=INgi$(MW5z1WQ1Wew$hW0Fi#o#~Dvo)j@C3XXSTZQW9^Xenoi=jYy?JyQIb zDsHMaAtFC~D)PXf07_c)sbHU`&NBJUf0z#icgzQ;MTP1$T;{0iaA-yJ)VsHmseLFG zi~kX6s{wTm;&}@jr0^@=^6A##tBz@sKHcmCvW^O^`!9*8piXEZ$8-p=+wUlSd7&!?r%Bb46^)07*r z-9nOmQYXInf<%U=#DMoDXge@lx}n zdhq-5I}UB=i&vd~1^!=@>|N}CKq>m<{THQ91AG83aa5QPEnKZD+L&J@0n4{jIYW~W z#tDDv@R|N*lRZ5qLG>k2;AMj_C@(1Zp^(Pf8;ksTFfNjvg=)8@p%G0tNKaKip$w&L z8jJ6=&zxQgmdfa>o`O<5!X5^KJJ@w08-?cD4DOy5vTVidxLI&h?Wbcr&SP`7l4v5e zFb8^v?5%LY!2x~{0pb9*W3AN#TG(hfoQjL~XK5Gt7^XoHb zg6}U~PBkxfSDUX3zA~DA_Hq{(Z~ed`1_xtjA6_6m!ba;FkK0XvMA@L7tqi8`aE zIe_i}BY~k%1{*ymqQ^ZijX(8|e+lV^u5%~7LpOMm+K!%?qEgr5hauQ5lK;j#sJu@S zE?Kb`q8;k^_TFSgu*8e@5_DHILoI*YcYa`L3?-U6M-MnD(;^^;l(#CYiPahtfI4(Y z()gDm>r>}Mc!^WUFv)K|DtJ2`A14kEELfQuP_aDU7*$9^gHjeePlyvo2M7;V22?6v zNUI};C54|;r&5C?NzqHZta+HC~Tl z|JqQu&V7FzMrlJUQsY;{{OAX@U+&rRe^a$yX1q)jzQ~lp76+0Sgp-Y`;ZhRx>DBb%(``RM>%s-R^BGH9sf^9W} z8MF)Ox><3_WN%#Z%xH@qhcd|T_9a6~xTXM7NfGcTuc&i51-WVRa!;rf_Zhl?Tew#; z`qW}C5-a-RS+N25xwDln^qW3}r-sB09Hj@N$KYF>uAYST&(oaJBTxNaCBx3Xp5_VE zHMPJ82c)znq5ojpravl#LQW6H+9&Q(?bsm$qrZHIs1`@s}-u3F>->Z!lrj&VtTcTSQGJqB?j*5#*0vww=sW zCYcnGSiM#~g@)56d>d{jD_7MD+Aumm`E~grg z=#ImP*Y9>xUP*T?p?>7l^lgDQhEj-wN^XW;B>dXb)O#?kIBIoexL?28g6*v{m5M3S z2<43YQ{SGfMJ@XdL8q2v(Rg{R-MK`eOvh8G8{+&V_b1H5V zD@b!m=qnsdBHUMV0?6fm=z}~#`vj2W9nOaIoP%Pe?&7RM43Fp)7O)rz@+~Im#6crtrHs&$M8Gb z;G?x}pA&ww#K-KMaflb`*~6bcA&#YH2O!V>sTAltR)E16lk0wd3Eui_#q7h`TMv==^LR-|q}z+v2cE z^FT(jn;%*YOKWK!3tR@hVk>27|HltQxorbWJe_dni2ityslLAL?-#HVjpPSdF*+3< z{5LPejj0?>rfWB+Y$o6k=ep-jr@^o1N_Fta;Gh~t4|^+Fb+n!O8Mx-dn$Z~_J>QY> z(%YT^KVk{AEpSC6@DvVPBwVz?gkdX|dY6GGP6wS#9-ldBP?$RwJA37JRrSJF*M&0+ zNmc1bD!{yx{OGtj9XiVLhQ0+EA-`CP%20TDgl&LjeIa-4sSg`j0i&t3J{8Xb{z-Y!pru-P>Rg;S-d`*ApW(_nC-8mxXv;nbUe3Yz&cTa z&D3So&7^Fj3q;ffObtVQRu(>@(DCwx-YBqf;2~3&D>pfqL2fwpW*Pdz`TYFg?Q9La zJ;^t*ucbM=eFb8qM4qDFQq8@^9u5s}S(%9U5nK`9-4qJyFBJ~nUI37}-Sp z;3Um$(+o1WDOJf}ev#*KJJc#WzDMEE(r2U~h3Wd^u=8j*bTVxz>IY1E2FPyMY8(wi zz^n_>R5IRsTO=#HtCRF5#~U71X{yWEWLh*QhRdxVgQxuIkPSmt-nYsCLd#?T64jZ$eWyR;LrNAeQ6({{p*-A7x0sayZ!O=`3qT(k5Bpg z@jTI)+}VQsa+}{~|3toN8{j^u9DU-ytTqh_x7}e4n&VrXP#3In4yf{5sf0k9rOVDl z^>&*$eJk>n$!^2?*-B;n8Nux&PdcQyftoB(EOs-oNcH3DH#@EYdj#6{rW2nE7XE}y zTo*#XCN+;>MH0y%OKS z@jWzh*Y{r#n(@1fhx~BxxafdWdBlT4Ow+|nJC!2!<+_QCTXxTt$VSzDE*t^<8Q~j>ej7N(T z8Cq_KhoN(kITVQBTa}suK8--=uC?rvsnLC0wz{rPzS1<<4dMKapM!=eUq3~Z>Fcsq zq5nEz5Xd?@R+^|Tc|m1s!nbaMZ-itRYc1XTRWNG%YC$7XhXZ%FoU@5w$(V1p3v^+( zPnXm!2_Ce{HLP*4zzjq!b{IPRytK>X!pJz}5W2!C@`0AWgL?mz14lo1A<4BUfvl{U zQPfB&2e(Cpkkw})?lO8uLXMjs?GTb=Wo0`m(rf1^YAd2M+&xItfe3zA<9b#^r z7r6snrb1fAdKRzo5vm9$X(V50Raf;L3Yw_ZNe(Msgi_Z>z@RKcUu5j%M3yNGfW z?VIP*A3kv+SFWn+ThuqNJ?`Wr<g}*K zW@JP_vl)|+u1@;pa{+z5IZ6&kbW|PTr{vL63QEPYr0gD3Q=;|ovOr*JI{&3nxb0x) z5|1U#NrGh@`1W1XrAE2$y0`Q%r+snk*{vB8m5Hrj-kIwpzf~^OUI${KJt40-e`;&; z@a`nBL3?7hKx2m4cqAuNT&sZ=^pJHm&WPousOWHNxXqbTelLf&74O1_y4c*e1?z7V zXP>!?rhU_YhvPv@H z4~)s|YataS5W1KM|hV{^M3Pqp6;M3X6}vt5Q6?-vCk%S^uDB*mI`TO3<4zDWm019 zXJ?<@BOAM#hGY@6slWtk$7%J8rFe)uKkOSrv)#s(?eji=%kwKjLxB0d0bW*Yt_8Qf z_Kt;(0Zl8uJqha6b)&4Aw01u4%?fi){sq@e&jniGEP+IjD&(WpA2;mr7^H$w`z z#x2U%CBoQ@==DX-CYs~;;R!*+z^c7tP!@1xZz0! z(f!cl>9k@K$h59b$ZO&FTcpOHE&^Adzg7B|NQKXrQ*@qA92WlSaoD8@YO_1i^KHT^ znk?G&JEXNPP;9VEY(Enc4i{MxJ1#t)UwU+e=VGa7yOW$=)2u!yT^jB|_0;}lpNWe3$cy$J%`HcY zBBWPA9;z*iFu~7dc!)Z8R>ieqGqN6Dy@d_T`w@)#=nhwG8Iq zEH{3i|J?Nl+m9N-tP=h7OS82^C;K0-i;!YVll1l>n=-``5``yP?>F?x&vPrO^DB$O zE1Rz%pqTY?t=TrKYWGI`pg~{H^4;yW8>_R6Yx@JfTg}^#tn6ILti_=T>(|?-_6L)v z?7!A0n{Rjf_OBO1mqT|?ZAC~M2rOXuq_eBfJ30;*yH53OxWUb4T1I`NHwR@L!+a=U zx)2NB-8I?$D&BTUZ3S7IHj{tXs@1!>>bC@+FG6-(T6rQ@1Z1EfQ}n)@ub*L)T?e_ zUfhy8F|dxtK27Gq70e814c* ze|jc+buF2+4^Y_%jo%(Sa$(7#cI`q=1zaKJNY{W5!W=Ij7+L82+(#3JhbQ@^d=(ZK6sJZN1{kp?*zzA~Er*nH5R78JbWPxkf z7_vrES2cbpQv}NWF&aAGMtHl>yoq%k+OhCr|J)p%^AaVmflKk#uC)B#p3OT1pFJ)!#jxc<6kbJ%y_6fU zO)r<|F=gaD)w9hgBZ6!;a&vg+dCfkq6eadfPmK^nX2i^uCX)M7p_?W?qW&sfw))r( zH)AV^>Y9L3DBzoJ1{9@m?>Fm=C%Hm--;A?N#E2@NC5Z!H3=~Rwqm4Aja#iW2X12Uv zyef>Hajq4q6NL{W&cLD+5D#O_z{};Q3Zu%1hkJw0Qk4Sof>(+gYK(6Bgee(gM)vJV zMilAzHglyOqrEX4Or_nyLl-1-b<*ng3Av!ysmqabRaP65uJ+6yG_p%z#eFMfYO|HZ z)LcPyY-DDi$VyGmkS-0M6skHMo6DViI9?fH7=q>`@=$#s1wIzmen&cPiX}L1@2#W^ z5|KZ*FqFZ>+sd4m1xcx1CqIt_-V6ZE_&8C9)>6C$b{4BeEfK zNVQD0O|?#C{q7PgU6>Kpxn7>>AyrkAphdqKR%b30Bla~jtl&#}f_bzO*+w0Wky(aW z%Dx7yhM&5hhM2mTMz?ym28B9>Mvi(8(nfxM#3L4gYg;?n7U?IkLQlRukw}6HO2V+z z_t1M1;VzDLmsto&pqQ11;FOhHUyOkGSgOgT(OOlwSdOmR#qOykR4?Oz4XbD$58@eAc% z;X>XxwB`jU2$Z#08C3RVWt1kL)FnN$AQ=9ZB^5VoGL>V(098HK%OeJh_O<;LoEUug z*Ogb$VsLU_$CNC`3NOY9;8toaxwus~&?K8`3{{+|wQ+k6pV+=;-BKmY%@u7}F2SAx zlX#1v$^}Kf`bUJ$C*l?881G$dN{iT`)g5(1l~$&w>Z4Dp{+M|8+?JU&FXGkZV@3gf zB22J|uo#1!MC?f1!6@pI~!|y0b}vYbEr7D65}TcRkrh$_auPVMQkEN|& z;)eytSFSq^a0M*V|Gq_l`UXT2t%^a*`702OOR`HUCGm$s#i<-@h@74dQd^}4Dwrkj zXw5kV!^)v2gpCeEH)s@N9bN2VaZ=iFm!CSTKtgm)eP0m&yCy8?A?FH^F_I+vGKo#o zZCo{%Coy#j5wal&Q)9ZW^=@0zuBKxJh3X~?vO(A11T$bMMZ~|!Wgt?DQhgK8fX_8( zB~={fJ`wvCH=lPgLFOk#21_%PBJ^(iHLMGDxgohg6@A>R@81F~((m0}JrS>1Qm#LT z>SI3Qgwu_H6GTuL&2p|n9U$c0D3u&5XnZo!kdEg!Tc!%nKqVLuhL-Vy@@;Pzdjw5qA`dFf#p-9DhMIn7%vRTGh#Y%x2fJv zxmud2y^Pnl!-3wHY2Q_fM-)#XKy+_3fj)dbesXmVwfRXgO2`8T9qvf^E(ng5S;fh^ z!zG_TG=#}eC#sq6$v;%vF$$P63VeFJFF}6cNzf-U(C%EFS&9|;K*$Yvd0N}mIgh=n z=cJEldt#{GI3%2h>a_kW&1PQ=Dwm+I(G2(C)XH#oVz<;ZGH1v{;v<8g&_hBZ>bGTk zB9^U@|J|0ACz`fJuUKXDK>3Hr$(koebZ}Y=0%lbeGX{xJ#&gR1JugFWqB;sxoz0I? zpkZ)0q$e6rRIw-ys@x9Eiziwm^BY`QD0(;4WA7@*u+Dn{bM!iAlFHjT?>Sm~{)^uY z&%gQX9-^Nv^io4D-cNwzq26Mr$c}E5K{xyW^C^TXo^qYwr(jI3h))@V4Y>+&H&}l< zRL*K2Twj*y7hhM!jQY{>!HU0a*9^q@s44k8Upm-d7zuEJ+5(-$K$}G>uWPBrcN3*? zAU`y`5r%$KCbFKKP@}}XP^CRksn87;=KW4I>z5V!s~*&& z>4}WVBcBi@ICO-gV1MZI&KbMBFH#!1gvaUzI$mz@(-T~8pjn(HM z`B~4{-in9iCxX1NQHg`B!FhPl9V6Hu{E>|;Iom+~_qo?l7jTZVcGwBMDh%&G^*t1) z=R}sp`p73P(yrS=dN~YXshvVTQ=HfyMSt-FS zB8{`a`}OSN4Hi0PyK)7h4_()*4}Vm%51&(2s$Q?7TmpA;sf%Aqy*`q#B-h~LdN45E zXK7f&Bt};^=%GiQbdXW<$j8RFqV?EIEYIVJSmTIHk`Q&)_0u`A87F#x8#z=;=-v}D zg8Xe`G;P+Yf1t^sOJS#;54$unU4b3b%8g~L#I|1d&h*`xXFGbZX}UrG{ct?e%bA>k zmQs~mACFt8JFAVSICsWcD6jnPZ(=C=#^mEk!s~I2WaQ)YnvpUV4>yzt) zOx`A?@;2-3A5*)CLpum0xTBq^A(lte>A56=x#j7tRQs zn-gkIe4BoY5bvsgDmfSHRx&18Vwzat66#G5$c^DbeW<)u+W)b(3;xDjcSGK};7EDE zeifVYRCVn;TxgMLOwzj1JoE*bI5*7C((k3LEN@s3jk7B&^I3Nue!*R}&zC8FiI7)M z_BN@jYtB1|EKh@8%bEI9L6S(}luwypoJ^x}7x@{hKr3oT zqz<;B?U~TC2P~wFbMx;4SGWICri&YJsW?$IOev@(WSKveISS;lDlk~R%m%QFc0#)IZoGnmLVahN9Gyq1_#c}5|2mU zu)3Xj^2d6-QH*(yn)BMkAq+5^M(}#&WcCNh%K@ze`A-2nS}xUdqZlkH5LcProZAAa z&4S^0ul3pYM#cu4vyAC9b%$2X>&HG-j>TJjL`jc%ay(r-BW1y;Ll#7vy@wX5y~_g| z_?i6G(1Y0pS&*6aV>Cx+DK@vRSWV=88`YZF{Bwpqfj&$d7KzhHVYM-J?CK%8PW6FU zD+HQP{o@V5uVuim$yNwFKHL@-gq8auMhO$k5_P6 zXntDMFIqcu9ILou{80XRPB4((hGnvgrs4(D2PU^UK?KIgPmQZ-X4TDZPP3oE1_^Cv z^V^3zPM1PvzYY>F4m_Pwnr<%NHToythQX} ziXC7^TKi774ld2lKDyafxc+K#YXZAXT`4z<^-KiyK%-7 z5T@Ysfccv6kZJn0z@wdO+ml_sc4bb#&KumOk%AkFQuZ9mZfExa@5ijlb>B1Qv(L8| zT_D0+$0_dHZ)CiDygYk1d$>H@j&8BpU`^KIvy_yTn%nrqIlkDcORXV*Oy9O`P=8)` z`Dy?kiL3Wx6L%eVcr>r;)e(j0cj?QP?ThX4#S^`Sq+~n-*Ndr?@3-7Vw{x7gd{bs_ zFSd*8S1&f-wab?Za~wF8pTB)KO83FHYh=Uzy29=K`qEELw<}eYqpcN5^((L&*U_z$ zoplkbEwg#gb@TL=*}gi&`QY5GUei4JX8o{luU?(l+WWJLd*khSh0RYrulY65_{HJu zS(2E3{&jA=`#G121z|h=bRUX*<*x5QgsIpKkE5FcW^&^GWpfd-p|!Fzmq%57^LTB_ z2E6C7ki=?snC9o2SgB9|N58q)vVLOkaG6u(=6G;mQ$Nd{fWMF81nm0%*>k;jGukyd zkOE`9np=2X^I`H(k2fqL|J2?7MgV8d)&29YWvv9l#;jZYQtbFpyje+Z$M?G$`Oh+Y zS8FRQ?P^}$Y>)Qzbfs%q7ZFx`(hV2Bzu?o%797}J)4OOYCyO>QnQ+CUkKJy+TGlWGVU-nHbLNoJlCi*@a z+#Qgf6e!B2WH_kFNBgA*Gn5MK(eoEX6mQ>qe@7NT_h5yZe{{jowVhpd>e&1nNH5*f z`kjgEKR|kj>*l3Blb@bg>6=i?OA{gi zg&Wa}FCY?w!(VCEu*46CKQ9pc=JN_cfWU(oN*^}w`54hB!6BdTxQ}RR=eqt0qklud zRSoSqGVm&VZ|68%8?o{KJv(O6c+Jh7ZF9W$g@YVB0*Tgm23l>Kf@LjTS9F%^FFa}9 zEXO7Lcq8`9Pf_aTA%QJ>b0KNNBHv#AbE4-Jk{H5>QR_)VeCb)qn9#KS*TxMsEIej% zGi(60jP5|n2uJ=p{YY>ZQc|uGb@Fb1=8&DTDCghtQg`^7C^dC0l!qI))p#7K>!lD_ za3gMbx`u;+6U5Z7EVAl?Q8ZoH>zu{5>HI9RO!vPj*KEmj&RK$64#o6iwqv0IMTR~q z?$POkCz9I=6DMc#@wuDDox>nowW@P2wc)9n2g0>R`U9kgAQ|cF`O1ifI8>7MFk3EX zGMA!vYBY&l>BQ^U3^e%#at0~sI7re0p*tyde^PZ8O+s2N$wRTG{xQ$W48_+@3?R~n z0Yv&IF|PT45a}tWPGAi9=Hr{k5EXzD>dP7*EFnxd2RnbjjEyizX1IJ?%0rO&#l>z({9npAa)yDC9Si4fyrNIA@;hW z7v&lKW$+#8xz}`ffmrOGi3D-jSNMML{`B)s8QK-7;B}r@Q;a+kN*mfaNQgElV5vNX zHl?mlYS7KI7hP(Q;jS$DA@TjH3VnrptpE4Tk;mbh=r7ls!HYz1zT;JqO+Wd(!Sd_+dEQm5H*7x4_B5Br+j{vo8I#xMj)D4 zP35RUN(mn@DIj@j_v~!LkkiC!v5325xLFcPOF-y(Bn%~$C=E9=1N_MoBLHH*LdG0) zQ_W1?ut6K!h`Ja?)og@+4XwDcN!qc=uX0?%Heu%h55m6Dc2&CyS9r+FLPLJaHwOQba$)Y-H(hB2^InEzT}#e@WkcQ8`u@i| zCjGSWae1n%tP1FhQdOZ<)VqHJnA^MgrSM#Wf;q#i76;!&e8;4|U>#Q-)3EnyNg)Z4 zad(zq==b?4Q_To@;*Vh4mr5h668KfAipGRN0bB9EO+kb)GF>wDvdH!qGloUhGV}m% z0mICsqX7$EY>^(1%ySDc*vh<+;!LgU0R1;wQg=;)`Wy_M)EUVA+RCSX^ zT+QCp>mED2dwI=;RAo#&ZQpRg6RAb2u_Nb5`m?2p#b!<#B!%+BrFjgy&aZ4mCJU8- zb5Nd0sJ{~674z>1h{-H(3}d5I?L#>EVcSa}SVz&|ZZtSl{?EI%u)DP3UhVpjRs)5ueH-9f!#j?9J(Xlb&P0U|ovAlKf#eiaj>r8ww z$Gb^2$@y;h0db2n0LC90M)9Z0FM96^WoMbDeb_>F~98EbSguk;v?Y45b$p+hcF7uV$(qQstbxCwI99~Gr?5*0$|sM z94LmkYuQ?@YF}0DmcU9LhHY*DM*Ueunc1|;^Sq$E|F-xcrc2w8N|z;y-%DC!rUulI zm9dMdjT=OdU>vnSE&S_8+;6P6-#x51@Jw)7RUhN}d2mPXFKZuDOuyX!C{W0G0@2f< z1#&tJ+sHDP*jeSG9=vzf(>w~@DmMo_7Ht3oeG<5}`jto+COwp=wyuI^5jq=G<<=PJ zhqp|wN)(GTJ3;QJ;0GG-=N})@h{n>W>iu%?H9O-vQfAmRE)GDTx4dqPk?NUli}; zx~&nuKIe->A-hZ?Oj=t#N_wx84{>XFn2MSOGZ(~sU(4qVwV>C&=uT%f zxSwPe{M&h9Osmh$cjk>Jaih~gzm&LCoBDK48NfWaW5|nqWQ$KaXTxnxthiGTjR@d@ z^?>kPAb@Yx=QOcjyyQB$?sqwQeU%s}+jF+kf37A15be1z=Reu6&5JZ3+Kl{mlzHH# zjNK|1?)DntMw8~_a-2g7^A%Z3PTAxYBhZp*hGP*X?2dUczS%dPX%mZiDNdsU%>CbksMh2BU?oHhx`uEEUZ+{|ATUKK>0eHlm`GEU zrQJ7SSR%(5H*VcFymlxU@6O=Gx$5*yS&egsjQ?yxvKpyBVMGGM<0=kPW9syDC#p!7 z+qg#~Kw^~ilkntT%MG9MKF~eH;r3Y+{86D#erGOG?)xCcTCV3D1o@Zlt0J<2Nf>dc615%f#m8%aRpo zaRusjG&sd|Dq`JtRHRh2y8d2;HZSo@g*+x}1HzPviFVY5#Y-WqNm>#MUq}NZHb0}% zsEGG4s#?>`%A%5}Mfkb9p@TiAd#+T?ZG=ww#W9A%Gb5?fNhiw@IFe(t5hzuJB%mnIo5jM481?g;NglU2|l5As!$WOO`SP-SIKA* z{FiS(ex|e+>5A%6zggnXaPI{-`-M<4W*n!x(5KBwt^>&*7Sej`vb%)SR+pn)glFK1 z)$8r*u9)4Hh%0QQ69)=CC3pOr_rkaC=TC8YqAYJA2ITH+tyiZSH=lY`o5b~iCja>wm{el_-;+u#e^4FdmwUs#!Q{F(PZ!UgMw`t6JM?wb zo^8AN+YEBo&u1k}eZk(@YdmXCq0TL%t%ZNq%slWRKTnTloK(K-*9E9KIc3moe*ro4 zGA2R5mKQH^SF`GJKa`Ho{MqfzK%(@y6*Ae0y>Nx}GKU zO{t$h3{rLEm_u|U2rVuT3~W1H-*d8I2RH9&=B()4o;Gld6kG7oA{SfK51bW^Kt?-0 zu_tPBTM%eHb44hx233^0J37woEnS~ZLiSdt*p8}PX7DzDq3{uEq0G%dPMHRHEEaBl zom_;Ui>y4!De>)z!ih@DvL)LGqv zG#2ct#w~zdLca6Xx5s>;;84EJi|^*m`vk1b$sx!Y6D+1@?nMrvd~O8qI5W#LrDt|7 z!H0V#q}3bGL85v4yEp!vY3?Av+E7uH+yyc@pe>p5?kpYo*RN{M!}%7RO(BU*a*5)> z;?DF%b{q}l!KPj0u6oh&%b%u~+m>^DkgpDewfohVKM&VxYOgP znfwET{byo1Dl%ET8Q;Da+8gq^%7Xe&m_f9K=zh9FZ-U1F)2klG*CKF%J6H`5U(+t; z%$sX?nAqC{z|W(I0*nWySm$g-X@Kz{U!Vbf#ne28wlz>kvQ|&_M6^W^Zvyk|0`cuFvTeK*AXEyrildfo6sgmN$*v&}%k>nf8Klt$cRIgaRJrm1^+glrVcJ;`_`#8IM zl4b%p8>ScPf9dEab|8Gxhnj!Ik83*8#lXA`ZY=DcDEtTZ|IdU_o2FM7N_|~$$tMDp zI6elqLzS<1J|>+o7$mJ@Q!5u7@kB^OD^8bY)>p6=$wvKUP4H)24x9^u6!@<sEpJf;g_hZ$MV*sybL}Sb239$=7<_5O}|w&*TYU$@`lC|7VIn$y{ds$qE$cqJ+E7 z-T{r+RKr#(@9R3PBIA&?ka2CG*Bgcy2TI97ie=~Q(5_vhC(m%hSg?lTxhhl=|Mq+M zGr1ntA=X%xt3?|>G@`|xSl>WqpvdVy_&kF#~_8VYbTKHLMtRFZX3HyS;{}j@I~wAl3zwqgb02{zd~lq;%* zzt=fm=h^oBwGl8_z_Cv(T=V`ZU~IUW?BI7&K+*7P)7oxy7H&mINy1^(pX|Btu~b+$ ztsCGnPpR}bzWLA56}dKZJJS@%NV@{z&aFd5-u24bLWKy115emK30)>ipq7@+@$HUS zF!1H+Q=v;nRe|AmaoMPU8ESLM+3~n9luH+OCM?J4gc#mY54b%$0Bb`~mgTZ4KJz?O zg~8mcv%&5^z_fpc2sSh|7iCMDv;&)PT{MFY&07o`4N!2Hq&~nZ07dmwg7W(5-#(~$ zI_lpCf5Np%8Zs2k@%YBhV`xNBQ5=|;2izGP7P=m-+4rxK(5cX%Cjn{t4jC~^KH#L6 zDxN2!nD(J{B8q?G^8X~|c;U*~J&~bT&gSmcEk5Pyt?TM^^j-UdG@$<@{CtM>DWeg;PQS8{vZHcCm zfNqR|&dQTjSx$?Pm9H+^UIBiWLo=-UOT72l96x-HGxUN~BkJPDDud!oumS#pbF|Lf zsnEQt&*z zE^}mRiXwI>>O-O@QbUQK5*fNVX1uMNqNIhj#;uH@4TuEwezBV>iUErn)SUY;mRsge7+x5rXPb0NE?vq~1 zBN(Ya4wlrk{T7GV?Y!;v>nWN14T&fvTbj3gfK%BC}DPScOF2V@pXP?B8Gd^pZtBbIYi$Hvud@hU&P)az{ze6(uzdj5rf0WhGI4=Uc* z7AHAPk=>*5iLvs#SYVoD9N_M=MA`u?{?bzsAD>t`CdUC^c=_R!BvJMKtc9S{{#FNW zF3MrX34g%q@VC+y<@;R0m}qN3Mo=CTann!G;$65y419HL@kh zZxKk>tZt~V06}^F#9D2kmsCWd-z+z7$y2z*!eoh)2d9jRVg<}i(iAK zK+)F%G$-jp*c>GdX9t&6F10;NhUnMT-uX-? zU^6hW)HV|WmW!E~i){&(0Lvk3lzDJh?V-A0HRONWh8V?5)9@^h1#bf}z%;-OvP9ic zY-Mt!3k0Ug9~GVq%c&U@``;s&y*Sv(@ahxdTRiGGKTXjoGAyuRqm4gL?(yZfGihZy zf1U%(nOv;ldrsv1ZAbIBj3p;UTFIs=Rl_=?)-+&$QrPPYt;y0a9iIMf(y=2a8rH?; z{C;iGUU+!Tv(E6Bi!Cot44wRnQ4tY zR9{Pufwvut95R@!fmDg!m`f2amw-BPUK61B>hnPPIIjs&PsapMA0^l~USe|#j&j#5 zr7zUrXSk%M5uiqI*sZC&$xX8Kn^4fUSZX_bmyG;(h-5chkls|z{}5gmH-$56d_fuC~@UY{189XLmKh!I*lOz<@b z{j>a*Nqj6JX|J-6@2+RjwJqbIsXCH<_uZWgPrJQYd-PG(l-`|PamPBE*T%y)Q@YV~ z1vB9*_9SziEB4fL+XHSMIKu9XakLU7-sB-9?Q(f#IvcF^eo``N@2q=x9rce}{*dj&mjnC5FAnF@LUdw>k!7`Uk4eELBnwBrG8l!J@HAwu<8avjUaUD9Mi zp5Sfwsz&@lRq}HCR|c`=)*oOs znU>>ba-olvSOvqdJ^it8s!WA(Tr0vS$wyOjn}#KuFS3s@nOCi-pGP#AU5(B`1~c@E z?tgdtIy@LBwj{C?Xen9Y+ytjfZn0M^QRwv+p&k({Y{KiX3MnDl{UErwS#XKWu{QiMx6$s5b9+2X4K0U=wq_4U_`BwpRuo{KMq>=;@w$q_AI*uf_&I=Z;{oKY>Ed|_>0%bJqL_1C%MT~vb(8MO@GO?~Z*v2sRRBF8hF z$YC4opfK}gn+AmnYNDzDQ>d9B^s{xbr2QoWVw|Ee#|@`V{4Wn?C~{C+TQhAR397Au z*Y)_6X{AXqj>j*9#lM%2Fzh+t6iE|8#>Os=Uue`WUD=@Id$3(jF-5%ZjMk$mf<_Zh ztUpof(qq|K&<{&Rc@cpKGnJydNY)^!8J9~$WaDnb=dQ#mV24)7Q-6f63)U2m_#b^) z%4c05K|G4lHu!?g;`K`Di}N4Jv02NN#{Tzo0wnH>-nz}q%x!Q_F@mN-qrA_q`fveV z=eyVXW33|1xbAzc^)_GX+IPktN@KFPnp~`9hlR&FQq}t(cN2}kM`VZ(8CS?#&q?S@ z^1e-F`Jn<}+V;VR^lNWPsK6_D z*D7VGaxpz=$eGhG!;4(e-LP!$x)={em8vX-W@%-(ibU*Q=0>~A5 zyjko&ky%B7e_V71O^#A+-omAwt<@)>_eZV9<>AH{Yga;^-g=h!(OGSYfHNLzQg~hh zG8RAXqdXLVELe5*uBDW$NIC>Vww9X3QenmeTi$PdU)Wyi=RhM()7N|SkHUA3UTz#N zDR@rn*81cYzNZdE>u2OscviVJ++>YQ4HIl>Gb|Vr?NWZjOP<#qIIaN`cIw2X@7(b7 zGniKza&}>m#MyAuc;G{4}ubFRt2ZT2mA@q<={%-c4DaW^yQ z4=3h47oO3lP4sf)%P6Cbn)1aVnCpw3ceL0go9O618h+(cF6Qwh*^tI0rJPiw3*m)9 z3b&q52ilUg6mUKo7vex!yO`t-!Ex$FkD9d4Ye>X8b3|ba^~X0<4l8ZJS%&yQ+TbLj z9Hz;5!(CVPokFFuijYznSykcKz^9K1pU?7%f5VAgi+EUZK37J)%8Z|egJz?rEu@S* zMs-}8--z9M(Qx|%ilRPIH{z+J+r^*Wf80)p7B}Q}B{8ahp~v;btiBFe!UgAk9KssZ z6u(Sv8%^p54}Ma*)UoOK#?MMcj=VyV40kpbGJ>b~7W`LZVk%-Yd!kvHzlliY~S47nW@1_TR}WNeDz07CD0tIM@n+NDHUUjp9&f;Ni)zj$%{QHz zvEzc=1On=Tp8Mq~*IuXoF`dY;YSg1IDq}w!&+&OEDAnSrVVOKv`wZ6xH={A-s1c$&M+H1fI3-LGN|Ig1ruMZ<)91^QA1`jHxapB@J+ z!k_8NKN0euE3qUBh9ZpaSLS?4u}eU}dt9$s*`(y!#aA&E78=oXAB=Ec#Oima6l$B) zb^{Mr6&$`@yo8^kFMJ-q%=B_u@JCZ}MAgToLU>`lPaVaN*&MA4v>JE6%Bc@*A?zIL z#@2ss_&k{3O-ty!yzI`%ki}D96E-p{b!LIk+f24B~%;fp2ZIMsUQ*m%p7@*dE?Ano%e^qAm=;|6aN|**iPK7-f{OTZ~pjsh6V- zdIaxzqW7=R=okiPjH&kqW~}V<_N1`ZsCg-KOC8>6Q0yGU@^H)9mKv(t#)s{#stcr% zNraa;hfmh)EJf?^35v-nD21xkx57Wpbn?6{_(Ihl#MZltj?Pl+{~725~S z=L-ZlKhHX3lQX6omlsQzzHW7WzQ8fBTa#SLvLHNLH6nj!T*mCKjj_;6sB4g&ZW3*d zVIp_P_w7>1^}zU$V7Zd&`0S^}?O7}S<_8{PMf)!azA3-SEUzk3&8Lint(vtA5Gv_T zMy^I@r6M>q%S7Jzm22~exA-X|9F%`%kiwf2a;K|bpyPa}A12*)UkK_i@|Xc+D$b~N zes!>fxEXMTQ6RGcg9xxJ9L=FFj!tkMQzxh6|At+CiHpUS4g%is|9eZ0?RQMN0dAdU z-S9>2Pe~857cj})gB0wcY-7?>N`*(RG~^Y>c+0dO?$OCGqAWc}Cw|l-kqm8mAGYk8 z^gqdnCw*Ge>!Q|OIPXwF=-ZuExG8dh&4DrUP4p)mT%R2`PV^ghj|>)|?ElWj2O)mF zOku;W1|mtJoujr8u8n+uGxKGO<%RPp4`hxqv3f*36R^ZkffWOGHZdS^17DHhY{KQi ztsdXN^Pqr`cYVUhg;DXsZwcEw*6C)p>)0M(((I5*MVwiK${2bc@N`rtl1S1K`E@2d zr97&@O(2MW9-4yKV>o*6TW?H+99x9d>Kb}ID@_vh#15tS9v*djWa}Jv#sXAWaZWxt zVx^+twc(_aXZjD~Asq*)M!Q(F;6k#i={UI_M3TJpW>X;%@#ouiGip>Nd6|>4&R_P~ z5#sy9#_zX1c*7md^gA@v<(D`U53$ek{NSoC(N@5z;{e|V1J8hdi@B-2xtz7B4eZ$S zEuuTmH{Jj%?D`*pt%`E>?P9XIoA-3r@B`|?IWRRfcwQSfqnW+xYC&Uq}Tj=%0XwfmqI>Un7k5xiw2gCf1Mz(@vs zU)0w{<((*FLSS>D?;YlaQHlwj_8|5#<~w_Zb8CH{rPeM1J^H7z!T&z+^Zh6A|KAM! z;B_Xk`+)1a0bdT1-vht7ql@K#BLC^t&g+SWYSRt!plQ4#xpvPaD_VG5c>ueoiwH{Y z^L6G5gHmM`(v6)hwTS{(wnlH|o_4q18fGJUOA0T)V%dD67&b*Lw(LsiPoj;mhi|3* z9^Q4D-(j(Q*jVaU&@XtIwfJt%K30p*7@Qj2rPvyLad{5%8)^TlSR8H+h)eqakM*=9}b|0L!VR_*CC^ zd+!BzN9Xq83*8Ja(pYyTC8aqg7Q)J}--fN;dQ7)&^3032A2lmnR`;f`cp5E|7(?$GlJ-I($f{Ff@7&IQd<`>NpCYZsvfL8o>FTfZ2%26>OSm;3lQ= zKc#0*ja`WTZG0jv2M5%~EG-cNXN`fezgCbaKvDbiR;N=+Fi;xw&nEm%;XjLFU0}od zj2r~2b^29E>6Eg=dnV;X?GCd?m|A(Klx9Ut%9+Y}m_SVFxl>^4t^WpiDny3~#1wKn z1-2`l0RJKDhDpE_3_2xb+`(+p$*`ZS31U((>$9FxJWPMJB&1o=`AXSTO;Zsm>`N zF!uz2F~dD`#)}F4`)uhH2}F&25D0VbglUbLah}?T7M?WeZ}}&t^?%(V@1p-nhfXp% nOz7XUfYSznKq1Ayb?C300IG@yL^TkI0{ANg77yr3kN5rs&*4-X literal 48504 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 diff --git a/extras/~$Library speed comparisons.xlsx b/extras/~$Library speed comparisons.xlsx deleted file mode 100644 index 2107d658f7008f8b7bcd710ab0dc7e77c612f2f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 165 zcmWd(C`!yKPs~wp%1A6JNi0gtRUifkG6XObF(fi%F_Z(z90mmjCx#3ls{{xW!7{l( HF={~oWsDYW diff --git a/src/SPIFlash.cpp b/src/SPIFlash.cpp index 1635722..19bd3fd 100644 --- a/src/SPIFlash.cpp +++ b/src/SPIFlash.cpp @@ -67,6 +67,27 @@ SPIFlash::SPIFlash(uint8_t cs) { //Identifies chip and establishes parameters bool SPIFlash::begin(uint32_t flashChipSize) { +#ifdef PRINTNAMECHANGEALERT + if (!Serial) { + Serial.begin(115200); + } + for (uint8_t i = 0; i < 230; i++) { + Serial.print("-"); + } + Serial.println(); + Serial.println("\t\t\t\t\t\t\t\t\t\tImportant Notice"); + for (uint8_t i = 0; i < 230; i++) { + Serial.print("-"); + } + Serial.println(); + Serial.println("\t\t\t\t\tThis version of the library - v3.1.0 will be the last version to ship under the name SPIFlash."); + Serial.println("\t\t\t\tStarting early May - when v3.2.0 is due - this library will be renamed 'SPIMemory' on the Arduino library manager."); + Serial.println("\t\t\t\t\t\t\tPlease refer to the Readme file for further details."); + for (uint8_t i = 0; i < 230; i++) { + Serial.print("-"); + } + Serial.println(); + #endif #ifdef RUNDIAGNOSTIC Serial.println("Chip Diagnostics initiated."); Serial.println(); @@ -183,7 +204,7 @@ uint64_t SPIFlash::getUniqueID(void) { } CHIP_DESELECT - long long _uid; + long long _uid = 0; for (uint8_t i = 0; i < 8; i++) { _uid += _uniqueID[i]; _uid = _uid << 8; @@ -239,7 +260,7 @@ uint8_t SPIFlash::readByte(uint32_t _addr, bool fastRead) { #ifdef RUNDIAGNOSTIC _spifuncruntime = micros(); #endif - uint8_t data; + uint8_t data = 0; _read(_addr, data, sizeof(data), fastRead); #ifdef RUNDIAGNOSTIC _spifuncruntime = micros() - _spifuncruntime; @@ -255,7 +276,7 @@ int8_t SPIFlash::readChar(uint32_t _addr, bool fastRead) { #ifdef RUNDIAGNOSTIC _spifuncruntime = micros(); #endif - int8_t data; + int8_t data = 0; _read(_addr, data, sizeof(data), fastRead); #ifdef RUNDIAGNOSTIC _spifuncruntime = micros() - _spifuncruntime; @@ -679,8 +700,9 @@ bool SPIFlash::eraseSection(uint32_t _addr, uint32_t _sz) { return false; } - // If size of data is > 4KB more than one sector needs to be erased. So the number of erase sessions is determined by the quotient of _sz/KB(4). If the _sz is not perfectly divisible by KB(4), then an additional sector has to be erased. - uint32_t noOfEraseRunsB4Boundary, noOf4KBEraseRuns, EraseFunc, KB64Blocks, KB32Blocks, KB4Blocks, totalBlocks; + // If size of data is > 4KB more than one sector needs to be erased. So the number of erase sessions is determined by the quotient of _sz/KB(4). If the _sz is not perfectly divisible by KB(4), then an additional sector has to be erased. + uint32_t noOfEraseRunsB4Boundary = 0; + uint32_t noOf4KBEraseRuns, KB64Blocks, KB32Blocks, KB4Blocks, totalBlocks; if (_sz/KB(4)) { noOf4KBEraseRuns = _sz/KB(4); @@ -732,7 +754,7 @@ bool SPIFlash::eraseSection(uint32_t _addr, uint32_t _sz) { //Serial.print("_eraseFuncOrder: 0x"); //Serial.println(_eraseFuncOrder[j], HEX); - uint16_t _timeFactor; + uint16_t _timeFactor = 0; switch (_eraseFuncOrder[j]) { case BLOCK64ERASE: _timeFactor = 1200; diff --git a/src/SPIFlash.h b/src/SPIFlash.h index bbc3bdd..447ad37 100644 --- a/src/SPIFlash.h +++ b/src/SPIFlash.h @@ -61,6 +61,8 @@ //#define ENABLEZERODMA // //#define ZERO_SPISERCOM SERCOM4 // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// +#define PRINTNAMECHANGEALERT + #include #include "defines.h" #include @@ -270,7 +272,7 @@ class SPIFlash { uint8_t _readStat2(void); uint8_t _readStat3(void); template bool _write(uint32_t _addr, const T& value, uint32_t _sz, bool errorCheck, uint8_t _dataType); - template bool _read(uint32_t _addr, T& value, uint32_t _sz, bool fastRead = false); + template bool _read(uint32_t _addr, T& value, uint32_t _sz, bool fastRead = false, uint8_t _dataType = 0x00); //template bool _writeErrorCheck(uint32_t _addr, const T& value); template bool _writeErrorCheck(uint32_t _addr, const T& value, uint32_t _sz, uint8_t _dataType = 0x00); //-------------------------------- Private variables ----------------------------------// @@ -337,33 +339,6 @@ template bool SPIFlash::readAnything(uint32_t _addr, T& data, bool fas //---------------------------------- Private Templates ----------------------------------// -// Private template to check for errors in writing to flash memory -// Takes three arguments - -// 1. _addr --> Any address from 0 to maxAddress -// 2. const T& value --> Variable with the data to be error checked -// 3. _sz --> Size of the data variable to be error checked, in bytes (1 byte = 8 bits) -/*template bool SPIFlash::_writeErrorCheck(uint32_t _addr, const T& value, uint32_t _sz) { - if (!_notBusy() || _isChipPoweredDown()) { - return false; - } - //Serial.print(F("Address being error checked: ")); - //Serial.println(_addr); - _currentAddress = _addr; - const uint8_t* p = (const uint8_t*)(const void*)&value; - CHIP_SELECT - _nextByte(WRITE, READDATA); - _transferAddress(); - for (uint16_t i = 0; i < _sz; i++) { - if (*p++ != _nextByte(READ)) { - _troubleshoot(ERRORCHKFAIL); - _endSPI(); - return false; - } - //_delay_us(5); - } - _endSPI(); - return true; -}*/ template bool SPIFlash::_writeErrorCheck(uint32_t _addr, const T& value, uint32_t _sz, uint8_t _dataType) { if (_isChipPoweredDown() || !_addressCheck(_addr, _sz) || !_notBusy()) { return false; @@ -385,7 +360,6 @@ template bool SPIFlash::_writeErrorCheck(uint32_t _addr, const T& valu } } else { - const uint8_t* p = (const uint8_t*)(const void*)&value; CHIP_SELECT _nextByte(WRITE, READDATA); _transferAddress(); @@ -397,8 +371,8 @@ template bool SPIFlash::_writeErrorCheck(uint32_t _addr, const T& valu } } _endSPI(); - return true; } + return true; } // Writes any type of data to a specific location in the flash memory. @@ -498,26 +472,39 @@ template bool SPIFlash::_write(uint32_t _addr, const T& value, uint32_ // 2. T& value --> Variable to return data into // 3. _sz --> Size of the variable in bytes (1 byte = 8 bits) // 4. fastRead --> defaults to false - executes _beginFastRead() if set to true -template bool SPIFlash::_read(uint32_t _addr, T& value, uint32_t _sz, bool fastRead) { - if (_prep(READDATA, _addr, _sz)) { +template bool SPIFlash::_read(uint32_t _addr, T& value, uint32_t _sz, bool fastRead, uint8_t _dataType) { + if (!_prep(READDATA, _addr, _sz)) { + return false; + } + else { uint8_t* p = (uint8_t*)(void*)&value; - CHIP_SELECT - if (fastRead) { - _nextByte(WRITE, FASTREAD); + + if (_dataType == _STRING_) { + char _inChar[_sz]; + _beginSPI(READDATA); + _nextBuf(READDATA, (uint8_t*) &(*_inChar), _sz); + _endSPI(); + for (uint16_t i = 0; i < _sz; i++) { + *p++ = _inChar[i]; + } } else { - _nextByte(WRITE, READDATA); - } - _transferAddress(); - for (uint16_t i = 0; i < _sz; i++) { - *p++ =_nextByte(READ); + CHIP_SELECT + if (fastRead) { + _nextByte(WRITE, FASTREAD); + } + else { + _nextByte(WRITE, READDATA); + } + _transferAddress(); + for (uint16_t i = 0; i < _sz; i++) { + *p++ =_nextByte(READ); + } + _endSPI(); } - _endSPI(); return true; } - else { - return false; - } + return true; } #endif // _SPIFLASH_H_ From 61a196d1b043dfe2ba9b51385e45798845ff29c8 Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Sun, 4 Mar 2018 22:13:18 +1000 Subject: [PATCH 13/26] Fixed issue with TestFlash.ino that caused a memory issue when compiling on the Arduino Uno/Atmega328 --- examples/TestFlash/TestFlash.ino | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/TestFlash/TestFlash.ino b/examples/TestFlash/TestFlash.ino index 8817577..ff4c51b 100644 --- a/examples/TestFlash/TestFlash.ino +++ b/examples/TestFlash/TestFlash.ino @@ -1,10 +1,10 @@ /* - ------------------------------------------------------------------------------------------------------------------------------------ + ---------------------------------------------------------------------------------------------------------------------------------- | Winbond Flash | - | SPIFlash library test v3.1.0 | + | SPIFlash library test v3.0.1 | |----------------------------------------------------------------------------------------------------------------------------------| | Marzogh | - | 04.03.2018 | + | 16.11.2016 | | Modified: hanyazou | | 19.11.2017 | |----------------------------------------------------------------------------------------------------------------------------------| @@ -530,6 +530,11 @@ void printAllPages(uint8_t outputType) { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Print commands~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// +void printLine() +{ + Serial.println(F("----------------------------------------------------------------------------------------------------------------------------------")); +} + void printSplash() { Serial.println(F(" SPIFlash library test ")); From df27bb9d578a2e841dee7af2c91ff6d2791c2a20 Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Sun, 4 Mar 2018 22:25:32 +1000 Subject: [PATCH 14/26] Fixed issue with TestFlash.ino that caused a memory issue when compiling on the Arduino Uno/Atmega328 --- examples/TestFlash/command_list.ino | 97 +++-------------------------- 1 file changed, 10 insertions(+), 87 deletions(-) diff --git a/examples/TestFlash/command_list.ino b/examples/TestFlash/command_list.ino index f853a1f..9279781 100644 --- a/examples/TestFlash/command_list.ino +++ b/examples/TestFlash/command_list.ino @@ -1,10 +1,14 @@ void commandList() { - printSplash(); - getID(); - printLine(); + Serial.println(F("-----------------------------------------------------------------------------------------------------------------------------------")); + Serial.println(F(" Winbond Flash ")); + Serial.println(F(" SPIFlash library test v3.1.0 ")); + Serial.println(F(" ----------------------------------------------------------------------------------------------------------------------------------")); + Serial.println(F(" Marzogh ")); + Serial.println(F(" 24.11.2015 ")); + Serial.println(F(" ----------------------------------------------------------------------------------------------------------------------------------")); Serial.println(F(" (Please make sure your Serial monitor is set to 'No Line Ending') ")); - Serial.println(); - Serial.println(); + Serial.println(F(" ***************************************************************** ")); + Serial.println(F(" ")); Serial.println(F(" # Please pick from the following commands and type the command number into the Serial console # ")); Serial.println(F(" For example - to write a byte of data, you would have to use the Write Byte function - so type '3' into the serial console. ")); Serial.println(F(" -------------------------------- ")); @@ -55,86 +59,5 @@ void commandList() { Serial.println(F(" 14. eraseChip")); Serial.print(F("\t\t")); Serial.println(F("'14' erases the entire chip")); - printLine(); -} - -void printLine(void) { - //Serial.println(); - for (uint8_t i = 0; i < 130; i++) { - Serial.print(F("-")); - } - Serial.println(); -} - -void clearprintBuffer(char *bufPtr) { - for (uint8_t i = 0; i < 128; i++) { - //printBuffer[i] = 0; - *bufPtr++ = 0; - } -} - -void printUniqueID(void) { - Serial.print("Unique ID: "); - long long _uniqueID = flash.getUniqueID(); - Serial.print(uint32_t(_uniqueID / 1000000L)); - Serial.print(uint32_t(_uniqueID % 1000000L)); - Serial.print(", "); - Serial.print("0x"); - Serial.print(uint32_t(_uniqueID >> 32), HEX); - Serial.println(uint32_t(_uniqueID), HEX); -} - -void getID(void) { - - char printBuffer[128]; - printLine(); - for (uint8_t i = 0; i < 50; i++) { - Serial.print(F(" ")); - } - Serial.print(F("SPIFlash Library version")); -#ifdef LIBVER - uint8_t _ver, _subver, _bugfix; - flash.libver(&_ver, &_subver, &_bugfix); - clearprintBuffer(&printBuffer[1]); - sprintf(printBuffer, ": %d.%d.%d", _ver, _subver, _bugfix); - Serial.println(printBuffer); -#else - Serial.println(F("< 2.5.0")); -#endif - printLine(); - - for (uint8_t i = 0; i < 65; i++) { - Serial.print(F(" ")); - } - Serial.println(F("Get ID")); - printLine(); - uint8_t b1, b2; - //uint16_t b3; - uint32_t JEDEC = flash.getJEDECID(); - uint32_t maxPage = flash.getMaxPage(); - uint32_t capacity = flash.getCapacity(); - b1 = (JEDEC >> 16); - b2 = (JEDEC >> 8); - //b3 = (JEDEC >> 0); - - clearprintBuffer(&printBuffer[1]); -#if defined (ARDUINO_ARCH_ESP32) - sprintf(printBuffer, "\t\t\tJEDEC ID: %04xh", JEDEC); -#else - sprintf(printBuffer, "\t\t\tJEDEC ID: %04lxh", JEDEC); -#endif - Serial.println(printBuffer); - //Serial.print(F("\t\t\tJEDEC ID: ")); - //Serial.print(JEDEC, HEX); - //Serial.println(F("xh")); - clearprintBuffer(&printBuffer[1]); -#if defined (ARDUINO_ARCH_ESP32) - sprintf(printBuffer, "\t\t\tManufacturer ID: %02xh\n\t\t\tMemory Type: %02xh\n\t\t\tCapacity: %u bytes\n\t\t\tMaximum pages: %u", b1, b2, capacity, maxPage); -#else - sprintf(printBuffer, "\t\t\tManufacturer ID: %02xh\n\t\t\tMemory Type: %02xh\n\t\t\tCapacity: %lu bytes\n\t\t\tMaximum pages: %lu", b1, b2, capacity, maxPage); -#endif - Serial.print(printBuffer); - Serial.print("\n\t\t\t"); - printUniqueID(); + Serial.println(F(" ----------------------------------------------------------------------------------------------------------------------------------")); } - From 1321e392e34525600cb89fda162c22f28a03088e Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Wed, 7 Mar 2018 18:38:52 +1000 Subject: [PATCH 15/26] Updated comments to fix errors --- README.md | 19 +++++++++---------- src/SPIFlash.cpp | 13 +++++++------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 9c97e61..785433b 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,6 @@ SPIFlash flash(33); - Every version of the library >= v3.0.0 supports the ability to use any of multiple SPI interfaces (if your micro-controller supports them). Switching to use another SPI interface is done by calling ```SPIFlash flash(csPin, &SPI1);``` (or &SPI2 and so on), instead of ```SPIFlash flash(csPin)```. * NOTE: This is currently only supported on the SAMD and STM32 architectures. -- Make sure to include ```#include``` when you include ```#include```. - Also make sure to include ```flash.begin(CHIPSIZE*)``` in ```void setup()```. This enables the library to detect the type of flash chip installed and load the right parameters. * Optional @@ -97,28 +96,28 @@ SPIFlash flash(33); The library enables the following functions:
-##### Non-I/O commands +##### Non-Read/Write functions
-###### begin(_chipsize*) -Must be called at the start in setup(). This function detects the type of chip being used and sets parameters accordingly. An optional CHIPSIZE parameter can be declared as an argument with this function. For supported CHIPSIZE values, please refer to the appropriate [wiki section](https://github.com/Marzogh/SPIFlash/wiki/begin()) or look at defines.h . +###### `begin(_chipsize*)` +Must be called at the start in setup(). This function detects the type of chip being used and sets parameters accordingly. An optional CHIPSIZE parameter can be declared as an argument with this function. For supported CHIPSIZE values, please refer to the appropriate [wiki section](https://github.com/Marzogh/SPIFlash/wiki/Chipsize) or look at defines.h . -###### setClock(clockSpeed) +###### `setClock(clockSpeed)` Must be called straight after begin(). This function takes a 32-bit number as replacement for the default maximum clock speed (104MHz for Winbond NOR flash) thereby initiating future SPI transactions with the user-defined clock speed. Use with care. -###### error(_verbosity) +###### `error(_verbosity)` Returns the (8-bit) error code generated by the function called immediately before this is called. If the optional VERBOSE argument is used, a verbose troubleshooting report is printed to Serial. Refer to the [Error reporting](https://github.com/Marzogh/SPIFlash/wiki/Error-reporting) section the Wiki for further reference. -###### getMANID() +###### `getMANID()` Returns the Manufacturer ID as a 16-bit value. -###### getJEDECID() +###### `getJEDECID()` Returns the JEDEC ID as a 32-bit value. -###### getUniqueID() +###### `getUniqueID()` Returns the Unique ID as a 64-bit value. -###### getAddress(sizeOfData) +###### `getAddress(sizeOfData)` Gets the next available address for use. Has two variants: * Takes the size of the data as an argument and returns a 32-bit address * Takes a three variables, the size of the data and two other variables to return a page number value & an offset into. diff --git a/src/SPIFlash.cpp b/src/SPIFlash.cpp index 19bd3fd..1452224 100644 --- a/src/SPIFlash.cpp +++ b/src/SPIFlash.cpp @@ -104,7 +104,7 @@ bool SPIFlash::begin(uint32_t flashChipSize) { // If no capacity is defined in user code if (!flashChipSize) { #ifdef RUNDIAGNOSTIC - Serial.println("No Chip size defined by user"); + Serial.println("No Chip size defined by user. Automated identification initiated."); #endif bool retVal = _chipID(); _endSPI(); @@ -156,12 +156,15 @@ uint32_t SPIFlash::getMaxPage(void) { return (_chip.capacity / SPI_PAGESIZE); } +#ifdef RUNDIAGNOSTICS //Returns the time taken to run a function. Must be called immediately after a function is run as the variable returned is overwritten each time a function from this library is called. Primarily used in the diagnostics sketch included in the library to track function time. +//This function can only be called if #define RUNDIAGNOSTICS is uncommented in SPIFlash.h float SPIFlash::functionRunTime(void) { return _spifuncruntime; } +#endif -//Returns the library version as a string +//Returns the library version as three bytes bool SPIFlash::libver(uint8_t *b1, uint8_t *b2, uint8_t *b3) { *b1 = LIBVER; *b2 = LIBSUBVER; @@ -212,11 +215,9 @@ uint64_t SPIFlash::getUniqueID(void) { return _uid; } -//Gets the next available address for use. Has two variants: -// A. Takes the size of the data as an argument and returns a 32-bit address -// B. Takes a three variables, the size of the data and two other variables to return a page number value & an offset into. +//Gets the next available address for use. +// Takes the size of the data as an argument and returns a 32-bit address // All addresses in the in the sketch must be obtained via this function or not at all. -// Variant A uint32_t SPIFlash::getAddress(uint16_t size) { bool _loopedOver = false; if (!_addressCheck(currentAddress, size)){ From 087c340cd89050757f093f329c4749835651f47b Mon Sep 17 00:00:00 2001 From: Marzogh Date: Wed, 7 Mar 2018 20:11:22 +1000 Subject: [PATCH 16/26] Fix TravisCI errors --- src/SPIFlash.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/SPIFlash.cpp b/src/SPIFlash.cpp index 1452224..7a3113d 100644 --- a/src/SPIFlash.cpp +++ b/src/SPIFlash.cpp @@ -156,13 +156,15 @@ uint32_t SPIFlash::getMaxPage(void) { return (_chip.capacity / SPI_PAGESIZE); } -#ifdef RUNDIAGNOSTICS //Returns the time taken to run a function. Must be called immediately after a function is run as the variable returned is overwritten each time a function from this library is called. Primarily used in the diagnostics sketch included in the library to track function time. -//This function can only be called if #define RUNDIAGNOSTICS is uncommented in SPIFlash.h +//This function can only be called if #define RUNDIAGNOSTIC is uncommented in SPIFlash.h float SPIFlash::functionRunTime(void) { +#ifdef RUNDIAGNOSTIC return _spifuncruntime; -} +#else + return 0; #endif +} //Returns the library version as three bytes bool SPIFlash::libver(uint8_t *b1, uint8_t *b2, uint8_t *b3) { From 5895bff6da1c44822833ccfae73fb3305a54beb1 Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Thu, 8 Mar 2018 16:48:36 +1000 Subject: [PATCH 17/26] Fixed formatting in Readme.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 785433b..39174c2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![license](https://img.shields.io/github/license/Marzogh/SPIFlash.svg)](https://github.com/Marzogh/SPIFlash/blob/master/LICENSE) ### Arduino library for Flash Memory Chips (SPI based only) - Download the latest stable release (v3.0.0) from
here. Please report any bugs in issues. + Download the latest stable release from [here](https://github.com/Marzogh/SPIFlash/releases/latest). Please report any bugs in [issues](https://github.com/Marzogh/SPIFlash/issues/new). This Arduino library is for use with flash memory chips that communicate using the SPI protocol. In its current form it supports identifying the flash chip and its various features; automatic address allocation and management; writing and reading a number of different types of data, ranging from 8-bit to 32-bit (signed and unsigned) values, floats, Strings, arrays of bytes/chars and structs to and from various locations; sector, block and chip erase; and powering down for low power operation. From 649d8f22c4f23cead308f9a38b6142126b4cf4db Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Fri, 9 Mar 2018 00:10:12 +1000 Subject: [PATCH 18/26] Updated Diagnostics.ino to include eraseSection(). --- .../FlashDiagnostics/Diagnostics_functions.ino | 15 +++++++++++++++ examples/FlashDiagnostics/FlashDiagnostics.ino | 1 + extras/Changes.log | 2 ++ 3 files changed, 18 insertions(+) diff --git a/examples/FlashDiagnostics/Diagnostics_functions.ino b/examples/FlashDiagnostics/Diagnostics_functions.ino index 173a4bc..4bae611 100644 --- a/examples/FlashDiagnostics/Diagnostics_functions.ino +++ b/examples/FlashDiagnostics/Diagnostics_functions.ino @@ -490,6 +490,21 @@ void eraseSectorTest() { Serial.println(); } +void eraseSectionTest() { + Serial.println(); + uint32_t _time, _addr; + _addr = random(0, 0xFFFFF); + Serial.print(F("\t\t\tErase 72KB Section: ")); + if (flash.eraseSection(_addr, KB(72))) { + _time = flash.functionRunTime(); + pass(TRUE); + printTime(_time, 0); + } + else { + pass(FALSE); + } +} + void eraseBlock32KTest() { Serial.println(); uint32_t _time, _addr; diff --git a/examples/FlashDiagnostics/FlashDiagnostics.ino b/examples/FlashDiagnostics/FlashDiagnostics.ino index beaf51b..19e09bb 100644 --- a/examples/FlashDiagnostics/FlashDiagnostics.ino +++ b/examples/FlashDiagnostics/FlashDiagnostics.ino @@ -64,6 +64,7 @@ void setup() { getID(); eraseChipTest(); + eraseSectionTest(); eraseBlock64KTest(); eraseBlock32KTest(); eraseSectorTest(); diff --git a/extras/Changes.log b/extras/Changes.log index b914929..136cb32 100644 --- a/extras/Changes.log +++ b/extras/Changes.log @@ -18,6 +18,8 @@ Bugs squashed Enhancements: --> A new function - 'flash.eraseSection(address, size)' - has been introduced in this version. When a user requires a large and variable (between writes) amount of data to be written to the flash memory on the fly and to have the correct amount of space erased to fit the data, this function will automatically calculate and erase the right amount of space to fit the data. Please note that if the the amount of data being written is consistently the same size, the pre-existing 'flash.eraseSector()', 'flash.eraseBlock32K()' and 'flash.eraseBlock64K()' functions will operate a lot faster. +--> Updated Diagnostics.ino to include eraseSection(). + --> All I/O functions now check to see if the flash chip is powered down. If it is, they prevent the function from running and returns an error. A new error code 'CHIPISPOWEREDDOWN' will be returned upon calling flash.error(). From ca77f6e8c2eb41dc587597c179eebc656a20b218 Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Fri, 9 Mar 2018 15:07:55 +1000 Subject: [PATCH 19/26] Added renaming notice to ReadMe.md --- README.md | 57 ++++++++++++++++++++++++++++++++++-------------- src/SPIFlash.cpp | 2 +- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 39174c2..02677d5 100644 --- a/README.md +++ b/README.md @@ -5,24 +5,49 @@ [![GitHub pull requests](https://img.shields.io/github/issues-pr/Marzogh/SPIFlash.svg)](https://github.com/Marzogh/SPIFlash/pulls) [![license](https://img.shields.io/github/license/Marzogh/SPIFlash.svg)](https://github.com/Marzogh/SPIFlash/blob/master/LICENSE) -### Arduino library for Flash Memory Chips (SPI based only) +## Arduino library for Flash Memory Chips (SPI based only) Download the latest stable release from [here](https://github.com/Marzogh/SPIFlash/releases/latest). Please report any bugs in [issues](https://github.com/Marzogh/SPIFlash/issues/new). This Arduino library is for use with flash memory chips that communicate using the SPI protocol. In its current form it supports identifying the flash chip and its various features; automatic address allocation and management; writing and reading a number of different types of data, ranging from 8-bit to 32-bit (signed and unsigned) values, floats, Strings, arrays of bytes/chars and structs to and from various locations; sector, block and chip erase; and powering down for low power operation. +
+ +>### Important note from developer + +>The term 'SPI Flash' is a fairly common way to refer to Flash memory chips that communicate over the SPI protocol and there are a number of libraries that are named SPIFlash. When I first started work on this library in 2014, it was mostly as an exercise to improve my embedded systems programming skills. When I asked for it to be included in the list of Arduino libraries, I did not really expect it to go very far or get very popular. But, before I knew it, I was releasing new versions every other month and I found the number of users got way bigger than I imagined it would. The amount of traffic the GitHub repository gets still surprises me. + +>A few months ago, @LowPowerLab raised an issue ([#83](https://github.com/Marzogh/SPIFlash/issues/83)) about the problems the name of this library was causing the users of his library - also called SPIFlash. The fact that this library is in the Arduino Library manager meant that his users were being asked to upgrade their version of SPIFlash when the libraries were actually different. I can understand how much of an annoyance this can be for a user. + +>@LowPowerLab's version of SPIFlash has been around for longer than this one and his library is a major part of his commercial line of development boards. Since I am a hobbyist developer (I'm a full-time geneticist & a part-time dabbler in ecology - if you're curious) and this library is not a commercial product with branding and trademarks to worry about, the least I can do is change the name of this library so it stops being an annoyance to @LowPowerLab's customers. +>> On a side note, if you did not know already, @LowPowerLab makes and sells a fantastic line of Arduino compatible boards - the [Moteino](https://lowpowerlab.com/shop/) series - and has developed a fantastic IoT protocol to use with them to add smarts to your home. In January this year, I finally got around to getting my hands on some of his boards and have been playing around with them. They are fantastic! I'd strongly recommend you check them out - if you haven't already done so. + +>I asked the Arduino developers if there was a way to migrate this library to a new name without breaking the upgrade path ([Issue #6904](https://github.com/arduino/Arduino/issues/6904)) and was told that it was not possible. The only way is to pull my version of SPIFlash from the library manager and ask for a renamed version to be included in the library manager after. -- For details of the Flash chips compatible with this library please refer to the list below. +>So, this is what I have decided to do.: +- This version - v3.1.0 - will be the last version to be released under the SPIFlash name. +- Anyone downloading this version of the library will be able to read this notice in the ReadMe file. +- Anyone using this version of the library will see a notice in their Serial output directing them to this notice in the ReadMe file. (this can be removed by commenting out the `#define PRINTNAMECHANGEALERT` in `SPIFlash.h`) +- Version 3.2.0 will be released in a couple of months (in May most likely) under a new name. I'm currently thinking of calling it SPIMemory - suggestions are welcome. +- This version of SPIFlash will be removed from the library manager then and replaced with the new one. + +>The only change will have to be made in end-user code will be to change the `#include SPIFlash.h` to `#include SPIMemory.h` (or whatever the new name will be). After the name change, rest assured that older versions will remain accessible and the development history of the library will be preserved. + +>I apologise for any trouble this might cause you as the end user, but, given the facts, it is the only thing I can do to be fair to @LowPowerLab + +>Thanks again for using `SPIFlash` and I hope you will continue to find it useful in whatever new name it will take on. + +
-#### Compatibility +### Compatibility -##### Arduino IDEs supported (actually tested with) +#### Arduino IDEs supported (actually tested with) - IDE v1.5.x - IDE v1.6.0-v1.6.5 - IDE v1.6.9-v1.6.12 - IDE v1.8.1-v1.8.5 -##### Boards +#### Boards -###### Completely supported +##### Completely supported - Arduino Uno - Arduino Leonardo - Arduino Due @@ -36,16 +61,16 @@ This Arduino library is for use with flash memory chips that communicate using t - Arduino Micro - Arduino Fio -###### In BETA +##### In BETA - ESP32 Boards (Tested on the Adafruit ESP32 Feather) The library is known to work with the ESP32 core as of the current commit 8ba91b9 on 07.11.2017. ```ESP32 support will remain in beta till the ESP32 core can be installed via the Arduino boards manager.``` NOTE: ESP32 boards usually have an SPIFlash already attached to their SS pin, so the user has to declare the ChipSelect pin being used when the constructor is declared - for example ``` SPIFlash flash(33); ``` -##### Flash memory compatibility +#### Flash memory compatibility -###### Completely supported (Actually tested with) +##### Completely supported (Actually tested with) - Winbond - W25Q16BV - W25Q64FV @@ -58,26 +83,26 @@ SPIFlash flash(33); - S25FL116K - S25FL127S -###### Should work with (Similar enough to the ones actually tested with) +##### Should work with (Similar enough to the ones actually tested with) - Winbond (All SPI Flash chips) - Microchip (SST25 & SST26 series) - Cypress/Spansion (S25FL series) -#### Installation +### Installation -##### Option 1 +#### Option 1 - Open the Arduino IDE. - Go to Sketch > Include Library > Manage libraries. - Search for SPIFlash. - Install the latest version. -##### Option 2 +#### Option 2 - Click on the 'Clone or download' button above the list of files on this page . - Select Download ZIP. A .zip file will download to your computer. - Unzip the archive and rename resulting folder to 'SPIFlash' - Move the folder to your libraries folder (~/sketches/libraries) -#### Usage +### Usage - The library is called by declaring the```SPIFlash flash(csPin*)``` constructor where 'flash' can be replaced by a user constructor of choice and 'csPin' is the Chip Select pin for the flash module. @@ -89,14 +114,14 @@ SPIFlash flash(33); * Optional -###### Notes on Address overflow and Error checking +##### Notes on Address overflow and Error checking - The library has Address overflow enabled by default - i.e. if the last address read/written from/to, in any function, is 0xFFFFF then, the next address read/written from/to is 0x00000. This can be disabled by uncommenting ```#define DISABLEOVERFLOW``` in SPIFlash.h. (Address overflow only works for Read / Write functions. Erase functions erase only a set number of blocks/sectors irrespective of overflow.) - All write functions have Error checking turned on by default - i.e. every byte written to the flash memory will be checked against the data stored on the Arduino. Users who require greater write speeds can disable this function by setting an optional last 'errorCheck' argument in any write function to NOERRCHK - For eg. call the function ```writeByte(address, *data_buffer, NOERRCHK)``` instead of ```writeByte(address, *data_buffer)```. The library enables the following functions:
-##### Non-Read/Write functions +#### Non-Read/Write functions
###### `begin(_chipsize*)` diff --git a/src/SPIFlash.cpp b/src/SPIFlash.cpp index 7a3113d..c8c0f24 100644 --- a/src/SPIFlash.cpp +++ b/src/SPIFlash.cpp @@ -87,7 +87,7 @@ bool SPIFlash::begin(uint32_t flashChipSize) { Serial.print("-"); } Serial.println(); - #endif +#endif #ifdef RUNDIAGNOSTIC Serial.println("Chip Diagnostics initiated."); Serial.println(); From 00bf3dd306d9e53785de1178453e305cef37147a Mon Sep 17 00:00:00 2001 From: Marzogh Date: Fri, 9 Mar 2018 15:20:20 +1000 Subject: [PATCH 20/26] Name change notice formatting fixed --- README.md | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 02677d5..fb564b4 100644 --- a/README.md +++ b/README.md @@ -12,27 +12,26 @@ This Arduino library is for use with flash memory chips that communicate using t
>### Important note from developer - >The term 'SPI Flash' is a fairly common way to refer to Flash memory chips that communicate over the SPI protocol and there are a number of libraries that are named SPIFlash. When I first started work on this library in 2014, it was mostly as an exercise to improve my embedded systems programming skills. When I asked for it to be included in the list of Arduino libraries, I did not really expect it to go very far or get very popular. But, before I knew it, I was releasing new versions every other month and I found the number of users got way bigger than I imagined it would. The amount of traffic the GitHub repository gets still surprises me. - +> >A few months ago, @LowPowerLab raised an issue ([#83](https://github.com/Marzogh/SPIFlash/issues/83)) about the problems the name of this library was causing the users of his library - also called SPIFlash. The fact that this library is in the Arduino Library manager meant that his users were being asked to upgrade their version of SPIFlash when the libraries were actually different. I can understand how much of an annoyance this can be for a user. - +> >@LowPowerLab's version of SPIFlash has been around for longer than this one and his library is a major part of his commercial line of development boards. Since I am a hobbyist developer (I'm a full-time geneticist & a part-time dabbler in ecology - if you're curious) and this library is not a commercial product with branding and trademarks to worry about, the least I can do is change the name of this library so it stops being an annoyance to @LowPowerLab's customers. >> On a side note, if you did not know already, @LowPowerLab makes and sells a fantastic line of Arduino compatible boards - the [Moteino](https://lowpowerlab.com/shop/) series - and has developed a fantastic IoT protocol to use with them to add smarts to your home. In January this year, I finally got around to getting my hands on some of his boards and have been playing around with them. They are fantastic! I'd strongly recommend you check them out - if you haven't already done so. - +> >I asked the Arduino developers if there was a way to migrate this library to a new name without breaking the upgrade path ([Issue #6904](https://github.com/arduino/Arduino/issues/6904)) and was told that it was not possible. The only way is to pull my version of SPIFlash from the library manager and ask for a renamed version to be included in the library manager after. - +> >So, this is what I have decided to do.: -- This version - v3.1.0 - will be the last version to be released under the SPIFlash name. -- Anyone downloading this version of the library will be able to read this notice in the ReadMe file. -- Anyone using this version of the library will see a notice in their Serial output directing them to this notice in the ReadMe file. (this can be removed by commenting out the `#define PRINTNAMECHANGEALERT` in `SPIFlash.h`) -- Version 3.2.0 will be released in a couple of months (in May most likely) under a new name. I'm currently thinking of calling it SPIMemory - suggestions are welcome. -- This version of SPIFlash will be removed from the library manager then and replaced with the new one. - +>- This version - v3.1.0 - will be the last version to be released under the SPIFlash name. +>- Anyone downloading this version of the library will be able to read this notice in the ReadMe file. +>- Anyone using this version of the library will see a notice in their Serial output directing them to this notice in the ReadMe file. (this can be removed by commenting out the `#define PRINTNAMECHANGEALERT` in `SPIFlash.h`) +>- Version 3.2.0 will be released in a couple of months (in May most likely) under a new name. I'm currently thinking of calling it SPIMemory - suggestions are welcome. +>- This version of SPIFlash will be removed from the library manager then and replaced with the new one. +> >The only change will have to be made in end-user code will be to change the `#include SPIFlash.h` to `#include SPIMemory.h` (or whatever the new name will be). After the name change, rest assured that older versions will remain accessible and the development history of the library will be preserved. - +> >I apologise for any trouble this might cause you as the end user, but, given the facts, it is the only thing I can do to be fair to @LowPowerLab - +> >Thanks again for using `SPIFlash` and I hope you will continue to find it useful in whatever new name it will take on.
From 6d97bd39d9a384e95dfc979d27251a7e9b665926 Mon Sep 17 00:00:00 2001 From: Levente Orban Date: Sun, 11 Mar 2018 17:12:27 +0100 Subject: [PATCH 21/26] Minor fix in API description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fb564b4..fc7a569 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ Must be called straight after begin(). This function takes a 32-bit number as re ###### `error(_verbosity)` Returns the (8-bit) error code generated by the function called immediately before this is called. If the optional VERBOSE argument is used, a verbose troubleshooting report is printed to Serial. Refer to the [Error reporting](https://github.com/Marzogh/SPIFlash/wiki/Error-reporting) section the Wiki for further reference. -###### `getMANID()` +###### `getManID()` Returns the Manufacturer ID as a 16-bit value. ###### `getJEDECID()` From 67f3b4893a95924ad90f1b449cfdaead37592a89 Mon Sep 17 00:00:00 2001 From: Levente Orban Date: Fri, 16 Mar 2018 18:33:23 +0100 Subject: [PATCH 22/26] Minor typo fix in API --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc7a569..39ed551 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ size = sizeof(variable) can be used for all types of data but String objects. ###### getCapacity() Returns the capacity in bytes as a 32-bit value. -###### getmaxPage() +###### getMaxPage() Returns the maximum number of pages in the flash memory as a 32-bit value. ###### functionRunTime() From 6afd295374dc6689d5d25417e83162200b5846c5 Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Mon, 26 Mar 2018 13:51:55 +1000 Subject: [PATCH 23/26] Updated library.properties to include name change notice. -> v3.2.0 onwards, this library will be called SPIMemory --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index 88e9e82..4c6778d 100644 --- a/library.properties +++ b/library.properties @@ -3,7 +3,7 @@ version=3.1.0 author=Prajwal Bhattaram maintainer=Prajwal Bhattaram sentence=SPI Flash library for Arduino. -paragraph=This library enables read, write, erase and power functions on the following Flash chips - W25X05**, W25X10**, W25X20**, W25X40**, W25Q80**, W25Q16**, W25Q32**, W25Q64**, W25Q128**, W25Q256**, SST25VF064C, SST26VF064B & S25FL116K. All other Winbond flash chips can also be used with this library from v2.6.0 onwards. A number of Microchip, Cypress & Spansion chips can be used with with the library from v3.0.0 onwards. Refer to change log for further information about this release. +paragraph=This is the last version (v3.1.0) with the name SPIFlash. For future updates (starting 15 May 2018) please download the SPIMemory library instead. This library enables read, write, erase and power functions on the following Flash chips - W25X05**, W25X10**, W25X20**, W25X40**, W25Q80**, W25Q16**, W25Q32**, W25Q64**, W25Q128**, W25Q256**, SST25VF064C, SST26VF064B & S25FL116K. All other Winbond flash chips can also be used with this library from v2.6.0 onwards. A number of Microchip, Cypress & Spansion chips can be used with with the library from v3.0.0 onwards. Refer to change log for further information about this release. category=Data Storage url=https://github.com/Marzogh/SPIFlash architectures=avr,sam,samd,esp8266,esp32,Simblee,stm32 From f11e15debadf55207654cc9bf18efdf77fa546bf Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Mon, 26 Mar 2018 13:52:18 +1000 Subject: [PATCH 24/26] Updated library.properties to include name change notice. -> v3.2.0 onwards, this library will be called SPIMemory --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 39ed551..fb0c08d 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,10 @@ This Arduino library is for use with flash memory chips that communicate using t >- This version - v3.1.0 - will be the last version to be released under the SPIFlash name. >- Anyone downloading this version of the library will be able to read this notice in the ReadMe file. >- Anyone using this version of the library will see a notice in their Serial output directing them to this notice in the ReadMe file. (this can be removed by commenting out the `#define PRINTNAMECHANGEALERT` in `SPIFlash.h`) ->- Version 3.2.0 will be released in a couple of months (in May most likely) under a new name. I'm currently thinking of calling it SPIMemory - suggestions are welcome. +>- Version 3.2.0 will be released in a couple of months (in May most likely) under a new name - SPIMemory. >- This version of SPIFlash will be removed from the library manager then and replaced with the new one. > ->The only change will have to be made in end-user code will be to change the `#include SPIFlash.h` to `#include SPIMemory.h` (or whatever the new name will be). After the name change, rest assured that older versions will remain accessible and the development history of the library will be preserved. +>The only change will have to be made in end-user code will be to change the `#include SPIFlash.h` to `#include SPIMemory.h`. After the name change, rest assured that older versions will remain accessible and the development history of the library will be preserved. > >I apologise for any trouble this might cause you as the end user, but, given the facts, it is the only thing I can do to be fair to @LowPowerLab > From b7f2a38cd86470c31ec846d1640a5b084e8fe5c5 Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Mon, 26 Mar 2018 14:21:05 +1000 Subject: [PATCH 25/26] Updated travis.yml --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6790cc7..381a13e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,18 +4,18 @@ before_install: - source <(curl -SLs https://raw.githubusercontent.com/Marzogh/Travis-CI/master/install.sh) script: - build_main_platforms - #- build_platform trinket #- build_platform rtl8195a #- build_platform uno #- build_platform due #- build_platform zero #- build_platform esp8266 #- build_platform leonardo + #- build_platform mega #- build_platform rtl8195a #- build_platform simblee - #- build_platform mega - #- build_platform fio - #- build_platform micro + #- build_platform cplayClassic + #- build_platform cplayExpress + #- build_platform trinket notifications: email: on_success: change From 01cec3ebe1b1e1b5e0e297db85052e53d500eeb4 Mon Sep 17 00:00:00 2001 From: Prajwal Bhattaram Date: Mon, 26 Mar 2018 14:25:35 +1000 Subject: [PATCH 26/26] Updated badges in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fb0c08d..8083807 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SPIFlash [![Build Status](https://travis-ci.org/Marzogh/SPIFlash.svg?branch=stable)](https://travis-ci.org/Marzogh/SPIFlash) [![DOI](https://zenodo.org/badge/18908/Marzogh/SPIFlash.svg)](https://zenodo.org/badge/latestdoi/18908/Marzogh/SPIFlash) [![GitHub release](https://img.shields.io/github/release/Marzogh/SPIFlash.svg)](https://github.com/Marzogh/SPIFlash) -[![GitHub commits](https://img.shields.io/github/commits-since/Marzogh/SPIFlash/v3.0.0.svg)](https://github.com/Marzogh/SPIFlash/compare/v3.0.0...development) +[![GitHub commits](https://img.shields.io/github/commits-since/Marzogh/SPIFlash/v3.0.0.svg)](https://github.com/Marzogh/SPIFlash/compare/v3.0.1...stable) [![GitHub issues](https://img.shields.io/github/issues/Marzogh/SPIFlash.svg)](https://github.com/Marzogh/SPIFlash/issues) [![GitHub pull requests](https://img.shields.io/github/issues-pr/Marzogh/SPIFlash.svg)](https://github.com/Marzogh/SPIFlash/pulls) [![license](https://img.shields.io/github/license/Marzogh/SPIFlash.svg)](https://github.com/Marzogh/SPIFlash/blob/master/LICENSE)