adding sleep/wakeup functions, moving WirelessHEX

This commit is contained in:
Felix Rusu 2013-07-13 23:07:08 -04:00
parent 722fe3c3b8
commit 53156895f3
6 changed files with 42 additions and 480 deletions

View File

@ -14,6 +14,11 @@
#include <SPIFlash.h> #include <SPIFlash.h>
/// 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 /// 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 /// 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 Atmel-Adesto 4Mbit AT25DF041A: 0x1F44 (page 27: http://www.adestotech.com/sites/default/files/datasheets/doc3668.pdf)
@ -184,6 +189,16 @@ void SPIFlash::blockErase32K(long addr) {
unselect(); unselect();
} }
void SPIFlash::sleep() {
command(SPIFLASH_SLEEP); // Block Erase
unselect();
}
void SPIFlash::wakeup() {
command(SPIFLASH_WAKE); // Block Erase
unselect();
}
/// cleanup /// cleanup
void SPIFlash::end() { void SPIFlash::end() {
SPI.end(); SPI.end();

View File

@ -24,23 +24,28 @@
#include <SPI.h> #include <SPI.h>
/// 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 /// Standard SPI flash commands
/// Assuming the WP pin is pulled up (to disable hardware write protection) /// 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. /// 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. /// 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 /// 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: /// 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 Disable operation completes successfully
/// • Write Status Register operation completes successfully or aborts /// • Write Status Register operation completes successfully or aborts
/// • Protect Sector operation completes successfully or aborts /// • Protect Sector operation completes successfully or aborts
/// • Unprotect Sector operation completes successfully or aborts /// • Unprotect Sector operation completes successfully or aborts
/// • Byte/Page Program 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 highest unprotected memory location
/// • Sequential Program Mode reaches the end of the memory array /// • Sequential Program Mode reaches the end of the memory array
/// • Sequential Program Mode aborts /// • Sequential Program Mode aborts
/// • Block Erase operation completes successfully or aborts /// • Block Erase operation completes successfully or aborts
/// • Chip Erase operation completes successfully or aborts /// • Chip Erase operation completes successfully or aborts
/// • Hold condition aborts /// • Hold condition aborts
#define SPIFLASH_WRITEENABLE 0x06 // write enable #define SPIFLASH_WRITEENABLE 0x06 // write enable
#define SPIFLASH_WRITEDISABLE 0x04 // write disable #define SPIFLASH_WRITEDISABLE 0x04 // write disable
@ -76,6 +81,8 @@ public:
void blockErase4K(long address); void blockErase4K(long address);
void blockErase32K(long address); void blockErase32K(long address);
word readDeviceId(); word readDeviceId();
void sleep();
void wakeup();
void end(); void end();
protected: protected:
void select(); void select();

View File

@ -1,407 +0,0 @@
/*
* Copyright (c) 2013 by Felix Rusu <felix@lowpowerlab.com>
* Library for facilitating wireless programming using an RFM12B radio (get library at: https://github.com/LowPowerLab/RFM12B)
* and the SPI Flash memory library for arduino/moteino (get library at: TODO).
* DEPENDS ON the two libraries mentioned above
* Install all three of these libraries in your Arduino/libraries folder ([Arduino > Preferences] for location of Arduino folder)
*
* 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 <WirelessHEX.h>
#include <avr/wdt.h>
/// Checks whether the last message received was a wireless programming request handshake
/// If so it will start the handshake protocol, receive the new HEX image and
/// store it on the external flash chip, then reboot
/// Assumes radio has been initialized and has just received a message (is not in SLEEP mode, and you called CRCPass())
/// Assumes flash is an external SPI flash memory chip that has been initialized
void CheckForWirelessHEX(RFM12B radio, SPIFlash flash, boolean DEBUG)
{
//special FLASH command, enter a FLASH image exchange sequence
if (*radio.DataLen >= 4 && radio.Data[0]=='F' && radio.Data[1]=='L' && radio.Data[2]=='X' && radio.Data[3]=='?')
{
byte remoteID = radio.GetSender();
if (*radio.DataLen == 7 && radio.Data[4]=='E' && radio.Data[5]=='O' && radio.Data[6]=='F')
{ //sender must have not received EOF ACK so just resend
radio.Send(remoteID, "FLX?OK",6);
}
else if (HandleWirelessHEXData(radio, remoteID, flash, DEBUG))
{
if (DEBUG) Serial.print("FLASH IMG TRANSMISSION SUCCESS!\n");
resetUsingWatchdog(DEBUG);
}
else
{
if (DEBUG) Serial.print("Timeout, erasing written data ... ");
flash.blockErase32K(0); //clear any written data
if (DEBUG) Serial.println("DONE");
}
}
}
boolean HandleWirelessHEXData(RFM12B radio, byte remoteID, SPIFlash flash, boolean DEBUG) {
uint8_t c;
long now=0;
uint16_t tmp,seq=0,len;
char buffer[16];
int timeout = 3000; //3s for flash data
uint16_t bytesFlashed=10;
radio.SendACK("FLX?OK",6); //ACK the HANDSHAKE
if (DEBUG) Serial.println("FLX?OK (ACK sent)");
//first clear the fist 32k block (dedicated to a new FLASH image)
flash.blockErase32K(0);
flash.writeBytes(0,"FLXIMG:", 7);
flash.writeByte(9,':');
now=millis();
while(1)
{
if (radio.ReceiveComplete() && radio.CRCPass() && radio.GetSender() == remoteID)
{
byte dataLen = *radio.DataLen;
if (dataLen >= 4 && radio.Data[0]=='F' && radio.Data[1]=='L' && radio.Data[2]=='X')
{
if (radio.Data[3]==':' && dataLen >= 7) //FLX:_:_
{
byte index=3;
tmp = 0;
//read packet SEQ
for (byte i = 4; i<8; i++) //up to 4 characters for seq number
{
index++;
if (radio.Data[i] >=48 && radio.Data[i]<=57)
tmp = tmp*10+radio.Data[i]-48;
else if (radio.Data[i]==':')
{
if (i==4)
return false;
else break;
}
}
if (DEBUG) {
Serial.print("radio [");
Serial.print(dataLen);
Serial.print("] > ");
PrintHex83((byte*)radio.Data, dataLen);
}
if (radio.Data[index++] != ':') return false;
now = millis(); //got "good" packet
if (tmp==seq)
{
for(byte i=index;i<dataLen;i++)
flash.writeByte(bytesFlashed++, radio.Data[i]);
//send ACK
tmp = sprintf(buffer, "FLX:%u:OK", tmp);
if (DEBUG) Serial.println((char*)buffer);
radio.SendACK(buffer, tmp);
seq++;
}
}
if (radio.Data[3]=='?')
{
if (dataLen==4) //ACK for handshake was lost, resend
{
radio.SendACK("FLX?OK",6);
if (DEBUG) Serial.println("FLX?OK");
}
if (dataLen==7 && radio.Data[4]=='E' && radio.Data[5]=='O' && radio.Data[6]=='F') //Expected EOF
{
if ((bytesFlashed-10)>31744) {
if (DEBUG) Serial.println("IMG exceeds 31k");
return false; //just return, let MAIN timeout
}
radio.SendACK("FLX?OK",6);
if (DEBUG) Serial.println("FLX?OK");
//save # of bytes written
flash.writeByte(7,(bytesFlashed-10)>>8);
flash.writeByte(8,(bytesFlashed-10));
flash.writeByte(9,':');
return true;
}
}
}
#ifdef LED //blink!
pinMode(LED,OUTPUT); digitalWrite(LED,HIGH); delay(1); digitalWrite(LED,LOW);
#endif
}
//abort FLASH sequence if no valid packet received for a long time
if (millis()-now > timeout)
{
return false;
}
}
}
// reads a line feed (\n) terminated line from the serial stream
// returns # of bytes read, up to 255
// timeout in ms, will timeout and return after so long
byte readSerialLine(char* input, char endOfLineChar, byte maxLength, uint16_t timeout)
{
byte inputLen = 0;
Serial.setTimeout(timeout);
inputLen = Serial.readBytesUntil(endOfLineChar, input, maxLength);
input[inputLen]=0;//null-terminate it
Serial.setTimeout(0);
//Serial.println();
return inputLen;
}
/// returns TRUE if a HEX file transmission was detected and it was actually transmitted successfully
boolean CheckForSerialHEX(byte* input, byte inputLen, RFM12B radio, byte targetID, uint16_t TIMEOUT, uint16_t ACKTIMEOUT, boolean DEBUG)
{
if (inputLen == 4 && input[0]=='F' && input[1]=='L' && input[2]=='X' && input[3]=='?') {
if (HandleSerialHandshake(radio, targetID, false, TIMEOUT, ACKTIMEOUT, DEBUG))
{
Serial.println("FLX?OK"); //signal serial handshake back to host script
if (HandleSerialHEXData(radio, targetID, TIMEOUT, ACKTIMEOUT, DEBUG))
{
Serial.println("FLX?OK"); //signal EOF serial handshake back to host script
if (DEBUG) Serial.println("FLASH IMG TRANSMISSION SUCCESS");
return true;
}
if (DEBUG) Serial.println("FLASH IMG TRANSMISSION FAIL");
return false;
}
}
return false;
}
boolean HandleSerialHandshake(RFM12B radio, byte targetID, boolean isEOF, uint16_t TIMEOUT, uint16_t ACKTIMEOUT, boolean DEBUG)
{
//temp
//return true;
byte retries = 3;
long now = millis();
while (millis()-now<TIMEOUT)
{
radio.Send(targetID, isEOF ? "FLX?EOF" : "FLX?", isEOF?7:4, true);
if (waitForAck(radio, ACKTIMEOUT))
{
if (*radio.DataLen == 6 && radio.Data[0]=='F' && radio.Data[1]=='L' && radio.Data[2]=='X' &&
radio.Data[3]=='?' && radio.Data[4]=='O' && radio.Data[5]=='K')
return true;
}
}
if (DEBUG) Serial.println("Handshake fail");
return false;
}
boolean HandleSerialHEXData(RFM12B radio, byte targetID, uint16_t TIMEOUT, uint16_t ACKTIMEOUT, boolean DEBUG) {
long now=millis();
uint16_t seq=0, tmp=0, inputLen;
byte remoteID = radio.GetSender(); //save the remoteID as soon as possible
byte sendBuf[32];
char input[64];
//a FLASH record should not be more than 64 bytes: FLX:9999:10042000FF4FA591B4912FB7F894662321F48C91D6
while(1) {
inputLen = readSerialLine(input);
if (inputLen == 0) goto timeoutcheck;
tmp = 0;
if (inputLen >= 6) { //FLX:9:
if (input[0]=='F' && input[1]=='L' && input[2]=='X')
{
if (input[3]==':')
{
byte index = 3;
for (byte i = 4; i<8; i++) //up to 4 characters for seq number
{
index++;
if (input[i] >=48 && input[i]<=57)
tmp = tmp*10+input[i]-48;
else if (input[i]==':')
{
if (i==4)
return false;
else break;
}
}
//Serial.print("input[index] = ");Serial.print("[");Serial.print(index);Serial.print("]=");Serial.println(input[index]);
if (input[index++] != ':') return false;
now = millis(); //got good packet
byte hexDataLen = validateHEXData(input+index, inputLen-index);
if (hexDataLen>0)
{
if (tmp==seq) //only read data when packet number is the next expected SEQ number
{
byte sendBufLen = prepareSendBuffer(input+index+8, sendBuf, hexDataLen, seq); //extract HEX data from input to BYTE data into sendBuf (go from 2 HEX bytes to 1 byte), +8 will jump over the header directly to the HEX raw data
//Serial.print("PREP ");Serial.print(sendBufLen); Serial.print(" > "); PrintHex83(sendBuf, sendBufLen);
//SEND RADIO DATA
if (sendHEXPacket(radio, remoteID, sendBuf, sendBufLen, seq, TIMEOUT, ACKTIMEOUT, DEBUG))
{
sprintf((char*)sendBuf, "FLX:%u:OK",seq);
Serial.println((char*)sendBuf); //response to host (python?)
seq++;
}
else return false;
}
}
else Serial.println("FLX:INV");
}
if (inputLen==7 && input[3]=='?' && input[4]=='E' && input[5]=='O' && input[6]=='F')
{
//SEND RADIO EOF
return HandleSerialHandshake(radio, targetID, true, TIMEOUT, ACKTIMEOUT, DEBUG);
}
}
}
//abort FLASH sequence if no valid packet received for a long time
timeoutcheck:
if (millis()-now > TIMEOUT)
{
Serial.print("Timeout receiving FLASH image from SERIAL, aborting...");
//send abort msg or just let node timeout as well?
return false;
}
}
return true;
}
//returns length of HEX data bytes if everything is valid
//returns 0 if any validation failed
byte validateHEXData(void* data, byte length)
{
//assuming 1 byte record length, 2 bytes address, 1 byte record type, N data bytes, 1 CRC byte
char* input = (char*)data;
if (length <12 || length%2!=0) return 0; //shortest possible intel data HEX record is 12 bytes
//Serial.print("VAL > "); Serial.println((char*)input);
uint16_t checksum=0;
//check valid HEX data and CRC
for (byte i=0; i<length;i++)
{
if (!((input[i] >=48 && input[i]<=57) || (input[i] >=65 && input[i]<=70))) //0-9,A-F
return 0;
if (i%2 && i<length-2) checksum+=BYTEfromHEX(input[i-1], input[i]);
}
checksum=(checksum^0xFFFF)+1;
//TODO : CHECK for address continuity (intel HEX addresses are big endian)
//Serial.print("final CRC:");Serial.println((byte)checksum, HEX);
//Serial.print("CRC byte:");Serial.println(BYTEfromHEX(input[length-2], input[length-1]), HEX);
//check CHECKSUM byte
if (((byte)checksum) != BYTEfromHEX(input[length-2], input[length-1]))
return false;
byte dataLength = BYTEfromHEX(input[0], input[1]); //length of actual HEX flash data (usually 16bytes)
//calculate record length
if (length != dataLength*2 + 10) //add headers and checksum bytes (a total of 10 combined)
return 0;
return dataLength; //all validation OK!
}
//returns the final size of the buf
byte prepareSendBuffer(char* hexdata, byte*buf, byte length, uint16_t seq)
{
byte seqLen = sprintf(((char*)buf), "FLX:%u:", seq);
for (byte i=0; i<length;i++)
buf[seqLen+i] = BYTEfromHEX(hexdata[i*2], hexdata[i*2+1]);
return seqLen+length;
}
//assume A and B are valid HEX chars [0-9A-F]
byte BYTEfromHEX(char MSB, char LSB)
{
return (MSB>=65?MSB-55:MSB-48)*16 + (LSB>=65?LSB-55:LSB-48);
}
//return the SEQ of the ACK received, or -1 if invalid
boolean sendHEXPacket(RFM12B radio, byte targetID, byte* sendBuf, byte hexDataLen, uint16_t seq, uint16_t TIMEOUT, uint16_t ACKTIMEOUT, boolean DEBUG)
{
long now = millis();
while(1) {
if (DEBUG) { Serial.print("RFTX > "); PrintHex83(sendBuf, hexDataLen); }
radio.Send(targetID, sendBuf, hexDataLen, true);
if (waitForAck(radio, ACKTIMEOUT))
{
byte ackLen = *radio.DataLen;
if (DEBUG) { Serial.print("RFACK > "); Serial.print(ackLen); Serial.print(" > "); PrintHex83((byte*)radio.Data, ackLen); }
if (ackLen >= 8 && radio.Data[0]=='F' && radio.Data[1]=='L' && radio.Data[2]=='X' &&
radio.Data[3]==':' && radio.Data[ackLen-3]==':' &&
radio.Data[ackLen-2]=='O' && radio.Data[ackLen-1]=='K')
{
uint16_t tmp=0;
sscanf((const char*)radio.Data, "FLX:%u:OK", &tmp);
return tmp == seq;
}
}
if (millis()-now > TIMEOUT)
{
Serial.println("Timeout waiting for packet ACK, aborting FLASH operation ...");
break; //abort FLASH sequence if no valid ACK was received for a long time
}
}
return false;
}
// wait a few milliseconds for proper ACK to me, return true if indeed received
boolean waitForAck(RFM12B radio, uint16_t ACKTIMEOUT)
{
long now = millis();
while (millis() - now <= ACKTIMEOUT) {
if (radio.ACKReceived(radio.GetSender()))
return true;
}
return false;
}
void PrintHex83(uint8_t *data, uint8_t length) // prints 8-bit data in hex
{
char tmp[length*2+1];
byte first ;
int j=0;
for (uint8_t i=0; i<length; i++)
{
first = (data[i] >> 4) | 48;
if (first > 57) tmp[j] = first + (byte)39;
else tmp[j] = first ;
j++;
first = (data[i] & 0x0F) | 48;
if (first > 57) tmp[j] = first + (byte)39;
else tmp[j] = first;
j++;
}
tmp[length*2] = 0;
Serial.println(tmp);
}
/// Use watchdog to reset
void resetUsingWatchdog(boolean DEBUG)
{
//wdt_disable();
if (DEBUG) Serial.print("REBOOTING");
wdt_enable(WDTO_15MS);
while(1) if (DEBUG) Serial.print('.');
}

View File

@ -1,47 +0,0 @@
/*
* Copyright (c) 2013 by Felix Rusu <felix@lowpowerlab.com>
* Library for facilitating wireless programming using an RFM12B radio (get library at: http://github.com/LowPowerLab/RFM12B)
* and the SPI Flash memory library for arduino/moteino (get library at: http://github.com/LowPowerLab/SPIFlash)
* DEPENDS ON the two libraries mentioned above, and comes bundled with the SPIFlash library (above link)
* Install all three of these libraries in your Arduino/libraries folder ([Arduino > Preferences] for location of Arduino folder)
*
* 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 _WirelessHEX_H_
#define _WirelessHEX_H_
#define LED 9 //LED on digital pin 9
#ifndef DEFAULT_TIMEOUT
#define DEFAULT_TIMEOUT 3000
#endif
#ifndef ACK_TIMEOUT
#define ACK_TIMEOUT 20
#endif
#include <RFM12B.h>
#include <SPIFlash.h>
//functions used in the REMOTE node
void CheckForWirelessHEX(RFM12B radio, SPIFlash flash, boolean DEBUG=false);
void resetUsingWatchdog(boolean DEBUG=false);
boolean HandleWirelessHEXData(RFM12B radio, byte remoteID, SPIFlash flash, boolean DEBUG=false);
//functions used in the MAIN node
boolean CheckForSerialHEX(byte* input, byte inputLen, RFM12B radio, byte targetID, uint16_t TIMEOUT=DEFAULT_TIMEOUT, uint16_t ACKTIMEOUT=ACK_TIMEOUT, boolean DEBUG=false);
boolean HandleSerialHandshake(RFM12B radio, byte targetID, boolean isEOF, uint16_t TIMEOUT=DEFAULT_TIMEOUT, uint16_t ACKTIMEOUT=ACK_TIMEOUT, boolean DEBUG=false);
boolean HandleSerialHEXData(RFM12B radio, byte targetID, uint16_t TIMEOUT=DEFAULT_TIMEOUT, uint16_t ACKTIMEOUT=ACK_TIMEOUT, boolean DEBUG=false);
boolean waitForAck(RFM12B radio, uint16_t ACKTIMEOUT=ACK_TIMEOUT);
byte validateHEXData(void* data, byte length);
byte prepareSendBuffer(char* hexdata, byte*buf, byte length, uint16_t seq);
boolean sendHEXPacket(RFM12B radio, byte remoteID, byte* sendBuf, byte hexDataLen, uint16_t seq, uint16_t TIMEOUT=DEFAULT_TIMEOUT, uint16_t ACKTIMEOUT=ACK_TIMEOUT, boolean DEBUG=false);
byte BYTEfromHEX(char MSB, char LSB);
byte readSerialLine(char* input, char endOfLineChar=10, byte maxLength=64, uint16_t timeout=1000);
void PrintHex83(byte* data, byte length);
#endif

View File

@ -1,5 +1,4 @@
SPIFlash KEYWORD1 SPIFlash KEYWORD1
WirelessHEX KEYWORD1
initialize KEYWORD2 initialize KEYWORD2
command KEYWORD2 command KEYWORD2
readStatus KEYWORD2 readStatus KEYWORD2
@ -12,11 +11,6 @@ chipErase KEYWORD2
blockErase4K KEYWORD2 blockErase4K KEYWORD2
blockErase32K KEYWORD2 blockErase32K KEYWORD2
readDeviceId KEYWORD2 readDeviceId KEYWORD2
sleep KEYWORD2
wakeup KEYWORD2
end KEYWORD2 end KEYWORD2
CheckForWirelessHEX KEYWORD2
HandleWirelessHEXData KEYWORD2
readSerialLine KEYWORD2
BYTEfromHEX KEYWORD2
waitForAck KEYWORD2
PrintHex83 KEYWORD2
resetUsingWatchdog KEYWORD2