diff --git a/libraries/MySensors/MyConfig.h b/libraries/MySensors/MyConfig.h index a4f8a53..4b7b4f1 100644 --- a/libraries/MySensors/MyConfig.h +++ b/libraries/MySensors/MyConfig.h @@ -6,10 +6,24 @@ // to see what is actually is happening when developing #define DEBUG -// Serial output baud rate +// Serial output baud rate (for debug prints and serial gateway) #define BAUD_RATE 115200 +/********************************** +* Over the air firmware updates +***********************************/ + +// The following define enables the safe over-the-air firmware update feature +// which requires external flash and the DualOptiBoot bootloader. +// Note: You can still have OTA FW updates without external flash but it +// requires the MYSBootloader and disabled MY_OTA_FIRMWARE_FEATURE +#define MY_OTA_FIRMWARE_FEATURE +// Slave select pin for external flash +#define MY_OTA_FLASH_SS 8 +// Flash jdecid +#define MY_OTA_FLASH_JDECID 0x1F65 + /********************************** * Message Signing Settings diff --git a/libraries/MySensors/MyHwATMega328.h b/libraries/MySensors/MyHwATMega328.h index 684ef59..8610f1b 100644 --- a/libraries/MySensors/MyHwATMega328.h +++ b/libraries/MySensors/MyHwATMega328.h @@ -12,6 +12,7 @@ #include + #ifdef __cplusplus #include #include diff --git a/libraries/MySensors/MySensor.cpp b/libraries/MySensors/MySensor.cpp index ebb69ef..91644e0 100755 --- a/libraries/MySensors/MySensor.cpp +++ b/libraries/MySensors/MySensor.cpp @@ -49,12 +49,39 @@ MySensor::MySensor(MyTransport &_radio, MyHw &_hw radio(_radio), #ifdef MY_SIGNING_FEATURE signer(_signer), +#endif +#ifdef MY_OTA_FIRMWARE_FEATURE + flash(MY_OTA_FLASH_SS, MY_OTA_FLASH_JDECID), #endif hw(_hw) { } +#ifdef MY_OTA_FIRMWARE_FEATURE +// do a crc16 on the whole received firmware +bool MySensor::isValidFirmware() { + void* ptr = 0; + uint16_t crc = ~0; + int j; + + for (uint16_t i = 0; i < fc.blocks * FIRMWARE_BLOCK_SIZE; ++i) { + uint8_t a = flash.readByte((uint16_t) ptr + i + FIRMWARE_START_OFFSET); + crc ^= a; + for (j = 0; j < 8; ++j) + { + if (crc & 1) + crc = (crc >> 1) ^ 0xA001; + else + crc = (crc >> 1); + } + } + return crc == fc.crc; +} + +#endif + + void MySensor::begin(void (*_msgCallback)(const MyMessage &), uint8_t _nodeId, boolean _repeaterMode, uint8_t _parentNodeId) { hw_init(); repeaterMode = _repeaterMode; @@ -71,6 +98,12 @@ void MySensor::begin(void (*_msgCallback)(const MyMessage &), uint8_t _nodeId, b hw_readConfigBlock((void*)&nc, (void*)EEPROM_NODE_ID_ADDRESS, sizeof(NodeConfig)); // Read latest received controller configuration from EEPROM hw_readConfigBlock((void*)&cc, (void*)EEPROM_CONTROLLER_CONFIG_ADDRESS, sizeof(ControllerConfig)); +#ifdef MY_OTA_FIRMWARE_FEATURE + // Read firmware config from EEPROM, i.e. type, version, CRC, blocks + hw_readConfigBlock((void*)&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig)); +#endif + + #ifdef MY_SIGNING_FEATURE // Read out the signing requirements from EEPROM hw_readConfigBlock((void*)doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS, sizeof(doSign)); @@ -158,6 +191,21 @@ void MySensor::setupNode() { // Wait configuration reply. wait(2000); + +#ifdef MY_OTA_FIRMWARE_FEATURE + RequestFirmwareConfig *reqFWConfig = (RequestFirmwareConfig *)msg.data; + mSetLength(msg, sizeof(RequestFirmwareConfig)); + mSetCommand(msg, C_STREAM); + mSetPayloadType(msg,P_CUSTOM); + // copy node settings to reqFWConfig + memcpy(reqFWConfig,&fc,sizeof(NodeFirmwareConfig)); + // add bootloader information + reqFWConfig->BLVersion = MY_OTA_BOOTLOADER_VERSION; + fwUpdateOngoing = false; + sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_CONFIG_REQUEST, false)); +#endif + + } } @@ -351,7 +399,29 @@ boolean MySensor::process() { hw_watchdogReset(); uint8_t to = 0; if (!radio.available(&to)) + { +#ifdef MY_OTA_FIRMWARE_FEATURE + unsigned long enter = hw_millis(); + if (fwUpdateOngoing && (enter - fwLastRequestTime > MY_OTA_RETRY_DELAY)) { + if (fwRetry == 0) { + debug(PSTR("fw upd fail\n")); + // Give up. We have requested MY_OTA_RETRY times without any packet in return. + fwUpdateOngoing = false; + return false; + } + fwRetry--; + fwLastRequestTime = enter; + // Time to (re-)request firmware block from controller + RequestFWBlock *firmwareRequest = (RequestFWBlock *)msg.data; + mSetLength(msg, sizeof(RequestFWBlock)); + firmwareRequest->type = fc.type; + firmwareRequest->version = fc.version; + firmwareRequest->block = (fwBlock - 1); + sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_REQUEST, false)); + } +#endif return false; + } #ifdef MY_SIGNING_FEATURE (void)signer.checkTimer(); // Manage signing timeout @@ -515,6 +585,55 @@ boolean MySensor::process() { return false; } } +#ifdef MY_OTA_FIRMWARE_FEATURE + else if (command == C_STREAM) { + if (type == ST_FIRMWARE_CONFIG_RESPONSE) { + NodeFirmwareConfig *firmwareConfigResponse = (NodeFirmwareConfig *)msg.data; + // compare with current node configuration, if they differ, start fw fetch process + if (memcmp(&fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig))) { + debug(PSTR("fw update\n")); + fwUpdateOngoing = true; + fwBlock = fc.blocks; + // copy new FW config + memcpy(&fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig)); + // Init flash + if (!flash.initialize()) { + debug(PSTR("flash init fail\n")); + } + + } + } else if (type == ST_FIRMWARE_RESPONSE) { + // Save block to eeprom + debug(PSTR("fw block %d\n"), fwBlock); + + ReplyFWBlock *firmwareResponse = (ReplyFWBlock *)msg.data; + // write to flash + flash.writeBytes(((fwBlock-1)*FIRMWARE_BLOCK_SIZE) + FIRMWARE_START_OFFSET, firmwareResponse->data, FIRMWARE_BLOCK_SIZE); + fwBlock--; + if (fwBlock == 0) { + // We're finished! Do a checksum and reboot. + if (isValidFirmware()) { + debug(PSTR("fw checksum ok\n")); + // All seems ok, write size and signature to flash (DualOptiboot will pick this up and flash it) + uint16_t fwsize = FIRMWARE_BLOCK_SIZE*fc.blocks; + flash.writeBytes(0, "FLXIMG:", 7); + flash.writeByte(7, fwsize >> 8); + flash.writeByte(8, fwsize); + flash.writeByte(9, ':'); + // Write the new firmware config to eeprom + hw_writeConfigBlock((void*)&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig)); + hw_reboot(); + } else { + debug(PSTR("fw checksum fail\n")); + } + } + } + // Make sure packet request occurs next time process() is called by timing out requestTime. + fwRetry = MY_OTA_RETRY+1; + fwLastRequestTime = 0; + return false; + } +#endif // Call incoming message callback if available if (msgCallback != NULL) { msgCallback(msg); @@ -561,17 +680,46 @@ void MySensor::wait(unsigned long ms) { } void MySensor::sleep(unsigned long ms) { - radio.powerDown(); - hw.sleep(ms); +#ifdef MY_OTA_FIRMWARE_FEATURE + if (fwUpdateOngoing) { + // Do not sleep node while fw update is ongoing + process(); + } else { +#endif + radio.powerDown(); + hw.sleep(ms); +#ifdef MY_OTA_FIRMWARE_FEATURE + } +#endif } bool MySensor::sleep(uint8_t interrupt, uint8_t mode, unsigned long ms) { +#ifdef MY_OTA_FIRMWARE_FEATURE + if (fwUpdateOngoing) { + // Do not sleep node while fw update is ongoing + process(); + return false; + } else { +#endif radio.powerDown(); return hw.sleep(interrupt, mode, ms) ; +#ifdef MY_OTA_FIRMWARE_FEATURE + } +#endif } int8_t MySensor::sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) { - radio.powerDown(); - return hw.sleep(interrupt1, mode1, interrupt2, mode2, ms) ; +#ifdef MY_OTA_FIRMWARE_FEATURE + if (fwUpdateOngoing) { + // Do not sleep node while fw update is ongoing + process(); + return -1; + } else { +#endif + radio.powerDown(); + return hw.sleep(interrupt1, mode1, interrupt2, mode2, ms) ; +#ifdef MY_OTA_FIRMWARE_FEATURE + } +#endif } diff --git a/libraries/MySensors/MySensor.h b/libraries/MySensors/MySensor.h index df297cf..7873c22 100644 --- a/libraries/MySensors/MySensor.h +++ b/libraries/MySensors/MySensor.h @@ -23,6 +23,9 @@ #include "MySigningNone.h" #endif #include "MyMessage.h" +#ifdef MY_OTA_FIRMWARE_FEATURE +#include "utility/SPIFlash.h" +#endif #include #include @@ -61,6 +64,7 @@ typedef MyHwATMega328 MyHwDriver; // Search for a new parent node after this many transmission failures #define SEARCH_FAILURES 5 + struct NodeConfig { uint8_t nodeId; // Current node id @@ -72,6 +76,53 @@ struct ControllerConfig { uint8_t isMetric; }; + +// Size of each firmware block +#define FIRMWARE_BLOCK_SIZE 16 +// Number of times a firmware block should be requested before giving up +#define FIRMWARE_MAX_REQUESTS 5 +// Number of times to request a fw block before giving up +#define MY_OTA_RETRY 5 +// Number of millisecons before re-request a fw block +#define MY_OTA_RETRY_DELAY 500 +// Start offset for firmware in flash (DualOptiboot wants to keeps a signature first) +#define FIRMWARE_START_OFFSET 10 +// Bootloader version +#define MY_OTA_BOOTLOADER_MAJOR_VERSION 3 +#define MY_OTA_BOOTLOADER_MINOR_VERSION 0 +#define MY_OTA_BOOTLOADER_VERSION (MY_OTA_BOOTLOADER_MINOR_VERSION * 256 + MY_OTA_BOOTLOADER_MAJOR_VERSION) + + +// FW config structure, stored in eeprom +typedef struct { + uint16_t type; + uint16_t version; + uint16_t blocks; + uint16_t crc; +} __attribute__((packed)) NodeFirmwareConfig; + +typedef struct { + uint16_t type; + uint16_t version; + uint16_t blocks; + uint16_t crc; + uint16_t BLVersion; +} __attribute__((packed)) RequestFirmwareConfig; + +typedef struct { + uint16_t type; + uint16_t version; + uint16_t block; +} __attribute__((packed)) RequestFWBlock; + +typedef struct { + uint16_t type; + uint16_t version; + uint16_t block; + uint8_t data[FIRMWARE_BLOCK_SIZE]; +} __attribute__((packed)) ReplyFWBlock; + + #ifdef __cplusplus class MySensor { @@ -243,6 +294,15 @@ class MySensor protected: NodeConfig nc; // Essential settings for node to work ControllerConfig cc; // Configuration coming from controller +#ifdef MY_OTA_FIRMWARE_FEATURE + NodeFirmwareConfig fc; + bool fwUpdateOngoing; + unsigned long fwLastRequestTime; + uint16_t fwBlock; + uint8_t fwRetry; + SPIFlash flash; +#endif + bool repeaterMode; bool autoFindParent; bool isGateway; @@ -268,6 +328,12 @@ class MySensor void (*timeCallback)(unsigned long); // Callback for requested time messages void (*msgCallback)(const MyMessage &); // Callback for incoming messages from other nodes and gateway. +#ifdef MY_OTA_FIRMWARE_FEATURE +// do a crc16 on the whole received firmware + bool isValidFirmware(); +#endif + + void requestNodeId(); void setupNode(); void findParentNode(); diff --git a/libraries/MySensors/examples/RelayActuator/RelayActuator.ino b/libraries/MySensors/examples/RelayActuator/RelayActuator.ino index 9a7e7c7..615049d 100644 --- a/libraries/MySensors/examples/RelayActuator/RelayActuator.ino +++ b/libraries/MySensors/examples/RelayActuator/RelayActuator.ino @@ -3,6 +3,7 @@ #include #include +//#include #include #include #include @@ -14,8 +15,9 @@ // NRFRF24L01 radio driver (set low transmit power by default) MyTransportNRF24 radio(RF24_CE_PIN, RF24_CS_PIN, RF24_PA_LEVEL_GW); +//MyTransportRF69 radio; // Message signing driver (none default) -// MySigningNone signer; +//MySigningNone signer; // Select AtMega328 hardware profile MyHwATMega328 hw; // Construct MySensors library diff --git a/libraries/MySensors/examples/SerialGateway/SerialGateway.ino b/libraries/MySensors/examples/SerialGateway/SerialGateway.ino index 8dc5e26..d103407 100644 --- a/libraries/MySensors/examples/SerialGateway/SerialGateway.ino +++ b/libraries/MySensors/examples/SerialGateway/SerialGateway.ino @@ -23,8 +23,9 @@ #define NO_PORTB_PINCHANGES -#include #include +#include + #include #include #include @@ -43,9 +44,11 @@ // NRFRF24L01 radio driver (set low transmit power by default) MyTransportNRF24 radio(RF24_CE_PIN, RF24_CS_PIN, RF24_PA_LEVEL_GW); // Message signing driver (none default) -MySigningNone signer; +//MySigningNone signer; // Construct MySensors library -MySensor gw(radio, signer); +MyHwATMega328 hw; + +MySensor gw(radio, hw); char inputString[MAX_RECEIVE_LENGTH] = ""; // A string to hold incoming commands from serial/ethernet interface int inputPos = 0; diff --git a/libraries/MySensors/utility/SPIFlash.cpp b/libraries/MySensors/utility/SPIFlash.cpp new file mode 100644 index 0000000..03d6a1a --- /dev/null +++ b/libraries/MySensors/utility/SPIFlash.cpp @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2013 by Felix Rusu + * SPI Flash memory library for arduino/moteino. + * This works with 256byte/page SPI flash memory + * For instance a 4MBit (512Kbyte) flash chip will have 2048 pages: 256*2048 = 524288 bytes (512Kbytes) + * Minimal modifications should allow chips that have different page size but modifications + * DEPENDS ON: Arduino SPI library + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#include + +byte SPIFlash::UNIQUEID[8]; + +/// IMPORTANT: NAND FLASH memory requires erase before write, because +/// it can only transition from 1s to 0s and only the erase command can reset all 0s to 1s +/// See http://en.wikipedia.org/wiki/Flash_memory +/// The smallest range that can be erased is a sector (4K, 32K, 64K); there is also a chip erase command + +/// Constructor. JedecID is optional but recommended, since this will ensure that the device is present and has a valid response +/// get this from the datasheet of your flash chip +/// Example for Atmel-Adesto 4Mbit AT25DF041A: 0x1F44 (page 27: http://www.adestotech.com/sites/default/files/datasheets/doc3668.pdf) +/// Example for Winbond 4Mbit W25X40CL: 0xEF30 (page 14: http://www.winbond.com/NR/rdonlyres/6E25084C-0BFE-4B25-903D-AE10221A0929/0/W25X40CL.pdf) +SPIFlash::SPIFlash(uint8_t slaveSelectPin, uint16_t jedecID) { + _slaveSelectPin = slaveSelectPin; + _jedecID = jedecID; +} + +/// Select the flash chip +void SPIFlash::select() { + noInterrupts(); + //save current SPI settings + _SPCR = SPCR; + _SPSR = SPSR; + //set FLASH chip SPI settings + SPI.setDataMode(SPI_MODE0); + SPI.setBitOrder(MSBFIRST); + SPI.setClockDivider(SPI_CLOCK_DIV4); //decided to slow down from DIV2 after SPI stalling in some instances, especially visible on mega1284p when RFM69 and FLASH chip both present + SPI.begin(); + digitalWrite(_slaveSelectPin, LOW); +} + +/// UNselect the flash chip +void SPIFlash::unselect() { + digitalWrite(_slaveSelectPin, HIGH); + //restore SPI settings to what they were before talking to the FLASH chip + SPCR = _SPCR; + SPSR = _SPSR; + interrupts(); +} + +/// setup SPI, read device ID etc... +boolean SPIFlash::initialize() +{ + _SPCR = SPCR; + _SPSR = SPSR; + pinMode(_slaveSelectPin, OUTPUT); + unselect(); + wakeup(); + + if (_jedecID == 0 || readDeviceId() == _jedecID) { + command(SPIFLASH_STATUSWRITE, true); // Write Status Register + SPI.transfer(0); // Global Unprotect + unselect(); + return true; + } + return false; +} + +/// Get the manufacturer and device ID bytes (as a short word) +word SPIFlash::readDeviceId() +{ +#if defined(__AVR_ATmega32U4__) // Arduino Leonardo, MoteinoLeo + command(SPIFLASH_IDREAD); // Read JEDEC ID +#else + select(); + SPI.transfer(SPIFLASH_IDREAD); +#endif + word jedecid = SPI.transfer(0) << 8; + jedecid |= SPI.transfer(0); + unselect(); + return jedecid; +} + +/// Get the 64 bit unique identifier, stores it in UNIQUEID[8]. Only needs to be called once, ie after initialize +/// Returns the byte pointer to the UNIQUEID byte array +/// Read UNIQUEID like this: +/// flash.readUniqueId(); for (byte i=0;i<8;i++) { Serial.print(flash.UNIQUEID[i], HEX); Serial.print(' '); } +/// or like this: +/// flash.readUniqueId(); byte* MAC = flash.readUniqueId(); for (byte i=0;i<8;i++) { Serial.print(MAC[i], HEX); Serial.print(' '); } +byte* SPIFlash::readUniqueId() +{ + command(SPIFLASH_MACREAD); + SPI.transfer(0); + SPI.transfer(0); + SPI.transfer(0); + SPI.transfer(0); + for (byte i=0;i<8;i++) + UNIQUEID[i] = SPI.transfer(0); + unselect(); + return UNIQUEID; +} + +/// read 1 byte from flash memory +byte SPIFlash::readByte(long addr) { + command(SPIFLASH_ARRAYREADLOWFREQ); + SPI.transfer(addr >> 16); + SPI.transfer(addr >> 8); + SPI.transfer(addr); + byte result = SPI.transfer(0); + unselect(); + return result; +} + +/// read unlimited # of bytes +void SPIFlash::readBytes(long addr, void* buf, word len) { + command(SPIFLASH_ARRAYREAD); + SPI.transfer(addr >> 16); + SPI.transfer(addr >> 8); + SPI.transfer(addr); + SPI.transfer(0); //"dont care" + for (word i = 0; i < len; ++i) + ((byte*) buf)[i] = SPI.transfer(0); + unselect(); +} + +/// Send a command to the flash chip, pass TRUE for isWrite when its a write command +void SPIFlash::command(byte cmd, boolean isWrite){ +#if defined(__AVR_ATmega32U4__) // Arduino Leonardo, MoteinoLeo + DDRB |= B00000001; // Make sure the SS pin (PB0 - used by RFM12B on MoteinoLeo R1) is set as output HIGH! + PORTB |= B00000001; +#endif + if (isWrite) + { + command(SPIFLASH_WRITEENABLE); // Write Enable + unselect(); + } + //wait for any write/erase to complete + // a time limit cannot really be added here without it being a very large safe limit + // that is because some chips can take several seconds to carry out a chip erase or other similar multi block or entire-chip operations + // a recommended alternative to such situations where chip can be or not be present is to add a 10k or similar weak pulldown on the + // open drain MISO input which can read noise/static and hence return a non 0 status byte, causing the while() to hang when a flash chip is not present + while(busy()); + select(); + SPI.transfer(cmd); +} + +/// check if the chip is busy erasing/writing +boolean SPIFlash::busy() +{ + /* + select(); + SPI.transfer(SPIFLASH_STATUSREAD); + byte status = SPI.transfer(0); + unselect(); + return status & 1; + */ + return readStatus() & 1; +} + +/// return the STATUS register +byte SPIFlash::readStatus() +{ + select(); + SPI.transfer(SPIFLASH_STATUSREAD); + byte status = SPI.transfer(0); + unselect(); + return status; +} + + +/// Write 1 byte to flash memory +/// WARNING: you can only write to previously erased memory locations (see datasheet) +/// use the block erase commands to first clear memory (write 0xFFs) +void SPIFlash::writeByte(long addr, uint8_t byt) { + command(SPIFLASH_BYTEPAGEPROGRAM, true); // Byte/Page Program + SPI.transfer(addr >> 16); + SPI.transfer(addr >> 8); + SPI.transfer(addr); + SPI.transfer(byt); + unselect(); +} + +/// write 1-256 bytes to flash memory +/// WARNING: you can only write to previously erased memory locations (see datasheet) +/// use the block erase commands to first clear memory (write 0xFFs) +/// WARNING: if you write beyond a page boundary (or more than 256bytes), +/// the bytes will wrap around and start overwriting at the beginning of that same page +/// see datasheet for more details +void SPIFlash::writeBytes(long addr, const void* buf, uint16_t len) { + command(SPIFLASH_BYTEPAGEPROGRAM, true); // Byte/Page Program + SPI.transfer(addr >> 16); + SPI.transfer(addr >> 8); + SPI.transfer(addr); + for (uint16_t i = 0; i < len; i++) + SPI.transfer(((byte*) buf)[i]); + unselect(); +} + +/// erase entire flash memory array +/// may take several seconds depending on size, but is non blocking +/// so you may wait for this to complete using busy() or continue doing +/// other things and later check if the chip is done with busy() +/// note that any command will first wait for chip to become available using busy() +/// so no need to do that twice +void SPIFlash::chipErase() { + command(SPIFLASH_CHIPERASE, true); + unselect(); +} + +/// erase a 4Kbyte block +void SPIFlash::blockErase4K(long addr) { + command(SPIFLASH_BLOCKERASE_4K, true); // Block Erase + SPI.transfer(addr >> 16); + SPI.transfer(addr >> 8); + SPI.transfer(addr); + unselect(); +} + +/// erase a 32Kbyte block +void SPIFlash::blockErase32K(long addr) { + command(SPIFLASH_BLOCKERASE_32K, true); // Block Erase + SPI.transfer(addr >> 16); + SPI.transfer(addr >> 8); + SPI.transfer(addr); + unselect(); +} + +void SPIFlash::sleep() { + command(SPIFLASH_SLEEP); + unselect(); +} + +void SPIFlash::wakeup() { + command(SPIFLASH_WAKE); + unselect(); +} + +/// cleanup +void SPIFlash::end() { + SPI.end(); +} \ No newline at end of file diff --git a/libraries/MySensors/utility/SPIFlash.h b/libraries/MySensors/utility/SPIFlash.h new file mode 100644 index 0000000..f0c071a --- /dev/null +++ b/libraries/MySensors/utility/SPIFlash.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2013 by Felix Rusu + * SPI Flash memory library for arduino/moteino. + * This works with 256byte/page SPI flash memory + * For instance a 4MBit (512Kbyte) flash chip will have 2048 pages: 256*2048 = 524288 bytes (512Kbytes) + * Minimal modifications should allow chips that have different page size but modifications + * DEPENDS ON: Arduino SPI library + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef _SPIFLASH_H_ +#define _SPIFLASH_H_ + +#if ARDUINO >= 100 +#include +#else +#include +#include "pins_arduino.h" +#endif + +#include + +/// IMPORTANT: NAND FLASH memory requires erase before write, because +/// it can only transition from 1s to 0s and only the erase command can reset all 0s to 1s +/// See http://en.wikipedia.org/wiki/Flash_memory +/// The smallest range that can be erased is a sector (4K, 32K, 64K); there is also a chip erase command + +/// Standard SPI flash commands +/// Assuming the WP pin is pulled up (to disable hardware write protection) +/// To use any write commands the WEL bit in the status register must be set to 1. +/// This is accomplished by sending a 0x06 command before any such write/erase command. +/// The WEL bit in the status register resets to the logical “0” state after a +/// device power-up or reset. In addition, the WEL bit will be reset to the logical “0” state automatically under the following conditions: +/// • Write Disable operation completes successfully +/// • Write Status Register operation completes successfully or aborts +/// • Protect Sector operation completes successfully or aborts +/// • Unprotect Sector operation completes successfully or aborts +/// • Byte/Page Program operation completes successfully or aborts +/// • Sequential Program Mode reaches highest unprotected memory location +/// • Sequential Program Mode reaches the end of the memory array +/// • Sequential Program Mode aborts +/// • Block Erase operation completes successfully or aborts +/// • Chip Erase operation completes successfully or aborts +/// • Hold condition aborts +#define SPIFLASH_WRITEENABLE 0x06 // write enable +#define SPIFLASH_WRITEDISABLE 0x04 // write disable + +#define SPIFLASH_BLOCKERASE_4K 0x20 // erase one 4K block of flash memory +#define SPIFLASH_BLOCKERASE_32K 0x52 // erase one 32K block of flash memory +#define SPIFLASH_BLOCKERASE_64K 0xD8 // erase one 64K block of flash memory +#define SPIFLASH_CHIPERASE 0x60 // chip erase (may take several seconds depending on size) + // but no actual need to wait for completion (instead need to check the status register BUSY bit) +#define SPIFLASH_STATUSREAD 0x05 // read status register +#define SPIFLASH_STATUSWRITE 0x01 // write status register +#define SPIFLASH_ARRAYREAD 0x0B // read array (fast, need to add 1 dummy byte after 3 address bytes) +#define SPIFLASH_ARRAYREADLOWFREQ 0x03 // read array (low frequency) + +#define SPIFLASH_SLEEP 0xB9 // deep power down +#define SPIFLASH_WAKE 0xAB // deep power wake up +#define SPIFLASH_BYTEPAGEPROGRAM 0x02 // write (1 to 256bytes) +#define SPIFLASH_IDREAD 0x9F // read JEDEC manufacturer and device ID (2 bytes, specific bytes for each manufacturer and device) + // Example for Atmel-Adesto 4Mbit AT25DF041A: 0x1F44 (page 27: http://www.adestotech.com/sites/default/files/datasheets/doc3668.pdf) + // Example for Winbond 4Mbit W25X40CL: 0xEF30 (page 14: http://www.winbond.com/NR/rdonlyres/6E25084C-0BFE-4B25-903D-AE10221A0929/0/W25X40CL.pdf) +#define SPIFLASH_MACREAD 0x4B // read unique ID number (MAC) + +class SPIFlash { +public: + static byte UNIQUEID[8]; + SPIFlash(byte slaveSelectPin, uint16_t jedecID=0); + boolean initialize(); + void command(byte cmd, boolean isWrite=false); + byte readStatus(); + byte readByte(long addr); + void readBytes(long addr, void* buf, word len); + void writeByte(long addr, byte byt); + void writeBytes(long addr, const void* buf, uint16_t len); + boolean busy(); + void chipErase(); + void blockErase4K(long address); + void blockErase32K(long address); + word readDeviceId(); + byte* readUniqueId(); + + void sleep(); + void wakeup(); + void end(); +protected: + void select(); + void unselect(); + byte _slaveSelectPin; + uint16_t _jedecID; + byte _SPCR; + byte _SPSR; +}; + +#endif \ No newline at end of file