Merge remote-tracking branch 'upstream/master'

- Manually resolved RFM69.cpp

Conflicts:
	RFM69.cpp
This commit is contained in:
Ryan Pipkin 2015-08-22 20:03:51 -05:00
commit c3c9c7df1b
25 changed files with 4049 additions and 1322 deletions

View File

@ -0,0 +1,222 @@
// **********************************************************************************
// DoorBellMote sketch works with Moteinos equipped with RFM69W/RFM69HW
// Can be adapted to use Moteinos/Arduinos using RFM12B or other RFM69 variants (RFM69CW, RFM69HCW)
// http://www.LowPowerLab.com/
// 2015-07-22 (C) Felix Rusu of http://www.LowPowerLab.com/
// **********************************************************************************
// It detects current flow at the doorbell transformer and send a message each time to the gateway
// It can trigger doorbell through a relay powered from pins D6+D7
// Deploy and forget: wirelessly programmable via Moteino + WirelessHEX69 library
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
// **********************************************************************************
#include <RFM69.h> //get it here: http://github.com/lowpowerlab/rfm69
#include <SPIFlash.h> //get it here: http://github.com/lowpowerlab/spiflash
#include <WirelessHEX69.h> //get it here: https://github.com/LowPowerLab/WirelessProgramming
#include <SPI.h> //comes with Arduino IDE (www.arduino.cc)
//*****************************************************************************************************************************
// ADJUST THE SETTINGS BELOW DEPENDING ON YOUR HARDWARE/SITUATION!
//*****************************************************************************************************************************
#define GATEWAYID 1
#define NODEID 133
#define NETWORKID 100
//#define FREQUENCY RF69_433MHZ
//#define FREQUENCY RF69_868MHZ
#define FREQUENCY RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
#define ENCRYPTKEY "sampleEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less!
//#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W!
#define CHIMEPIN 4 // active HIGH chime signal from detector H11AA1 circuit
#define RELAYPIN1 6 //for the bell ring relay we just need 2 digital pins together to activate the relay for a short pulse
#define RELAYPIN2 7 //for the bell ring relay we just need 2 digital pins together to activate the relay for a short pulse
#define DISABLE_RELAY 5 //for the bell disable relay we use a single digital pin through a transistor
#define RELAY_PULSE_MS 250 //just enough that the doorbell chime will trigger
#define RINGDELAY 3000 //time between rings (avoid fast repeated rings)
//*****************************************************************************************************************************
#define LED 9 //pin connected to onboard LED
#define SERIAL_BAUD 115200
#define SERIAL_EN //comment out if you don't want any serial output
#ifdef SERIAL_EN
#define DEBUG(input) Serial.print(input)
#define DEBUGln(input) Serial.println(input)
#else
#define DEBUG(input)
#define DEBUGln(input)
#endif
RFM69 radio;
/////////////////////////////////////////////////////////////////////////////
// flash(SPI_CS, MANUFACTURER_ID)
// SPI_CS - CS pin attached to SPI flash chip (8 in case of Moteino)
// MANUFACTURER_ID - OPTIONAL, 0xEF30 for windbond 4mbit flash (Moteino OEM)
/////////////////////////////////////////////////////////////////////////////
SPIFlash flash(8, 0xEF30); //regular Moteinos have FLASH MEM on D8, MEGA has it on D4
char buff[50];
void setup(void)
{
Serial.begin(SERIAL_BAUD);
pinMode(CHIMEPIN, INPUT);
pinMode(RELAYPIN1, OUTPUT);
pinMode(RELAYPIN2, OUTPUT);
pinMode(DISABLE_RELAY, OUTPUT);
pinMode(LED, OUTPUT);
radio.initialize(FREQUENCY,NODEID,NETWORKID);
#ifdef IS_RFM69HW
radio.setHighPower(); //uncomment only for RFM69HW!
#endif
radio.encrypt(ENCRYPTKEY);
sprintf(buff, "DoorBellMote : %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
DEBUGln(buff);
if (flash.initialize())
DEBUGln("SPI Flash Init OK");
else
DEBUGln("SPI Flash Init FAIL! (is chip present?)");
radio.sendWithRetry(GATEWAYID, "START", 6);
Blink(LED, 100);
Blink(LED, 100);
Blink(LED, 100);
}
uint32_t doorPulseCount = 0;
uint32_t lastStatusTimestamp=0;
uint32_t LEDCYCLETIMER=0;
byte LEDSTATE=LOW;
char input;
boolean ring=false;
boolean disable=false;
byte disableStatus=0;
void loop()
{
if (Serial.available())
input = Serial.read();
if (input=='r')
{
DEBUGln("Relay test...");
pulseRelay();
input = 0;
}
if (millis()-(lastStatusTimestamp)>RINGDELAY)
{
if (digitalRead(CHIMEPIN) == HIGH)
{
lastStatusTimestamp = millis();
radio.sendWithRetry(GATEWAYID, "RING", 4);
Blink(LED,20);
Blink(LED,20);
Blink(LED,20);
}
}
if (radio.receiveDone())
{
DEBUG('[');DEBUG(radio.SENDERID);DEBUG("] ");
for (byte i = 0; i < radio.DATALEN; i++)
DEBUG((char)radio.DATA[i]);
if (radio.DATALEN==4)
if (radio.DATA[0]=='R' && radio.DATA[1]=='I' && radio.DATA[2]=='N' && radio.DATA[3]=='G')
ring = true;
if (radio.DATALEN==6)
if (radio.DATA[0]=='B' && radio.DATA[1]=='E' && radio.DATA[2]=='L' && radio.DATA[3]=='L' && radio.DATA[4]==':')
if (radio.DATA[5]=='0')
{
disableStatus = 1;
disable = true;
}
else if (radio.DATA[5]=='1')
{
disableStatus = 0;
disable = true;
}
// wireless programming token check
// DO NOT REMOVE, or GarageMote will not be wirelessly programmable any more!
CheckForWirelessHEX(radio, flash, true);
//first send any ACK to request
DEBUG(" [RX_RSSI:");DEBUG(radio.RSSI);DEBUG("]");
if (radio.ACKRequested())
{
radio.sendACK();
DEBUG(" - ACK sent.");
}
if (ring)
{
//if other relay is ON we must temporarily turn it off while we pulse the RING relay, to avoid any rail collapse and reset
if (disableStatus) digitalWrite(DISABLE_RELAY, 0);
pulseRelay();
if (disableStatus) digitalWrite(DISABLE_RELAY, 1);
radio.sendWithRetry(GATEWAYID, "RING OK", 4);
ring = false;
}
if (disable)
{
digitalWrite(DISABLE_RELAY, disableStatus); //disable it
sprintf(buff, "BELL:%d", disableStatus ? 0 : 1);
radio.sendWithRetry(GATEWAYID, buff, 6);
disable=false;
}
DEBUGln();
}
if (millis() - LEDCYCLETIMER > 2000) //flip onboard LED state every so often to indicate activity
{
LEDCYCLETIMER = millis();
LEDSTATE = !LEDSTATE;
digitalWrite(LED, LEDSTATE);
}
}
void pulseRelay()
{
digitalWrite(RELAYPIN1, HIGH);
digitalWrite(RELAYPIN2, HIGH);
delay(RELAY_PULSE_MS);
digitalWrite(RELAYPIN1, LOW);
digitalWrite(RELAYPIN2, LOW);
}
void Blink(byte PIN, byte DELAY_MS)
{
pinMode(PIN, OUTPUT);
digitalWrite(PIN,HIGH);
delay(DELAY_MS/2);
digitalWrite(PIN,LOW);
delay(DELAY_MS/2);
}

View File

@ -1,7 +1,8 @@
// ********************************************************************************************************** // **********************************************************************************************************
// GarageMote garage door controller sketch that works with Moteinos equipped with HopeRF RFM69W/RFM69HW // GarageMote garage door controller sketch that works with Moteinos equipped with RFM69W/RFM69HW
// Can be adapted to use Moteinos using RFM12B // Can be adapted to use Moteinos/Arduinos using RFM12B or other RFM69 variants (RFM69CW, RFM69HCW)
// 2014-07-14 (C) felix@lowpowerlab.com, http://www.LowPowerLab.com // http://www.LowPowerLab.com/GarageMote
// 2015-05-05 (C) Felix Rusu of http://www.LowPowerLab.com/
// ********************************************************************************************************** // **********************************************************************************************************
// It uses 2 hall effect sensors (and magnets mounted on the garage belt/chain) to detect the position of the // It uses 2 hall effect sensors (and magnets mounted on the garage belt/chain) to detect the position of the
// door, and a small signal relay to be able to toggle the garage opener. // door, and a small signal relay to be able to toggle the garage opener.
@ -11,28 +12,55 @@
// - solid OFF - door is in closed position // - solid OFF - door is in closed position
// - blinking - door is not in either open/close position // - blinking - door is not in either open/close position
// - pulsing - door is in motion // - pulsing - door is in motion
// **********************************************************************************************************
// Creative Commons Attrib Share-Alike License
// You are free to use/extend this code/library but please abide with the CCSA license:
// http://creativecommons.org/licenses/by-sa/4.0/
// ********************************************************************************** // **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
// ***************************************************************************************************************************
//#define WEATHERSHIELD //uncomment if WeatherShield is present to report temp/humidity/pressure periodically
//#define WEATHERSENDDELAY 300000 // send WeatherShield data every so often (ms)
// ***************************************************************************************************************************
#include <RFM69.h> //get it here: http://github.com/lowpowerlab/rfm69 #include <RFM69.h> //get it here: http://github.com/lowpowerlab/rfm69
#include <SPIFlash.h> //get it here: http://github.com/lowpowerlab/spiflash #include <SPIFlash.h> //get it here: http://github.com/lowpowerlab/spiflash
#include <WirelessHEX69.h> //get it here: https://github.com/LowPowerLab/WirelessProgramming #include <WirelessHEX69.h> //get it here: https://github.com/LowPowerLab/WirelessProgramming
#include <SPI.h> //comes with Arduino IDE (www.arduino.cc) #include <SPI.h> //comes with Arduino IDE (www.arduino.cc)
#ifdef WEATHERSHIELD
#include <SFE_BMP180.h> //get it here: https://github.com/LowPowerLab/SFE_BMP180
#include <SI7021.h> //get it here: https://github.com/LowPowerLab/SI7021
#include <Wire.h>
#endif
//***************************************************************************************************************************** //*****************************************************************************************************************************
// ADJUST THE SETTINGS BELOW DEPENDING ON YOUR HARDWARE/SITUATION! // ADJUST THE SETTINGS BELOW DEPENDING ON YOUR HARDWARE/TRANSCEIVER SETTINGS/REQUIREMENTS
//***************************************************************************************************************************** //*****************************************************************************************************************************
#define GATEWAYID 1 #define GATEWAYID 1
#define NODEID 122 #define NODEID 11
#define NETWORKID 100 #define NETWORKID 250
//#define FREQUENCY RF69_433MHZ //#define FREQUENCY RF69_433MHZ
//#define FREQUENCY RF69_868MHZ //#define FREQUENCY RF69_868MHZ
#define FREQUENCY RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ) #define FREQUENCY RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
#define ENCRYPTKEY "sampleEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less! #define ENCRYPTKEY "sampleEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less!
#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W! #define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W!
#define HALLSENSOR1 A0 #define HALLSENSOR1 A0
#define HALLSENSOR1_EN 4 #define HALLSENSOR1_EN 4
@ -71,24 +99,34 @@
#define DEBUGln(input); #define DEBUGln(input);
#endif #endif
#ifdef WEATHERSHIELD
SI7021 weatherShield_SI7021;
SFE_BMP180 weatherShield_BMP180;
#endif
//function prototypes
void setStatus(byte newSTATUS, boolean reportStatus=true); void setStatus(byte newSTATUS, boolean reportStatus=true);
void reportStatus();
boolean hallSensorRead(byte which);
void pulseRelay();
//global program variables
byte STATUS; byte STATUS;
unsigned long lastStatusTimestamp=0; unsigned long lastStatusTimestamp=0;
unsigned long ledPulseTimestamp=0; unsigned long ledPulseTimestamp=0;
byte lastRequesterNodeID=GATEWAYID; unsigned long lastWeatherSent=0;
int ledPulseValue=0; int ledPulseValue=0;
boolean ledPulseDirection=false; //false=down, true=up boolean ledPulseDirection=false; //false=down, true=up
RFM69 radio; RFM69 radio;
///////////////////////////////////////////////////////////////////////////// char Pstr[10];
// flash(SPI_CS, MANUFACTURER_ID) char sendBuf[30];
// SPI_CS - CS pin attached to SPI flash chip (8 in case of Moteino) SPIFlash flash(8, 0xEF30); //WINDBOND 4MBIT flash chip on CS pin D8 (default for Moteino)
// MANUFACTURER_ID - OPTIONAL, 0xEF30 for windbond 4mbit flash (Moteino OEM)
/////////////////////////////////////////////////////////////////////////////
SPIFlash flash(8, 0xEF30);
void setup(void) void setup(void)
{ {
#ifdef SERIAL_EN
Serial.begin(SERIAL_BAUD); Serial.begin(SERIAL_BAUD);
#endif
pinMode(HALLSENSOR1, INPUT); pinMode(HALLSENSOR1, INPUT);
pinMode(HALLSENSOR2, INPUT); pinMode(HALLSENSOR2, INPUT);
pinMode(HALLSENSOR1_EN, OUTPUT); pinMode(HALLSENSOR1_EN, OUTPUT);
@ -112,16 +150,27 @@ void setup(void)
if (hallSensorRead(HALLSENSOR_CLOSEDSIDE)==true) if (hallSensorRead(HALLSENSOR_CLOSEDSIDE)==true)
setStatus(STATUS_CLOSED); setStatus(STATUS_CLOSED);
else setStatus(STATUS_UNKNOWN); else setStatus(STATUS_UNKNOWN);
#ifdef WEATHERSHIELD
//initialize weather shield sensors
weatherShield_SI7021.begin();
if (weatherShield_BMP180.begin())
{ DEBUGln("BMP180 init success"); }
else { DEBUGln("BMP180 init fail\n"); }
#endif
} }
unsigned long doorPulseCount = 0; unsigned long doorPulseCount = 0;
char input; char input=0;
double P;
void loop() void loop()
{ {
#ifdef SERIAL_EN
if (Serial.available()) if (Serial.available())
input = Serial.read(); input = Serial.read();
#endif
if (input=='r') if (input=='r')
{ {
DEBUGln("Relay test..."); DEBUGln("Relay test...");
@ -169,7 +218,6 @@ void loop()
{ {
byte newStatus=STATUS; byte newStatus=STATUS;
boolean reportStatusRequest=false; boolean reportStatusRequest=false;
lastRequesterNodeID = radio.SENDERID;
DEBUG('[');DEBUG(radio.SENDERID);DEBUG("] "); DEBUG('[');DEBUG(radio.SENDERID);DEBUG("] ");
for (byte i = 0; i < radio.DATALEN; i++) for (byte i = 0; i < radio.DATALEN; i++)
DEBUG((char)radio.DATA[i]); DEBUG((char)radio.DATA[i]);
@ -256,6 +304,19 @@ void loop()
ledPulseTimestamp = millis(); ledPulseTimestamp = millis();
} }
} }
#ifdef WEATHERSHIELD
if (millis()-lastWeatherSent > WEATHERSENDDELAY)
{
lastWeatherSent = millis();
P = getPressure();
P*=0.0295333727; //transform to inHg
dtostrf(P, 3,2, Pstr);
sprintf(sendBuf, "F:%d H:%d P:%s", weatherShield_SI7021.getFahrenheitHundredths(), weatherShield_SI7021.getHumidityPercent(), Pstr);
byte sendLen = strlen(sendBuf);
radio.send(GATEWAYID, sendBuf, sendLen);
}
#endif
} }
//returns TRUE if magnet is next to sensor, FALSE if magnet is away //returns TRUE if magnet is next to sensor, FALSE if magnet is away
@ -269,22 +330,21 @@ boolean hallSensorRead(byte which)
return reading==0; return reading==0;
} }
void setStatus(byte newSTATUS, boolean reportStatusRequest) void setStatus(byte newSTATUS, boolean reportIt)
{ {
if (STATUS != newSTATUS) lastStatusTimestamp = millis(); if (STATUS != newSTATUS) lastStatusTimestamp = millis();
STATUS = newSTATUS; STATUS = newSTATUS;
DEBUGln(STATUS==STATUS_CLOSED ? "CLOSED" : STATUS==STATUS_CLOSING ? "CLOSING" : STATUS==STATUS_OPENING ? "OPENING" : STATUS==STATUS_OPEN ? "OPEN" : "UNKNOWN"); DEBUGln(STATUS==STATUS_CLOSED ? "CLOSED" : STATUS==STATUS_CLOSING ? "CLOSING" : STATUS==STATUS_OPENING ? "OPENING" : STATUS==STATUS_OPEN ? "OPEN" : "UNKNOWN");
if (reportStatusRequest) if (reportIt)
reportStatus(); reportStatus();
} }
boolean reportStatus() void reportStatus(void)
{ {
if (lastRequesterNodeID == 0) return false;
char buff[10]; char buff[10];
sprintf(buff, STATUS==STATUS_CLOSED ? "CLOSED" : STATUS==STATUS_CLOSING ? "CLOSING" : STATUS==STATUS_OPENING ? "OPENING" : STATUS==STATUS_OPEN ? "OPEN" : "UNKNOWN"); sprintf(buff, STATUS==STATUS_CLOSED ? "CLOSED" : STATUS==STATUS_CLOSING ? "CLOSING" : STATUS==STATUS_OPENING ? "OPENING" : STATUS==STATUS_OPEN ? "OPEN" : "UNKNOWN");
byte len = strlen(buff); byte len = strlen(buff);
return radio.sendWithRetry(lastRequesterNodeID, buff, len); radio.sendWithRetry(GATEWAYID, buff, len);
} }
void pulseRelay() void pulseRelay()
@ -302,4 +362,58 @@ void Blink(byte PIN, byte DELAY_MS)
digitalWrite(PIN,HIGH); digitalWrite(PIN,HIGH);
delay(DELAY_MS); delay(DELAY_MS);
digitalWrite(PIN,LOW); digitalWrite(PIN,LOW);
} }
#ifdef WEATHERSHIELD
double getPressure()
{
char status;
double T,P,p0,a;
// If you want sea-level-compensated pressure, as used in weather reports,
// you will need to know the altitude at which your measurements are taken.
// We're using a constant called ALTITUDE in this sketch:
// If you want to measure altitude, and not pressure, you will instead need
// to provide a known baseline pressure. This is shown at the end of the sketch.
// You must first get a temperature measurement to perform a pressure reading.
// Start a temperature measurement:
// If request is successful, the number of ms to wait is returned.
// If request is unsuccessful, 0 is returned.
status = weatherShield_BMP180.startTemperature();
if (status != 0)
{
// Wait for the measurement to complete:
delay(status);
// Retrieve the completed temperature measurement:
// Note that the measurement is stored in the variable T.
// Function returns 1 if successful, 0 if failure.
status = weatherShield_BMP180.getTemperature(T);
if (status != 0)
{
// Start a pressure measurement:
// The parameter is the oversampling setting, from 0 to 3 (highest res, longest wait).
// If request is successful, the number of ms to wait is returned.
// If request is unsuccessful, 0 is returned.
status = weatherShield_BMP180.startPressure(3);
if (status != 0)
{
// Wait for the measurement to complete:
delay(status);
// Retrieve the completed pressure measurement:
// Note that the measurement is stored in the variable P.
// Note also that the function requires the previous temperature measurement (T).
// (If temperature is stable, you can do one temperature measurement for a number of pressure measurements.)
// Function returns 1 if successful, 0 if failure.
status = weatherShield_BMP180.getPressure(P,T);
if (status != 0)
{
return P;
}
}
}
}
return 0;
}
#endif

View File

@ -4,9 +4,9 @@
// Library and code by Felix Rusu - felix@lowpowerlab.com // Library and code by Felix Rusu - felix@lowpowerlab.com
// Get the RFM69 and SPIFlash library at: https://github.com/LowPowerLab/ // Get the RFM69 and SPIFlash library at: https://github.com/LowPowerLab/
#include <RFM69.h> #include <RFM69.h> //get it here: https://www.github.com/lowpowerlab/rfm69
#include <SPI.h> #include <SPI.h>
#include <SPIFlash.h> #include <SPIFlash.h> //get it here: https://www.github.com/lowpowerlab/spiflash
#define NODEID 1 //unique for each node on same network #define NODEID 1 //unique for each node on same network
#define NETWORKID 100 //the same on all nodes that talk to each other #define NETWORKID 100 //the same on all nodes that talk to each other
@ -16,7 +16,6 @@
//#define FREQUENCY RF69_915MHZ //#define FREQUENCY RF69_915MHZ
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes! #define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
//#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W! //#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W!
#define ACK_TIME 30 // max # of ms to wait for an ack
#define SERIAL_BAUD 115200 #define SERIAL_BAUD 115200
#ifdef __AVR_ATmega1284P__ #ifdef __AVR_ATmega1284P__
@ -40,19 +39,21 @@ void setup() {
#endif #endif
radio.encrypt(ENCRYPTKEY); radio.encrypt(ENCRYPTKEY);
radio.promiscuous(promiscuousMode); radio.promiscuous(promiscuousMode);
//radio.setFrequency(919000000);
char buff[50]; char buff[50];
sprintf(buff, "\nListening at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915); sprintf(buff, "\nListening at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
Serial.println(buff); Serial.println(buff);
if (flash.initialize()) if (flash.initialize())
{ {
Serial.print("SPI Flash Init OK ... UniqueID (MAC): "); Serial.print("SPI Flash Init OK. Unique MAC = [");
flash.readUniqueId(); flash.readUniqueId();
for (byte i=0;i<8;i++) for (byte i=0;i<8;i++)
{ {
Serial.print(flash.UNIQUEID[i], HEX); Serial.print(flash.UNIQUEID[i], HEX);
Serial.print(' '); if (i!=8) Serial.print(':');
} }
Serial.println(']');
//alternative way to read it: //alternative way to read it:
//byte* MAC = flash.readUniqueId(); //byte* MAC = flash.readUniqueId();
//for (byte i=0;i<8;i++) //for (byte i=0;i<8;i++)
@ -60,12 +61,14 @@ void setup() {
// Serial.print(MAC[i], HEX); // Serial.print(MAC[i], HEX);
// Serial.print(' '); // Serial.print(' ');
//} //}
} }
else else
Serial.println("SPI Flash Init FAIL! (is chip present?)"); Serial.println("SPI Flash Init FAIL! (is chip present?)");
} }
byte ackCount=0; byte ackCount=0;
uint32_t packetCount = 0;
void loop() { void loop() {
//process any serial input //process any serial input
if (Serial.available() > 0) if (Serial.available() > 0)
@ -123,6 +126,9 @@ void loop() {
if (radio.receiveDone()) if (radio.receiveDone())
{ {
Serial.print("#[");
Serial.print(++packetCount);
Serial.print(']');
Serial.print('[');Serial.print(radio.SENDERID, DEC);Serial.print("] "); Serial.print('[');Serial.print(radio.SENDERID, DEC);Serial.print("] ");
if (promiscuousMode) if (promiscuousMode)
{ {
@ -163,4 +169,4 @@ void Blink(byte PIN, int DELAY_MS)
digitalWrite(PIN,HIGH); digitalWrite(PIN,HIGH);
delay(DELAY_MS); delay(DELAY_MS);
digitalWrite(PIN,LOW); digitalWrite(PIN,LOW);
} }

View File

@ -1,8 +1,18 @@
// ************************************************************************************************************* // *************************************************************************************************************
// MightyBoost control sample sketch // MightyBoost control sample sketch
// ************************************************************************************************************* // *************************************************************************************************************
// Copyright Felix Rusu (2014), felix@lowpowerlab.com // Copyright (2015) Felix Rusu of http://lowpowerlab.com
// http://lowpowerlab.com/ // http://lowpowerlab.com/mightyboost
// MightyBoost is a smart backup PSU controllable by Moteino, and this sketch is a sample control sketch to run
// MightyBoost in this mode.
// Be sure to check back for code updates and patches
// *************************************************************************************************************
// This sketch will provide control over the essential features of MightyBoost:
// - provide switched 5V power to a sensitive load like RaspberryPi which should not lose power instantly
// - Control the "5V*" output via Moteino+PowerButton (momentary tactile)
// - Monitor input supply and switch to battery backup when external power is lost
// - Monitor battery voltage and issue a shutdown/reboot signal when battery runs low
// This sketch may be extended to include integration with other LowPowerLab automation products
// ************************************************************************************************************* // *************************************************************************************************************
// License // License
// ************************************************************************************************************* // *************************************************************************************************************
@ -29,23 +39,10 @@
// Please maintain this license information along with authorship // Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code // and copyright notices in any redistribution of this code
// ************************************************************************************************************* // *************************************************************************************************************
// MightyBoost is a smart backup PSU controllable by Moteino, and this sketch is a sample control sketch to run
// MightyBoost in this mode.
// http://moteino.com
// http://github.com/lowpowerlab
// Be sure to check back for code updates and patches
// *************************************************************************************************************
// This sketch will provide control over the essential features of MightyBoost:
// - provide switched 5V power to a sensitive load like RaspberryPi which should not lose power instantly
// - Control the "5V*" output via Moteino+PowerButton (momentary tactile)
// - Monitor input supply and switch to battery backup when external power is lost
// - Monitor battery voltage and issue a shutdown signal when battery runs low
// This sketch may be extended to include integration with other LowPowerLab automation products
// *************************************************************************************************************
#define LED 5 // LED pin, should be analog for fading effect (PWM) #define LED 5 // LED pin, should be analog for fading effect (PWM)
#define BUTTON 3 // Power button pin #define BUTTON 3 // Power button pin
#define SIG_REQUESTHALT 6 // Signal to Pi to ask for a shutdown #define SIG_SHUTOFF 6 // Signal to Pi to ask for a shutdown
#define SIG_OKTOCUTOFF A0 // Signal from Pi that it's OK to cutoff power #define SIG_BOOTOK A0 // Signal from Pi that it's OK to cutoff power
// !!NOTE!! Originally this was D7 but it was moved to A0 at least temporarily. // !!NOTE!! Originally this was D7 but it was moved to A0 at least temporarily.
// On MightyBoost R1 you need to connect D7 and A0 with a jumper wire. // On MightyBoost R1 you need to connect D7 and A0 with a jumper wire.
// The explanation for this is given here: http://lowpowerlab.com/mightyboost/#source // The explanation for this is given here: http://lowpowerlab.com/mightyboost/#source
@ -55,24 +52,26 @@
// when plugged in this should be 4.80v, nothing to worry about // when plugged in this should be 4.80v, nothing to worry about
// when on battery power this should decrease from 4.15v (fully charged Lipoly) to 3.3v (discharged Lipoly) // when on battery power this should decrease from 4.15v (fully charged Lipoly) to 3.3v (discharged Lipoly)
// trigger a shutdown to the target device once voltage is around 3.4v to allow 30sec safe shutdown // trigger a shutdown to the target device once voltage is around 3.4v to allow 30sec safe shutdown
#define LOWBATTERYTHRESHOLD 3.7 // a shutdown will be triggered to the target device when battery voltage drops below this (Volts) #define LOWBATTERYTHRESHOLD 3.5 // a shutdown will be triggered to the target device when battery voltage drops below this (Volts)
#define ButtonHoldTime 1800 // Button must be hold this many mseconds before a shutdown sequence is started (should be much less than PIForceShutdownDelay) #define RESETHOLDTIME 500 // Button must be hold this many mseconds before a reset is issued (should be much less than SHUTDOWNHOLDTIME)
#define PIShutdownDelay_Min 6000 // will start checking the SIG_OKTOCUTOFF line after this long #define SHUTDOWNHOLDTIME 2000 // Button must be hold this many mseconds before a shutdown sequence is started (should be much less than ForcedShutoffDelay)
#define PIShutdownDelay_Max 38000 // window of time in which SIG_OKTOCUTOFF is expected to go HIGH #define ShutoffTriggerDelay 6000 // will start checking the SIG_BOOTOK line after this long
#define RecycleTime 50000 // window of time in which SIG_BOOTOK is expected to go HIGH
// should be at least 3000 more than Min // should be at least 3000 more than Min
// if nothing happens after this window, if button is // if nothing happens after this window, if button is
// still pressed, force cutoff power, otherwise switch back to normal ON state // still pressed, force cutoff power, otherwise switch back to normal ON state
#define PIForceShutdownDelay 6500 // when SIG_OKTOCUTOFF==0 (PI in unknown state): if button is held #define RESETPULSETIME 500 // When reset is issued, the SHUTOFF signal is held HIGH this many ms
// for this long, force shutdown (this should be less than PIShutdownDelay_Max) #define ForcedShutoffDelay 7500 // when SIG_BOOTOK==0 (PI in unknown state): if button is held
#define ShutdownFINALDELAY 4000 // after shutdown signal is received, delay for this long // for this long, force shutdown (this should be less than RecycleTime)
#define ShutdownFinalDelay 4500 // after shutdown signal is received, delay for this long
// to allow all PI LEDs to stop activity (pulse LED faster) // to allow all PI LEDs to stop activity (pulse LED faster)
#define PRINTPERIOD 1000 #define PRINTPERIOD 10000
int lastValidReading = 1; int lastValidReading = 1;
unsigned long lastValidReadingTime = 0; unsigned long lastValidReadingTime = 0;
unsigned long now=0; unsigned long NOW=0;
int PowerState = 0; int PowerState = 0;
long lastPeriod = -1; long lastPeriod = -1;
float systemVoltage = 5; float systemVoltage = 5;
@ -80,46 +79,85 @@ float systemVoltage = 5;
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
pinMode(BUTTON, INPUT_PULLUP); pinMode(BUTTON, INPUT_PULLUP);
pinMode(SIG_OKTOCUTOFF, INPUT); pinMode(SIG_BOOTOK, INPUT);
pinMode(SIG_REQUESTHALT, OUTPUT); pinMode(SIG_SHUTOFF, OUTPUT);
pinMode(LED, OUTPUT); pinMode(LED, OUTPUT);
pinMode(OUTPUT_5V, OUTPUT); pinMode(OUTPUT_5V, OUTPUT);
pinMode(A7, INPUT); pinMode(A7, INPUT);
digitalWrite(SIG_REQUESTHALT, LOW);//added after sudden shutdown quirks, DO NOT REMOVE! digitalWrite(SIG_SHUTOFF, LOW);//added after sudden shutdown quirks, DO NOT REMOVE!
digitalWrite(OUTPUT_5V, LOW);//added after sudden shutdown quirks, DO NOT REMOVE! digitalWrite(OUTPUT_5V, LOW);//added after sudden shutdown quirks, DO NOT REMOVE!
} }
void loop() { void loop() {
int reading = digitalRead(BUTTON); int reading = digitalRead(BUTTON);
now = millis(); NOW = millis();
digitalWrite(SIG_REQUESTHALT, LOW);//added after sudden shutdown quirks, DO NOT REMOVE! digitalWrite(SIG_SHUTOFF, LOW);//added after sudden shutdown quirks, DO NOT REMOVE!
boolean batteryLow = systemVoltage < LOWBATTERYTHRESHOLD; boolean batteryLow = systemVoltage < LOWBATTERYTHRESHOLD;
if (batteryLow || reading != lastValidReading && now - lastValidReadingTime > 200) { if (batteryLow || reading != lastValidReading && NOW - lastValidReadingTime > 200)
{
lastValidReading = reading; lastValidReading = reading;
lastValidReadingTime = now; lastValidReadingTime = NOW;
//((PowerState==0 && ()) || (PowerState==1 && (now - lastValidReadingTime > ButtonHoldTime)))
if (batteryLow || reading == 0) if (batteryLow || reading == 0)
{ {
//make sure the button is held down for at least 'ButtonHoldTime' before taking action (this is to avoid accidental button presses and consequently Pi shutdowns) //make sure the button is held down for at least 'RESETHOLDTIME' before taking action (this is to avoid accidental button presses and consequently Pi shutdowns)
now = millis(); NOW = millis();
while (!batteryLow && (PowerState == 1 && millis()-now < ButtonHoldTime)) { delay(10); if (digitalRead(BUTTON) != 0) return; } while (!batteryLow && (PowerState == 1 && millis()-NOW < RESETHOLDTIME)) { delay(10); if (digitalRead(BUTTON) != 0) return; }
//SIG_OKTOCUTOFF must be HIGH when Pi is ON. During boot, this will take a while to happen (till it executes the "shutdowncheck" script //RESETHOLDTIME is satisfied, now check if button still held until SHUTDOWNHOLDTIME is satisfied
analogWrite(LED, 128); //dim the LED to show something's going on
while (!batteryLow && (PowerState == 1 && millis()-NOW < SHUTDOWNHOLDTIME))
{
if (digitalRead(BUTTON) != 0)
{
if (BOOTOK()) //SIG_BOOTOK is HIGH so Pi is running the shutdowncheck.sh script, ready to intercept the RESET PULSE
{
digitalWrite(SIG_SHUTOFF, HIGH);
delay(RESETPULSETIME);
digitalWrite(SIG_SHUTOFF, LOW);
NOW = millis();
boolean recycleDetected=false;
while (millis()-NOW < RecycleTime) //blink LED while waiting for BOOTOK to go high
{
//blink 3 times and pause
digitalWrite(LED, LOW);
delay(100);
digitalWrite(LED, HIGH);
delay(100);
digitalWrite(LED, LOW);
delay(100);
digitalWrite(LED, HIGH);
delay(100);
digitalWrite(LED, LOW);
delay(100);
digitalWrite(LED, HIGH);
delay(500);
if (!BOOTOK()) recycleDetected = true;
else if (BOOTOK() && recycleDetected)
return;
}
return; //reboot pulse sent but it appears a reboot failed; exit all checks
}
else return; //ignore everything else (button was held for RESETHOLDTIME, but SIG_BOOTOK was LOW)
}
}
//SIG_BOOTOK must be HIGH when Pi is ON. During boot, this will take a while to happen (till it executes the "shutdowncheck" script)
//so I dont want to cutoff power before it had a chance to fully boot up //so I dont want to cutoff power before it had a chance to fully boot up
//if (batteryLow || (PowerState == 1 && digitalRead(SIG_OKTOCUTOFF)==1)) if (batteryLow || (PowerState == 1 && BOOTOK()))
if (batteryLow || (PowerState == 1 && analogRead(SIG_OKTOCUTOFF)>800))
{ {
// signal Pi to shutdown // signal Pi to shutdown
digitalWrite(SIG_REQUESTHALT, HIGH); digitalWrite(SIG_SHUTOFF, HIGH);
//now wait for the Pi to signal back //now wait for the Pi to signal back
now = millis(); NOW = millis();
float in, out; float in, out;
boolean forceShutdown = true; boolean forceShutdown = true;
while (millis()-now < PIShutdownDelay_Max) while (millis()-NOW < RecycleTime)
{ {
if (in > 6.283) in = 0; if (in > 6.283) in = 0;
in += .00628; in += .00628;
@ -128,29 +166,26 @@ void loop() {
analogWrite(LED,out); analogWrite(LED,out);
delayMicroseconds(1500); delayMicroseconds(1500);
//account for force-shutdown action (if button held for PIForceShutdownDelay, then force shutdown regardless) //account for force-shutdown action (if button held for ForcedShutoffDelay, then force shutdown regardless)
if (millis()-now <= (PIForceShutdownDelay-ButtonHoldTime) && digitalRead(BUTTON) != 0) if (millis()-NOW <= (ForcedShutoffDelay-SHUTDOWNHOLDTIME) && digitalRead(BUTTON) != 0)
forceShutdown = false; forceShutdown = false;
if (millis()-now >= (PIForceShutdownDelay-ButtonHoldTime) && forceShutdown) if (millis()-NOW >= (ForcedShutoffDelay-SHUTDOWNHOLDTIME) && forceShutdown)
{ {
PowerState = 0; PowerState = 0;
digitalWrite(LED, PowerState); //turn off LED to indicate power is being cutoff digitalWrite(LED, PowerState); //turn off LED to indicate power is being cutoff
digitalWrite(OUTPUT_5V, PowerState); //digitalWrite(LED, PowerState); digitalWrite(OUTPUT_5V, PowerState);
break; break;
} }
if (millis() - now > PIShutdownDelay_Min) if (millis() - NOW > ShutoffTriggerDelay)
{ {
// Pi signaling OK to turn off // Pi signaling OK to turn off
//if (digitalRead(SIG_OKTOCUTOFF) == 0) if (!BOOTOK())
if (analogRead(SIG_OKTOCUTOFF) < 800)
{ {
PowerState = 0; PowerState = 0;
digitalWrite(LED, PowerState); //turn off LED to indicate power is being cutoff digitalWrite(LED, PowerState); //turn off LED to indicate power is being cutoff
NOW = millis();
//delay(3500); //takes about 3sec between SIG_OKTOCUTOFF going LOW and Pi LEDs activity to stop while (millis()-NOW < ShutdownFinalDelay)
now = millis();
while (millis()-now < ShutdownFINALDELAY)
{ {
if (in > 6.283) in = 0; if (in > 6.283) in = 0;
in += .00628; in += .00628;
@ -160,7 +195,7 @@ void loop() {
delayMicroseconds(300); delayMicroseconds(300);
} }
digitalWrite(OUTPUT_5V, PowerState); //digitalWrite(LED, PowerState); digitalWrite(OUTPUT_5V, PowerState);
break; break;
} }
} }
@ -173,22 +208,21 @@ void loop() {
digitalWrite(OUTPUT_5V, PowerState); digitalWrite(OUTPUT_5V, PowerState);
} }
digitalWrite(SIG_REQUESTHALT, LOW); digitalWrite(SIG_SHUTOFF, LOW);
} }
//else if (PowerState == 1 && digitalRead(SIG_OKTOCUTOFF)==0) else if (PowerState == 1 && !BOOTOK())
else if (PowerState == 1 && analogRead(SIG_OKTOCUTOFF)<800)
{ {
now = millis(); NOW = millis();
unsigned long now2 = millis(); unsigned long NOW2 = millis();
int analogstep = 255 / ((PIForceShutdownDelay-ButtonHoldTime)/100); //every 500ms decrease LED intensity int analogstep = 255 / ((ForcedShutoffDelay-SHUTDOWNHOLDTIME)/100); //every 500ms decrease LED intensity
while (digitalRead(BUTTON) == 0) while (digitalRead(BUTTON) == 0)
{ {
if (millis()-now2 > 100) if (millis()-NOW2 > 100)
{ {
analogWrite(LED, 255 - ((millis()-now)/100)*analogstep); analogWrite(LED, 255 - ((millis()-NOW)/100)*analogstep);
now2 = millis(); NOW2 = millis();
} }
if (millis()-now > PIForceShutdownDelay-ButtonHoldTime) if (millis()-NOW > ForcedShutoffDelay-SHUTDOWNHOLDTIME)
{ {
//TODO: add blinking here to signal final shutdown delay //TODO: add blinking here to signal final shutdown delay
PowerState = 0; PowerState = 0;
@ -203,10 +237,10 @@ void loop() {
digitalWrite(OUTPUT_5V, PowerState); //digitalWrite(LED, PowerState); digitalWrite(OUTPUT_5V, PowerState); //digitalWrite(LED, PowerState);
} }
} }
digitalWrite(LED, PowerState); digitalWrite(LED, PowerState);
} }
int currPeriod = millis()/PRINTPERIOD; int currPeriod = millis()/PRINTPERIOD;
if (currPeriod != lastPeriod) if (currPeriod != lastPeriod)
{ {
@ -218,4 +252,8 @@ void loop() {
Serial.println(" (plugged in)"); Serial.println(" (plugged in)");
else Serial.println(" (running from battery!)"); else Serial.println(" (running from battery!)");
} }
}
boolean BOOTOK() {
return analogRead(SIG_BOOTOK) > 800;
} }

View File

@ -17,7 +17,6 @@
//#define FREQUENCY RF69_915MHZ //#define FREQUENCY RF69_915MHZ
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes! #define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
//#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W! //#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W!
#define ACK_TIME 30 // max # of ms to wait for an ack
#ifdef __AVR_ATmega1284P__ #ifdef __AVR_ATmega1284P__
#define LED 15 // Moteino MEGAs have LEDs on D15 #define LED 15 // Moteino MEGAs have LEDs on D15
#define FLASH_SS 23 // and FLASH SS on D23 #define FLASH_SS 23 // and FLASH SS on D23
@ -28,7 +27,7 @@
#define SERIAL_BAUD 115200 #define SERIAL_BAUD 115200
int TRANSMITPERIOD = 300; //transmit a packet to gateway so often (in ms) int TRANSMITPERIOD = 150; //transmit a packet to gateway so often (in ms)
char payload[] = "123 ABCDEFGHIJKLMNOPQRSTUVWXYZ"; char payload[] = "123 ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char buff[20]; char buff[20];
byte sendSize=0; byte sendSize=0;
@ -43,6 +42,7 @@ void setup() {
radio.setHighPower(); //uncomment only for RFM69HW! radio.setHighPower(); //uncomment only for RFM69HW!
#endif #endif
radio.encrypt(ENCRYPTKEY); radio.encrypt(ENCRYPTKEY);
//radio.setFrequency(919000000); //set frequency to some custom frequency
char buff[50]; char buff[50];
sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915); sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
Serial.println(buff); Serial.println(buff);
@ -125,33 +125,37 @@ void loop() {
radio.sendACK(); radio.sendACK();
Serial.print(" - ACK sent"); Serial.print(" - ACK sent");
} }
Blink(LED,5); Blink(LED,3);
Serial.println(); Serial.println();
} }
//send FLASH id
if(sendSize==0)
{
sprintf(buff, "FLASH_MEM_ID:0x%X", flash.readDeviceId());
byte buffLen=strlen(buff);
radio.sendWithRetry(GATEWAYID, buff, buffLen);
sendSize = (sendSize + 1) % 31;
}
int currPeriod = millis()/TRANSMITPERIOD; int currPeriod = millis()/TRANSMITPERIOD;
if (currPeriod != lastPeriod) if (currPeriod != lastPeriod)
{ {
lastPeriod=currPeriod; lastPeriod=currPeriod;
Serial.print("Sending[");
Serial.print(sendSize); //send FLASH id
Serial.print("]: "); if(sendSize==0)
for(byte i = 0; i < sendSize; i++) {
Serial.print((char)payload[i]); sprintf(buff, "FLASH_MEM_ID:0x%X", flash.readDeviceId());
byte buffLen=strlen(buff);
if (radio.sendWithRetry(GATEWAYID, payload, sendSize)) if (radio.sendWithRetry(GATEWAYID, buff, buffLen))
Serial.print(" ok!"); Serial.print(" ok!");
else Serial.print(" nothing..."); else Serial.print(" nothing...");
//sendSize = (sendSize + 1) % 31;
}
else
{
Serial.print("Sending[");
Serial.print(sendSize);
Serial.print("]: ");
for(byte i = 0; i < sendSize; i++)
Serial.print((char)payload[i]);
if (radio.sendWithRetry(GATEWAYID, payload, sendSize))
Serial.print(" ok!");
else Serial.print(" nothing...");
}
sendSize = (sendSize + 1) % 31; sendSize = (sendSize + 1) % 31;
Serial.println(); Serial.println();
Blink(LED,3); Blink(LED,3);
@ -164,4 +168,4 @@ void Blink(byte PIN, int DELAY_MS)
digitalWrite(PIN,HIGH); digitalWrite(PIN,HIGH);
delay(DELAY_MS); delay(DELAY_MS);
digitalWrite(PIN,LOW); digitalWrite(PIN,LOW);
} }

View File

@ -59,7 +59,7 @@
#define BUTTON_PIN 3 //user button on interrupt 1 #define BUTTON_PIN 3 //user button on interrupt 1
RFM69 radio; RFM69 radio;
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE); // I2C / TWI SSD1306 OLED 128x64 U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE); // I2C / TWI SSD1306 OLED 128x64
bool promiscuousMode = true; //set to 'true' to sniff all packets on the same network bool promiscuousMode = true; //set to 'true' to sniff all packets on the same network
void setup() { void setup() {

View File

@ -0,0 +1,146 @@
// **********************************************************************************************************
// GarageMote garage door controller base receiver sketch that works with Moteinos equipped with HopeRF RFM69W/RFM69HW
// Can be adapted to use Moteinos using RFM12B
// This is the sketch for the base, not the controller itself, and meant as another example on how to use a
// Moteino as a gateway/base/receiver
// 2014-07-14 (C) felix@lowpowerlab.com, http://www.LowPowerLab.com
// **********************************************************************************************************
// Creative Commons Attrib Share-Alike License
// You are free to use/extend this code/library but please abide with the CCSA license:
// http://creativecommons.org/licenses/by-sa/4.0/
// **********************************************************************************
#include <RFM69.h> //get it here: http://github.com/lowpowerlab/rfm69
#include <SPIFlash.h> //get it here: http://github.com/lowpowerlab/spiflash
#include <WirelessHEX69.h> //get it here: https://github.com/LowPowerLab/WirelessProgramming
#include <SPI.h> //comes with Arduino IDE (www.arduino.cc)
//*****************************************************************************************************************************
// ADJUST THE SETTINGS BELOW DEPENDING ON YOUR HARDWARE/SITUATION!
//*****************************************************************************************************************************
#define NODEID 1
#define NETWORKID 200
#define FREQUENCY RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
#define ENCRYPTKEY "sampleEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less!
#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W!
#define LED 9
#define FLASH_CS 8
#define SERIAL_BAUD 115200
#define SERIAL_EN //comment out if you don't want any serial verbose output
#define ACK_TIME 30 // # of ms to wait for an ack
//*****************************************************************************************************************************
#ifdef SERIAL_EN
#define DEBUG(input) {Serial.print(input); delay(1);}
#define DEBUGln(input) {Serial.println(input); delay(1);}
#else
#define DEBUG(input);
#define DEBUGln(input);
#endif
RFM69 radio;
SPIFlash flash(FLASH_CS, 0xEF30); //EF40 for 16mbit windbond chip
void setup() {
Serial.begin(SERIAL_BAUD);
delay(10);
radio.initialize(FREQUENCY,NODEID,NETWORKID);
#ifdef IS_RFM69HW
radio.setHighPower(); //uncomment only for RFM69HW!
#endif
radio.encrypt(ENCRYPTKEY);
char buff[50];
sprintf(buff, "\nListening at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
DEBUGln(buff);
if (flash.initialize())
{
DEBUGln("SPI Flash Init OK!");
}
else
DEBUGln("SPI Flash Init FAIL! (is chip present?)");
}
byte ackCount=0;
byte inputLen=0;
char input[64];
byte buff[61];
String inputstr;
void loop() {
inputLen = readSerialLine(input);
inputstr = String(input);
inputstr.toUpperCase();
if (inputLen > 0)
{
if (inputstr.equals("KEY?"))
{
DEBUG("ENCRYPTKEY:");
DEBUG(ENCRYPTKEY);
}
byte targetId = inputstr.toInt(); //extract ID if any
byte colonIndex = inputstr.indexOf(":"); //find position of first colon
if (targetId > 0) inputstr = inputstr.substring(colonIndex+1); //trim "ID:" if any
if (targetId > 0 && targetId != NODEID && targetId != RF69_BROADCAST_ADDR && colonIndex>0 && colonIndex<4 && inputstr.length()>0)
{
inputstr.getBytes(buff, 61);
//DEBUGln((char*)buff);
//DEBUGln(targetId);
//DEBUGln(colonIndex);
if (radio.sendWithRetry(targetId, buff, inputstr.length()))
{
DEBUGln("ACK:OK");
}
else
DEBUGln("ACK:NOK");
}
}
if (radio.receiveDone())
{
int rssi = radio.RSSI;
DEBUG('[');DEBUG(radio.SENDERID);DEBUG("] ");
if (radio.DATALEN > 0)
{
for (byte i = 0; i < radio.DATALEN; i++)
DEBUG((char)radio.DATA[i]);
DEBUG(" [RSSI:");DEBUG(rssi);DEBUG("]");
}
CheckForWirelessHEX(radio, flash, false); //non verbose DEBUG
if (radio.ACKRequested())
{
byte theNodeID = radio.SENDERID;
radio.sendACK();
DEBUG("[ACK-sent]");
}
DEBUGln();
Blink(LED,3);
}
}
void Blink(byte PIN, int DELAY_MS)
{
pinMode(PIN, OUTPUT);
digitalWrite(PIN,HIGH);
delay(DELAY_MS);
digitalWrite(PIN,LOW);
}
//readSerialLine already defined in WirelessHEX69
// 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=10, byte maxLength=64, uint16_t timeout=10);
//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;
//}

View File

@ -0,0 +1,186 @@
// **********************************************************************************************************
// GarageMote garage door controller base receiver sketch that works with Moteinos equipped with HopeRF RFM69W/RFM69HW
// Can be adapted to use Moteinos using RFM12B
// This is the sketch for the base, not the controller itself, and meant as another example on how to use a
// Moteino as a gateway/base/receiver
// 2014-07-14 (C) felix@lowpowerlab.com, http://www.LowPowerLab.com
// **********************************************************************************************************
// Creative Commons Attrib Share-Alike License
// You are free to use/extend this code/library but please abide with the CCSA license:
// http://creativecommons.org/licenses/by-sa/4.0/
// **********************************************************************************
#include <RFM69.h> //get it here: http://github.com/lowpowerlab/rfm69
#include <SPIFlash.h> //get it here: http://github.com/lowpowerlab/spiflash
#include <WirelessHEX69.h> //get it here: https://github.com/LowPowerLab/WirelessProgramming
#include <SPI.h> //comes with Arduino IDE (www.arduino.cc)
#include "ST7036.h" //get it from here: https://bitbucket.org/fmalpartida/st7036-display-driver/src/
#include "LCD_C0220BiZ.h" //get it from here: https://bitbucket.org/fmalpartida/st7036-display-driver/src/
#include <Wire.h> //comes with Arduino
//*****************************************************************************************************************************
// ADJUST THE SETTINGS BELOW DEPENDING ON YOUR HARDWARE/SITUATION!
//*****************************************************************************************************************************
#define NODEID 1
#define NETWORKID 200
#define FREQUENCY RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
#define ENCRYPTKEY "thisIsEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less!
#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W!
#define LED 9
#define FLASH_CS 8
#define SERIAL_BAUD 115200
#define SERIAL_EN //comment out if you don't want any serial verbose output
#define ACK_TIME 30 // # of ms to wait for an ack
#define BACKLIGHTPIN 5 //3=R,5=G,6=B
//*****************************************************************************************************************************
#ifdef SERIAL_EN
#define DEBUG(input) {Serial.print(input); delay(1);}
#define DEBUGln(input) {Serial.println(input); delay(1);}
#else
#define DEBUG(input);
#define DEBUGln(input);
#endif
RFM69 radio;
SPIFlash flash(FLASH_CS, 0xEF30); //EF40 for 16mbit windbond chip
//initialize LCD
ST7036 lcd = ST7036(2, 20, 0x78, BACKLIGHTPIN); //row count, column count, I2C addr, pin for backlight PWM
byte battChar[8] = {0b00000,0b01110,0b11111,0b11111,0b11111,0b11111,0b11111,0};
byte rssiChar[8] = {0b00000,0b00100,0b10101,0b01110,0b00100,0b00100,0b00100,0};
void setup() {
Serial.begin(SERIAL_BAUD);
delay(10);
radio.initialize(FREQUENCY,NODEID,NETWORKID);
#ifdef IS_RFM69HW
radio.setHighPower(); //uncomment only for RFM69HW!
#endif
radio.encrypt(ENCRYPTKEY);
char buff[50];
sprintf(buff, "\nListening @ %dmhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
DEBUGln(buff);
if (flash.initialize())
{
DEBUGln("SPI Flash Init OK!");
}
else
DEBUGln("SPI Flash Init FAIL! (is chip present?)");
lcd.init();
lcd.setContrast(10);
lcd.clear();
lcd.load_custom_character(0, battChar);
lcd.load_custom_character(1, rssiChar);
lcd.setCursor(0,0);
lcd.print(buff);
}
byte ackCount=0;
byte inputLen=0;
char input[64];
byte buff[61];
char LO[20];
char BAT[20];
char temp[25];
String inputstr;
void loop() {
inputLen = readSerialLine(input, 10, 64, 10); //readSerialLine(char* input, char endOfLineChar=10, byte maxLength=64, uint16_t timeout=10);
inputstr = String(input);
inputstr.toUpperCase();
if (inputLen > 0)
{
if (inputstr.equals("KEY?"))
{
DEBUG("ENCRYPTKEY:");
DEBUG(ENCRYPTKEY);
}
byte targetId = inputstr.toInt(); //extract ID if any
byte colonIndex = inputstr.indexOf(":"); //find position of first colon
if (targetId > 0) inputstr = inputstr.substring(colonIndex+1); //trim "ID:" if any
if (targetId > 0 && targetId != NODEID && targetId != RF69_BROADCAST_ADDR && colonIndex>0 && colonIndex<4 && inputstr.length()>0)
{
inputstr.getBytes(buff, 61);
//DEBUGln((char*)buff);
//DEBUGln(targetId);
//DEBUGln(colonIndex);
if (radio.sendWithRetry(targetId, buff, inputstr.length()))
{
DEBUGln("ACK:OK");
}
else
DEBUGln("ACK:NOK");
}
}
if (radio.receiveDone())
{
int rssi = radio.RSSI;
DEBUG('[');DEBUG(radio.SENDERID);DEBUG("] ");
if (radio.DATALEN > 0)
{
for (byte i = 0; i < radio.DATALEN; i++)
DEBUG((char)radio.DATA[i]);
DEBUG(" [RSSI:");DEBUG(rssi);DEBUG("]");
}
CheckForWirelessHEX(radio, flash, false); //non verbose DEBUG
if (radio.ACKRequested())
{
byte theNodeID = radio.SENDERID;
radio.sendACK();
DEBUG("[ACK-sent]");
}
DEBUGln();
Blink(LED,3);
lcd.clear();
lcd.setCursor(0,0);
//if (radio.DATALEN < RF69_MAX_DATA_LEN) radio.DATA[radio.DATALEN]=0;
byte matches = sscanf((const char*)radio.DATA, "%s BAT:%s", LO, BAT);
if (matches==2)
{
lcd.print(LO);
lcd.setCursor(0,14);
lcd.print(char(0));
lcd.setCursor(0,15);
lcd.print(BAT);
}
else lcd.print((const char*)radio.DATA);
lcd.setCursor(1,14);
lcd.print(char(1));
lcd.setCursor(1,16);
lcd.print(rssi);
}
}
void Blink(byte PIN, int DELAY_MS)
{
pinMode(PIN, OUTPUT);
digitalWrite(PIN,HIGH);
delay(DELAY_MS);
digitalWrite(PIN,LOW);
}
//readSerialLine already defined in WirelessHEX69
// 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=10, byte maxLength=64, uint16_t timeout=10);
//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;
//}

View File

@ -0,0 +1,261 @@
// Sample RFM69 sketch for PulseMote - reading an EE-SY310 based water/pulse meter
// Example: https://lowpowerlab.com/blog/2013/02/02/meet-the-watermote-moteino-based-water-meter-reader-ee-sy310/
// Copyright (c) 2015 Felix Rusu (felix@lowpowerlab.com). All rights reserved.
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
// ***************************************************************************************************************************
#include <RFM69.h> //get it here: http://github.com/lowpowerlab/rfm69
#include <SPIFlash.h> //get it here: http://github.com/lowpowerlab/spiflash
#include <WirelessHEX69.h> //get it here: https://github.com/LowPowerLab/WirelessProgramming
#include <EEPROM.h>
#include <SPI.h>
#include <TimerOne.h>
//*********************************************************************************************
//************ IMPORTANT SETTINGS - YOU MUST CHANGE/ONFIGURE TO FIT YOUR HARDWARE *************
//*********************************************************************************************
#define NODEID 5
#define GATEWAYID 1
#define NETWORKID 250
#define FREQUENCY RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
#define ENCRYPTKEY "sampleEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less!
//#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W!
#define PULSESPERGALLON 45 //how many pulses from sensor equal 1 gallon
#define GPMTHRESHOLD 8000 // GPM will reset after this many MS if no pulses are registered
#define XMITPERIOD 5000 // GPMthreshold should be less than 2*XMITPERIOD
//*********************************************************************************************
#ifdef __AVR_ATmega1284P__
#define LED 15 // Moteino MEGAs have LEDs on D15
#define INTERRUPTPIN 1 //INT1 = digital pin 11 (must be a hardware interrupt pin!)
#else
#define LED 9 // Moteinos have LEDs on D9
#define INTERRUPTPIN 1 //INT1 = digital pin 3 (must be a hardware interrupt pin!)
#endif
//*********************************************************************************************
#define SERIAL_EN //uncomment this line to enable serial IO (when you debug Moteino and need serial output)
#define SERIAL_BAUD 115200
#ifdef SERIAL_EN
#define DEBUG(input) {Serial.print(input);}
#define DEBUGln(input) {Serial.println(input);}
#else
#define DEBUG(input);
#define DEBUGln(input);
#endif
RFM69 radio;
SPIFlash flash(8, 0xEF30); //WINDBOND 4MBIT flash chip on CS pin D8 (default for Moteino)
volatile byte ledState = LOW;
volatile unsigned long PulseCounterVolatile = 0; // use volatile for shared variables
unsigned long NOW = 0;
unsigned long PulseCounter = 0;
unsigned long LASTMINUTEMARK = 0;
unsigned long PULSECOUNTLASTMINUTEMARK = 0; //keeps pulse count at the last minute mark
byte COUNTEREEPROMSLOTS = 10;
unsigned long COUNTERADDRBASE = 8; //address in EEPROM that points to the first possible slot for a counter
unsigned long COUNTERADDR = 0; //address in EEPROM that points to the latest Counter in EEPROM
byte secondCounter = 0;
unsigned long TIMESTAMP_pulse_prev = 0;
unsigned long TIMESTAMP_pulse_curr = 0;
int pulseAVGInterval = 0;
int pulsesPerXMITperiod = 0;
float GPM=0, GLM=0, GAL=0, GALlast=0, GPMlast=0, GLMlast=0;
byte sendLen;
char buff[80];
char* GALstr="99999999999999.99"; //longest expected GAL message
char* GPMstr="99999.99"; //longest expected GPM message
char* GLMstr="9999999.99"; //longest expected GLM message
boolean WPReady = false;
void setup() {
#ifdef SERIAL_EN
Serial.begin(SERIAL_BAUD);
#endif
radio.initialize(FREQUENCY,NODEID,NETWORKID);
#ifdef IS_RFM69HW
radio.setHighPower(); //uncomment only for RFM69HW!
#endif
radio.encrypt(ENCRYPTKEY);
pinMode(LED, OUTPUT);
//initialize counter from EEPROM
unsigned long savedCounter = EEPROM_Read_Counter();
if (savedCounter <=0) savedCounter = 1; //avoid division by 0
PulseCounterVolatile = PulseCounter = PULSECOUNTLASTMINUTEMARK = savedCounter;
attachInterrupt(INTERRUPTPIN, pulseCounterInterrupt, RISING);
Timer1.initialize(XMITPERIOD * 1000L);
Timer1.attachInterrupt(XMIT);
sprintf(buff, "\nTransmitting at %d Mhz, id:%d nid:%d gid:%d", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915, NODEID, NETWORKID, GATEWAYID);
DEBUG(buff);
for (byte i=0;i<strlen(ENCRYPTKEY);i++) DEBUG(ENCRYPTKEY[i]);
DEBUGln();
if (flash.initialize())
{
DEBUGln("FLASH MEM present, ready for wireless programming.");
WPReady = true;
}
else
DEBUGln("FLASH MEM not found, skipping wireless programming checks.");
}
void loop() {
if (WPReady && radio.receiveDone())
{
DEBUG('[');DEBUG(radio.SENDERID);DEBUG("] ");
for (byte i = 0; i < radio.DATALEN; i++)
DEBUG((char)radio.DATA[i]);
// wireless programming token check
// DO NOT REMOVE, or PulseMote will not be wirelessly programmable any more!
CheckForWirelessHEX(radio, flash, true);
}
}
void pulseCounterInterrupt()
{
noInterrupts();
ledState = !ledState;
PulseCounterVolatile++; // increase when LED turns on
digitalWrite(LED, ledState);
NOW = millis();
//remember how long between pulses (sliding window)
TIMESTAMP_pulse_prev = TIMESTAMP_pulse_curr;
TIMESTAMP_pulse_curr = NOW;
if (TIMESTAMP_pulse_curr - TIMESTAMP_pulse_prev > GPMTHRESHOLD)
//more than 'GPMthreshold' seconds passed since last pulse... resetting GPM
pulsesPerXMITperiod=pulseAVGInterval=0;
else
{
pulsesPerXMITperiod++;
pulseAVGInterval += TIMESTAMP_pulse_curr - TIMESTAMP_pulse_prev;
}
interrupts();
}
void XMIT()
{
noInterrupts();
PulseCounter = PulseCounterVolatile;
interrupts();
if (millis() - TIMESTAMP_pulse_curr >= 5000)
{
ledState = !ledState;
digitalWrite(LED, ledState);
}
//calculate Gallons counter
GAL = ((float)PulseCounter)/PULSESPERGALLON;
DEBUG("PulseCounter:");DEBUG(PulseCounter);DEBUG(", GAL: "); DEBUGln(GAL);
//calculate & output GPM
GPM = pulseAVGInterval > 0 ? 60.0 * 1000 * (1.0/PULSESPERGALLON)/(pulseAVGInterval/pulsesPerXMITperiod)
: 0;
dtostrf(GAL,3,2, GALstr);
dtostrf(GPM,3,2, GPMstr);
pulsesPerXMITperiod = 0;
pulseAVGInterval = 0;
secondCounter += XMITPERIOD/1000;
//once per minute, output a GallonsLastMinute count
if (secondCounter>=60)
{
//DEBUG("60sec mark ... ");
secondCounter=0;
GLM = ((float)(PulseCounter - PULSECOUNTLASTMINUTEMARK))/PULSESPERGALLON;
PULSECOUNTLASTMINUTEMARK = PulseCounter;
EEPROM_Write_Counter(PulseCounter);
dtostrf(GLM,3,2, GLMstr);
sprintf(buff, "GAL:%s GPM:%s GLM:%s", GALstr, GPMstr, GLMstr);
//DEBUGln("done");
}
else
{
sprintf(buff, "GAL:%s GPM:%s", GALstr, GPMstr);
}
if (GPM!=GPMlast || GAL!=GALlast || GLM!=GLMlast)
{
sendLen = strlen(buff);
radio.sendWithRetry(GATEWAYID, buff, sendLen);
GALlast = GAL;
GPMlast = GPM;
GLMlast = GLM;
}
DEBUGln(buff);
}
unsigned long EEPROM_Read_Counter()
{
return EEPROM_Read_ULong(EEPROM_Read_ULong(COUNTERADDR));
}
void EEPROM_Write_Counter(unsigned long counterNow)
{
if (counterNow == EEPROM_Read_Counter())
{
DEBUG("{EEPROM-SKIP(no changes)}");
return; //skip if nothing changed
}
DEBUG("{EEPROM-SAVE(");
DEBUG(EEPROM_Read_ULong(COUNTERADDR));
DEBUG(")=");
DEBUG(PulseCounter);
DEBUG("}");
unsigned long CounterAddr = EEPROM_Read_ULong(COUNTERADDR);
if (CounterAddr == COUNTERADDRBASE+8*(COUNTEREEPROMSLOTS-1))
CounterAddr = COUNTERADDRBASE;
else CounterAddr += 8;
EEPROM_Write_ULong(CounterAddr, counterNow);
EEPROM_Write_ULong(COUNTERADDR, CounterAddr);
}
unsigned long EEPROM_Read_ULong(int address)
{
unsigned long temp;
for (byte i=0; i<8; i++)
temp = (temp << 8) + EEPROM.read(address++);
return temp;
}
void EEPROM_Write_ULong(int address, unsigned long data)
{
for (byte i=0; i<8; i++)
{
EEPROM.write(address+7-i, data);
data = data >> 8;
}
}

View File

@ -0,0 +1,97 @@
// Sample sketch for the SonarMote - Simple distance reading
// http://lowpowerlab.com/sonar
// Ultrasonic sensor (HC-SR04) connected to D6 (Trig), D7 (Echo), and power enabled through D5
// Make sure you adjust the settings in the configuration section below !!!
// **********************************************************************************
// Copyright Felix Rusu, LowPowerLab.com
// Library and code by Felix Rusu - felix@lowpowerlab.com
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
// **********************************************************************************
#define TRIG 6
#define ECHO 7
#define EN 5
void setup() {
pinMode (TRIG,OUTPUT);//attach pin 2 to vcc
pinMode (ECHO,OUTPUT);//attach pin 2 to vcc
pinMode (EN,OUTPUT);//attach pin 5 to GND
pinMode (ECHO, INPUT);//attach pin 4 to Echo
// initialize serial communication:
Serial.begin(115200);
digitalWrite(EN, LOW);
}
long cm;
void loop()
{
cm = readDistanceCM();
Serial.print(cm); Serial.println("cm");
delay(500);
}
long readDistanceCM()
{
digitalWrite(EN, HIGH);
delay(75);
// The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
// Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
digitalWrite(TRIG, LOW);
delayMicroseconds(2);
digitalWrite(TRIG, HIGH);
delayMicroseconds(5);
digitalWrite(TRIG, LOW);
pulseIn(ECHO, HIGH);
delay(16);
digitalWrite(TRIG, LOW);
delayMicroseconds(2);
digitalWrite(TRIG, HIGH);
delayMicroseconds(5);
digitalWrite(TRIG, LOW);
// The same pin is used to read the signal from the PING))): a HIGH
// pulse whose duration is the time (in microseconds) from the sending
// of the ping to the reception of its echo off of an object.
long duration = pulseIn(ECHO, HIGH);
digitalWrite(EN, LOW);
return microsecondsToCentimeters(duration);
}
long microsecondsToInches(long microseconds)
{
// According to Parallax's datasheet for the PING))), there are
// 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
// second). This gives the distance travelled by the ping, outbound
// and return, so we divide by 2 to get the distance of the obstacle.
// See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
return microseconds / 74 / 2;
}
long microsecondsToCentimeters(long microseconds)
{
// The speed of sound is 340 m/s or 29 microseconds per centimeter.
// The ping travels out and back, so to find the distance of the
// object we take half of the distance travelled.
return microseconds / 29 / 2;
}

View File

@ -0,0 +1,277 @@
// Sample RFM69 sender/node sketch for the SonarMote - Distance tracker
// Can be used for inventory control - ex to measure distance in a multi lane cigarette pack rack
// More info/photos at: http://lowpowerlab.com/sonar
// Ultrasonic sensor (HC-SR04) connected to D6 (Trig), D7 (Echo), and power enabled through D5
// This sketch sleeps the Moteino and sensor most of the time. It wakes up every few seconds to take
// a distance reading. If it detects an approaching object (car) it increases the sampling rate
// and starts lighting up the LED (from green to yellow to red to blinking red). Once there is no more
// motion the LED is turned off and the cycle is back to a few seconds in between sensor reads.
// Button is connected on D3. Holding the button for a few seconds enters the "red zone adjust" mode (RZA).
// By default the red zone limit is at 25cm (LED turns RED below this and starts blinking faster and faster).
// In RZA, readings are taken for 5 seconds. In this time you have the chance to set a new red zone limit.
// Valid new red zone readings are between the RED__LIMIT_UPPER (default 25cm) and MAX_ADJUST_DISTANCE (cm).
// In RZA mode the BLU Led blinks fast to indicate new red limit distance. It blinks slow if the readings are invalid
// If desired this value could be saved to EEPROM to persist if unit is turned off
// Get the RFM69 at: https://github.com/LowPowerLab/
// Make sure you adjust the settings in the configuration section below !!!
// **********************************************************************************
// Copyright Felix Rusu, LowPowerLab.com
// Library and code by Felix Rusu - felix@lowpowerlab.com
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
// **********************************************************************************
#include <SPI.h>
#include <RFM69.h> //get it here: https://www.github.com/lowpowerlab/rfm69
#include <SPIFlash.h> //get it here: https://github.com/LowPowerLab/SPIFlash
#include <LowPower.h> //get library from: https://github.com/lowpowerlab/lowpower
//writeup here: http://www.rocketscream.com/blog/2011/07/04/lightweight-low-power-arduino-library/
//*********************************************************************************************
//************ IMPORTANT SETTINGS - YOU MUST CHANGE/ONFIGURE TO FIT YOUR HARDWARE *************
//*********************************************************************************************
#define NODEID 22 //unique for each node on same network
#define NETWORKID 100 //the same on all nodes that talk to each other
#define GATEWAYID 1
//Match frequency to the hardware version of the radio on your Moteino (uncomment one):
//#define FREQUENCY RF69_433MHZ
//#define FREQUENCY RF69_868MHZ
#define FREQUENCY RF69_915MHZ
#define IS_RFM69HW //uncomment only for RFM69HW! Remove/comment if you have RFM69W!
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
#define SENDLOOPS 80 //default:80 //if no message was sent for this many sleep loops/cycles, then force a send
#define READ_SAMPLES 3
//*********************************************************************************************
//#define BUZZER_ENABLE //uncomment this line if you have the BUZZER soldered and want the sketch to make sounds
#define SERIAL_EN //uncomment if you want serial debugging output
//*********************************************************************************************
#define SLEEP_FASTEST SLEEP_15MS
#define SLEEP_FAST SLEEP_250MS
#define SLEEP_SEC SLEEP_1S
#define SLEEP_LONG SLEEP_2S
#define SLEEP_LONGER SLEEP_4S
#define SLEEP_LONGEST SLEEP_8S
period_t sleepTime = SLEEP_LONGEST; //period_t is an enum type defined in the LowPower library (LowPower.h)
//*********************************************************************************************
#ifdef __AVR_ATmega1284P__
#define LED 15 // Moteino MEGAs have LEDs on D15
#define FLASH_SS 23
#else
#define LED 9 // Moteinos have LEDs on D9
#define FLASH_SS 8
#endif
#define TRIG 6 // digital pin wired to TRIG pin of ultrasonic sensor
#define ECHO 7 // digital pin wired to ECHO pin of ultrasonic sensor
#define SENSOR_EN 5 // digital pin that enables power to ultrasonic sensor
#define BUZZER 4 // digital pin that is connected to onboard buzzer
#define MAX_DISTANCE 150 // maximum valid distance
#define MIN_DISTANCE 2 // minimum valid distance
#define MAX_ADJUST_DISTANCE (MAX_DISTANCE-GRN_LIMIT_UPPER) //this is the amount by which the RED_LIMIT_UPPER can by increased
//
#ifdef SERIAL_EN
#define SERIAL_BAUD 115200
#define DEBUG(input) {Serial.print(input);}
#define DEBUGln(input) {Serial.println(input);}
#define SERIALFLUSH() {Serial.flush();}
#else
#define DEBUG(input);
#define DEBUGln(input);
#define SERIALFLUSH();
#endif
#define BATT_MONITOR A7 // Sense VBAT_COND signal (when powered externally should read ~3.25v/3.3v (1000-1023), when external power is cutoff it should start reading around 2.85v/3.3v * 1023 ~= 883 (ratio given by 10k+4.7K divider from VBAT_COND = 1.47 multiplier)
#define BATT_CYCLES SENDLOOPS // read and report battery voltage every this many sleep cycles (ex 30cycles * 8sec sleep = 240sec/4min). For 450 cycles you would get ~1 hour intervals between readings
#define BATT_FORMULA(reading) reading * 0.00322 * 1.475 // >>> fine tune this parameter to match your voltage when fully charged
#define BATT_LOW 3.3
byte sendLen;
byte sendLoops=SENDLOOPS;
float distance=0;
float prevDistance=0;
float batteryVolts = 5;
char buff[50]; //this is just an empty string used as a buffer to place the payload for the radio
char* BATstr="BAT:5.00v"; //longest battery voltage reading message = 9chars
char* DISTstr="99999.99cm"; //longest distance reading message = 5chars
void checkBattery(byte samples=10); //take 10 samples by default
float readDistance(byte samples=1); //take 1 samples by default
SPIFlash flash(FLASH_SS, 0xEF30); //EF30 for 4mbit Windbond chip (W25X40CL)
RFM69 radio;
void setup() {
#ifdef SERIAL_EN
Serial.begin(SERIAL_BAUD); // Open serial monitor at 115200 baud to see ping results.
#endif
radio.initialize(FREQUENCY,NODEID,NETWORKID);
#ifdef IS_RFM69HW
radio.setHighPower(); //uncomment only for RFM69HW!
#endif
radio.encrypt(ENCRYPTKEY);
//sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
sprintf(buff, "\nTransmitting at %d Mhz, id:%d nid:%d gid:%d", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915, NODEID, NETWORKID, GATEWAYID);
DEBUG(buff);
for (byte i=0;i<strlen(ENCRYPTKEY);i++) DEBUG(ENCRYPTKEY[i]);
DEBUGln();
radio.sleep();
if (flash.initialize()) flash.sleep();
pinMode(TRIG, OUTPUT);
pinMode(ECHO, INPUT);
pinMode(SENSOR_EN, OUTPUT);
digitalWrite(SENSOR_EN, LOW);
#ifdef BUZZER_ENABLE
pinMode(BUZZER, OUTPUT);
buzzer(50,2,100);
#endif
SERIALFLUSH();
readDistance(); //first reading seems to always be low
}
void loop() {
checkBattery();
distance = readDistance(READ_SAMPLES);
float diff = distance - prevDistance;
if ((diff > 1 || diff < -1) || (--sendLoops==0)) //only send a new message if the distance has changed by at least 1cm
{
if (distance > MAX_DISTANCE || distance < MIN_DISTANCE)
DISTstr = "0"; // zero, out of range
else dtostrf(distance,3,2, DISTstr);
sprintf(buff, "%scm BAT:%s", DISTstr, BATstr);
sendLen = strlen(buff);
digitalWrite(LED, HIGH);
DEBUG(buff);
if (radio.sendWithRetry(GATEWAYID, buff, sendLen))
{
prevDistance = distance;
DEBUG(" - ACK:OK! RSSI:");
DEBUGln(radio.RSSI);
}
else DEBUGln(" - ACK:NOK...");
digitalWrite(LED, LOW);
sendLoops = SENDLOOPS; //reset loop counter
}
radio.sleep();
SERIALFLUSH();
// if (radio.sendWithRetry(1, "123 TEST", 8))
// {
// //prevDistance = distance;
// DEBUG(" - ACK:OK! RSSI:");
// DEBUGln(radio.RSSI);
// }
// else DEBUGln(" - ACK:NOK...");
// SERIALFLUSH();
LowPower.powerDown(sleepTime, ADC_OFF, BOD_OFF); //put microcontroller to sleep to save battery life
}
float readDistance(byte samples)
{
if (samples == 0) samples = 1;
if (samples > 10) samples = 10;
digitalWrite(SENSOR_EN, HIGH);
//need about 60-75ms after power up before HC-SR04 will be usable, so just sleep in the meantime
LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF);
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
PING();
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
unsigned long duration = 0;
for (byte i=0; i<samples; i++)
{
duration += PING();
if (samples > 1) LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
}
digitalWrite(SENSOR_EN, LOW);
return microsecondsToCentimeters(duration / samples);
}
long PING()
{
digitalWrite(TRIG, LOW);
delayMicroseconds(2);
digitalWrite(TRIG, HIGH);
delayMicroseconds(5);
digitalWrite(TRIG, LOW);
return pulseIn(ECHO, HIGH);
}
byte cycleCount=BATT_CYCLES;
void checkBattery(byte samples)
{
if (cycleCount++ == BATT_CYCLES) //only read battery every BATT_CYCLES sleep cycles
{
unsigned int readings=0;
for (byte i=0; i<samples; i++) //take 10 samples, and average
readings+=analogRead(BATT_MONITOR);
batteryVolts = BATT_FORMULA(readings / 10.0);
dtostrf(batteryVolts,3,2, BATstr); //update the BATStr which gets sent every BATT_CYCLES or along with the MOTION message
if (batteryVolts <= BATT_LOW) BATstr = "LOW";
cycleCount = 0;
}
}
float microsecondsToInches(long microseconds)
{
// According to Parallax's datasheet for the PING))), there are
// 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
// second). This gives the distance travelled by the ping, outbound
// and return, so we divide by 2 to get the distance of the obstacle.
// See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
return microseconds / 74.0 / 2.0f;
}
float microsecondsToCentimeters(long microseconds)
{
// The speed of sound is 340 m/s or 29 microseconds per centimeter.
// The ping travels out and back, so to find the distance of the
// object we take half of the distance travelled.
return (float)microseconds / 29.0f / 2.0f;
}
#ifdef BUZZER_ENABLE
void buzzer(byte soundTime, byte repeats, byte repeatsDelay)
{
for (byte i=0;i<=repeats;i++)
{
tone(BUZZER, 4500); //4500hz makes a nice audible sound from a 3.3v Moteino digital pin
delay(soundTime);
noTone(BUZZER);
if (repeats>0) delay(repeatsDelay);
}
}
#endif
void Blink(byte pin)
{
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
delay(2);
digitalWrite(pin, LOW);
}

View File

@ -0,0 +1,417 @@
// Sample sketch for the SonarMote - Standalone parking assist with RGB LED indicator
// This example uses the NewPing library from https://code.google.com/p/arduino-new-ping/
// but that could be replaced by raw reading of the sonar sensor as seen in other SonarMote examples
// More info/photos at: http://lowpowerlab.com/sonar
// Ultrasonic sensor (HC-SR04) connected to D6 (Trig), D7 (Echo), and power enabled through D5
// This sketch sleeps the Moteino and sensor most of the time. It wakes up every few seconds to take
// a distance reading. If it detects an approaching object (car) it increases the sampling rate
// and starts lighting up the LED (from green to yellow to red to blinking red). Once there is no more
// motion the LED is turned off and the cycle is back to a few seconds in between sensor reads.
// Button is connected on D3. Holding the button for a few seconds enters the "red zone adjust" mode (RZA).
// By default the red zone limit is at 25cm (LED turns RED below this and starts blinking faster and faster).
// In RZA, readings are taken for 5 seconds. In this time you have the chance to set a new red zone limit.
// Valid new red zone readings are between the RED__LIMIT_UPPER (default 25cm) and MAX_ADJUST_DISTANCE (cm).
// In RZA mode the BLU Led blinks fast to indicate new red limit distance. It blinks slow if the readings are invalid
// If desired this value could be saved to EEPROM to persist if unit is turned off
// Make sure you adjust the settings in the configuration section below !!!
// **********************************************************************************
// Copyright Felix Rusu, LowPowerLab.com
// Library and code by Felix Rusu - felix@lowpowerlab.com
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
// **********************************************************************************
#include <NewPing.h> // get this library at: https://code.google.com/p/arduino-new-ping/
#include <LowPower.h> // get this library at: http://www.rocketscream.com/blog/2011/07/04/lightweight-low-power-arduino-library/
#define TRIG 6 // digital pin wired to TRIG pin of ultrasonic sensor
#define ECHO 7 // digital pin wired to ECHO pin of ultrasonic sensor
#define SENSOR_EN 5 // digital pin that enables power to ultrasonic sensor
#define RED A0 // pin connected to red LED
#define GRN A1 // pin connected to green LED
#define BLU A2 // pin connected to blue LED
#define BUTTON_INT 1 // user button on interrupt 1 (D3)
#define BUTTON_PIN 3 // user button on interrupt 1 (D3)
#define BUTTON_HOLD_MS 3000 // hold button this many ms before entering red zone adjust
#define MOTEINOLED 9 // moteino onboard LED
#define MAX_DISTANCE 220 // maximum valid distance
#define MIN_DISTANCE 2 // minimum valid distance
#define GRN_LIMIT_UPPER 180+redZoneAdjust // upper limit distance for GREEN
#define YLW_LIMIT_UPPER 40+redZoneAdjust // upper limit distance for YELLOW
#define RED_LIMIT_UPPER 25+redZoneAdjust // upper limit distance for RED
#define MAX_ADJUST_DISTANCE (MAX_DISTANCE-GRN_LIMIT_UPPER) //this is the amount by which the RED_LIMIT_UPPER can by increased
//possible states of LED status
#define STATE_SOLID 0
#define STATE_BLINK 1
//possible states for LED color
#define STATE_OFF 0
#define STATE_GRN 1
#define STATE_YLW 2
#define STATE_RED 3
byte state_LED = STATE_SOLID;
byte state_LEDCOLOR = STATE_OFF;
byte state_LEDONOFF = LOW;
byte redZoneAdjust = 0; //this is adjustable via the button (press button for a few seconds, then take a reading)
#define LED_RED {digitalWrite(RED,HIGH);digitalWrite(GRN,LOW);}
#define LED_GRN {digitalWrite(RED,LOW);digitalWrite(GRN,HIGH);}
#define LED_YLW {digitalWrite(RED,HIGH);digitalWrite(GRN,HIGH);}
#define LED_OFF {digitalWrite(RED,LOW);digitalWrite(GRN,LOW);}
#define SERIAL_EN //uncomment if you want serial debugging output
#ifdef SERIAL_EN
#define SERIAL_BAUD 115200
#define DEBUG(input) {Serial.print(input);}
#define DEBUGln(input) {Serial.println(input);}
#define SERIALFLUSH() {Serial.flush();}
#else
#define DEBUG(input);
#define DEBUGln(input);
#define SERIALFLUSH();
#endif
#define SLEEP_MINILOOP SLEEP_15MS
#define SLEEP_LOOP SLEEP_250MS
#define SLEEP_LONG SLEEP_2S
#define SLEEP_LOBATT SLEEP_4S
#define SLEEP_HIBERNATE SLEEP_8S
#define BATT_MONITOR A7 // Sense VBAT_COND signal (when powered externally should read ~3.25v/3.3v (1000-1023), when external power is cutoff it should start reading around 2.85v/3.3v * 1023 ~= 883 (ratio given by 10k+4.7K divider from VBAT_COND = 1.47 multiplier)
#define BATT_CYCLES 120 // read and report battery voltage every this many sleep cycles (ex 30cycles * 8sec sleep = 240sec/4min). For 450 cyclesyou would get ~1 hour intervals
#define BATT_FORMULA(reading) reading * 0.00322 * 1.475 // >>> fine tune this parameter to match your voltage when fully charged
#define BATT_LOW 3.35
NewPing sensor(TRIG, ECHO, MAX_DISTANCE); // NewPing setup of pins and maximum distance.
float readDistance(byte samples=3); //take 3 samples by default
void checkBattery(byte samples=10); //take 10 samples by default
float batteryVolts = 5;
void setup() {
#ifdef SERIAL_EN
Serial.begin(SERIAL_BAUD); // Open serial monitor at 115200 baud to see ping results.
#endif
pinMode(TRIG, OUTPUT);
pinMode(ECHO, INPUT);
pinMode(RED, OUTPUT);
pinMode(GRN, OUTPUT);
pinMode(BLU, OUTPUT);
pinMode(SENSOR_EN, OUTPUT);
digitalWrite(SENSOR_EN, LOW);
pinMode(BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(BUTTON_INT, buttonInterrupt, FALLING);
}
#define FLAG_INTERRUPT 0x01
volatile int mainEventFlags = 0;
boolean buttonPressed = false;
void buttonInterrupt()
{
mainEventFlags |= FLAG_INTERRUPT;
}
long distance=0;
long lastDistance=0;
long lastSigDistance=0;
byte loops=0;
byte miniLoops=0;
byte skipBlinkingLoops=5;
unsigned long now=0;
period_t sleepTime = SLEEP_LONG; //period_t is an enum type defined in the LowPower library (LowPower.h)
void loop() {
if (mainEventFlags & FLAG_INTERRUPT)
{
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_ON);
mainEventFlags &= ~FLAG_INTERRUPT;
if (!digitalRead(BUTTON_PIN)) {
buttonPressed=true;
}
}
if (buttonPressed)
{
detachInterrupt(BUTTON_INT);
DEBUGln("BUTTON PRESS!");
unsigned long timestamp = millis();
while (millis() - timestamp < BUTTON_HOLD_MS)
{
if (digitalRead(BUTTON_PIN))
{
buttonPressed = false;
break;
}
DEBUG('.');SERIALFLUSH();
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_ON);
timestamp-=40;
}
//if it's still pressed after BUTTON_HOLD_MS then enter red zone adjust mode
if (buttonPressed)
{
DEBUG("STILL_PRESSED");SERIALFLUSH();
handleRedZoneAdjust();
}
else
{
DEBUG("ABORTED");SERIALFLUSH();
}
attachInterrupt(BUTTON_INT, buttonInterrupt, FALLING);
}
if (miniLoops>0)
{
miniLoops--;
sleepTime = SLEEP_MINILOOP;
//when looping fast we need to wake the sensor about 60-75ms before doing a reading (about 5 miniloops assuming 1 miniloop=15ms)
//otherwise there will be a visible delay in the LED blinking
if (miniLoops == 5) digitalWrite(SENSOR_EN, HIGH);
}
else if (loops > 0)
{
loops--;
miniLoops=16; //16 mini loops translate
sleepTime = SLEEP_MINILOOP;
}
else
//sleep longer when no significant state changes happened
//if battery is low, sleep even longer to try to squeeze more life
sleepTime = (batteryVolts > BATT_LOW ? SLEEP_LONG : SLEEP_LOBATT);
if ((loops == 0 && miniLoops == 0) || ((miniLoops % skipBlinkingLoops) == 0))
{
DEBUG('*');
handleLEDState();
}
SERIALFLUSH(); //flush any characters in the serial buffer before sleeping otherwise they get lost or garbled
LowPower.powerDown(sleepTime, ADC_OFF, BOD_OFF); //put microcontroller to sleep to save battery life
if (miniLoops > 0) { DEBUG('.');return; } //as long as we still have miniloops we skip readings
//only proceed to a reading every loop
now = millis();
distance = readDistance();
DEBUGln();
DEBUG("Read: ");
DEBUG(distance); // Convert ping time to distance in cm and print result (0 = outside set distance range)
DEBUG("cm");
DEBUG(" [");
DEBUG(millis()-now);
DEBUGln("]ms");
if (distance > MAX_DISTANCE || distance < MIN_DISTANCE)
{
DEBUGln("Out of range");
loops=0;
lastDistance = distance;
return;
}
if (distance < GRN_LIMIT_UPPER && distance > YLW_LIMIT_UPPER && abs(lastSigDistance-distance)>20)
{
if (distance < lastSigDistance)
loops=12; //begin looping fast only when object is approaching
lastSigDistance = distance;
}
else if (distance < YLW_LIMIT_UPPER && abs(lastSigDistance-distance)>5)
{
if (distance < lastSigDistance)
loops=12; //begin looping fast only when object is approaching
lastSigDistance = distance;
}
//if the looping was started, determine the state we're in
if (loops > 0)
{
if (distance >= YLW_LIMIT_UPPER) { state_LEDCOLOR = STATE_GRN; state_LED = STATE_SOLID; }
else if (distance >= RED_LIMIT_UPPER) { state_LEDCOLOR=STATE_YLW; state_LED = STATE_SOLID; }
else { state_LEDCOLOR=STATE_RED; state_LED = STATE_BLINK; }
}
else { state_LEDCOLOR=STATE_OFF; state_LED = STATE_SOLID; }
//adjust the blinking rate based on the distance to the object
if (state_LEDCOLOR==STATE_RED)
{
if (distance > RED_LIMIT_UPPER-2)
skipBlinkingLoops = 8;
else if (distance > RED_LIMIT_UPPER-6)
skipBlinkingLoops = 6;
else if (lastDistance > RED_LIMIT_UPPER-10)
skipBlinkingLoops = 4;
else if (lastDistance > RED_LIMIT_UPPER-14)
skipBlinkingLoops = 2;
else
{
skipBlinkingLoops = 1;
state_LED = STATE_SOLID;
}
}
else skipBlinkingLoops = 1;
lastDistance = distance; //remember the last reading
checkBattery();
if (batteryVolts < BATT_LOW)
Blink(BLU);
else Blink(MOTEINOLED);
DEBUG("Batt: ");
DEBUG(batteryVolts);
DEBUGln("v");
}
//reads the ultrasonic sensor, takes 3 samples by default
float uS;
float readDistance(byte samples)
{
uS = 0;
if (loops == 0 && miniLoops == 0)
{
digitalWrite(SENSOR_EN, HIGH);
//need about 60-75ms after power up before HC-SR04 will be usable, so just sleep in the meantime
LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF);
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
}
sensor.ping();
for (byte i=0; i<samples; i++)
{
uS += sensor.ping(); // Send ping, get ping time in microseconds (uS).
if (samples >1) delay(4); //need a short delay between samples
}
digitalWrite(SENSOR_EN, LOW);
return (uS / samples) / US_ROUNDTRIP_CM;
}
////reads the ultrasonic sensor, takes 3 samples by default
//float readDistance(byte samples)
//{
// long duration, distance;
// digitalWrite(SENSOR_EN, HIGH);
// delay(75);
//
// digitalWrite(TRIG, LOW); // Added this line
// delayMicroseconds(2); // Added this line
// digitalWrite(TRIG, HIGH);
// delayMicroseconds(10); // Added this line
// digitalWrite(TRIG, LOW);
// pulseIn(ECHO, HIGH);
//
// digitalWrite(TRIG, LOW); // Added this line
// delayMicroseconds(2); // Added this line
// digitalWrite(TRIG, HIGH);
// delayMicroseconds(10); // Added this line
// digitalWrite(TRIG, LOW);
// duration = pulseIn(ECHO, HIGH);
// distance = (duration/2) / 29.1;
// digitalWrite(SENSOR_EN, LOW);
// return distance;
//}
//handles the status and color of the LED depending what state we are in
void handleLEDState()
{
switch(state_LEDCOLOR)
{
case STATE_OFF: LED_OFF; break;
case STATE_GRN: LED_GRN; break;
case STATE_YLW: LED_YLW; break;
case STATE_RED:
if (state_LED == STATE_BLINK)
{
if (state_LEDONOFF == HIGH)
{
LED_OFF;
state_LEDONOFF = LOW;
}
else
{
LED_RED;
state_LEDONOFF = HIGH;
}
}
else LED_RED;
break;
}
}
void Blink(byte pin)
{
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
delay(2);
digitalWrite(pin, LOW);
}
byte cycleCount=BATT_CYCLES;
void checkBattery(byte samples)
{
if (cycleCount++ == BATT_CYCLES) //only read battery every BATT_CYCLES sleep cycles
{
unsigned int readings=0;
for (byte i=0; i<samples; i++) //take 10 samples, and average
readings+=analogRead(BATT_MONITOR);
batteryVolts = BATT_FORMULA((float)readings / samples);
cycleCount = 0;
}
}
//enter red zone adjust mode
void handleRedZoneAdjust()
{
DEBUGln("\nRED_ZONE_ADJUST");SERIALFLUSH();
LED_OFF; //turn off all other LEDs
unsigned long startTimestamp=millis();
float distance;
int adjustTime = 5000;
byte state = HIGH;
while (millis()-startTimestamp < adjustTime)
{
digitalWrite(BLU, state);
state = state ? LOW : HIGH; //flip state for next loop
distance = readDistance();
if (distance > MAX_ADJUST_DISTANCE + RED_LIMIT_UPPER-redZoneAdjust)
delay(300);
else if (distance <= RED_LIMIT_UPPER-redZoneAdjust)
state = HIGH; //keep LED on
else
delay (distance);
DEBUG(distance);DEBUGln("cm");SERIALFLUSH();
Blink(MOTEINOLED);
}
digitalWrite(BLU, LOW); //turn LED off
if (distance > RED_LIMIT_UPPER-redZoneAdjust && distance <= MAX_ADJUST_DISTANCE + RED_LIMIT_UPPER-redZoneAdjust)
{
redZoneAdjust = distance - RED_LIMIT_UPPER - redZoneAdjust;
DEBUG("New RED_ZONE_SHIFT = "); DEBUGln(redZoneAdjust);SERIALFLUSH();
}
}

View File

@ -0,0 +1,511 @@
// Sample sketch for the SonarMote - Standalone parking assist with RGB LED indicator, piezo buzzer and OLED display
// This example uses the NewPing library from https://code.google.com/p/arduino-new-ping/
// but that could be replaced by raw reading of the sonar sensor as seen in other SonarMote examples
// More info/photos at: http://lowpowerlab.com/sonar
// Ultrasonic sensor (HC-SR04) connected to D6 (Trig), D7 (Echo), and power enabled through D5
// This sketch sleeps the Moteino and sensor most of the time. It wakes up every few seconds to take
// a distance reading. If it detects an approaching object (car) it increases the sampling rate
// and starts lighting up the LED (from green to yellow to red to blinking red). Once there is no more
// motion the LED is turned off and the cycle is back to a few seconds in between sensor reads.
// Button is connected on D3. Holding the button for a few seconds enters the "red zone adjust" mode (RZA).
// By default the red zone limit is at 25cm (LED turns RED below this and starts blinking faster and faster).
// In RZA, readings are taken for 5 seconds. In this time you have the chance to set a new red zone limit.
// Valid new red zone readings are between the RED__LIMIT_UPPER (default 25cm) and MAX_ADJUST_DISTANCE (cm).
// In RZA mode the BLU Led blinks fast to indicate new red limit distance. It blinks slow if the readings are invalid
// If desired this value could be saved to EEPROM to persist if unit is turned off
// Make sure you adjust the settings in the configuration section below !!!
// **********************************************************************************
// Copyright Felix Rusu, LowPowerLab.com
// Library and code by Felix Rusu - felix@lowpowerlab.com
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
// **********************************************************************************
#include <LowPower.h> // get this library at: http://www.rocketscream.com/blog/2011/07/04/lightweight-low-power-arduino-library/
//#define BUZZER_ENABLE //uncomment this line if you have the BUZZER soldered and want the sketch to make sounds
//#define OLED_ENABLE //uncomment this line if you have an OLED attached on the SonarMote and want to see the distance printed on it
//the OLED drawing will cause a visible delay in the LED blinking, nothing much to do about it without complicating the sketch a lot more
#ifdef OLED_ENABLE
#include "U8glib.h" //get library from: https://code.google.com/p/u8glib/
#endif
#ifdef __AVR_ATmega1284P__
#define LED 15 // Moteino MEGAs have LEDs on D15
#else
#define LED 9 // Moteinos have LEDs on D9
#endif
#define TRIG 6 // digital pin wired to TRIG pin of ultrasonic sensor
#define ECHO 7 // digital pin wired to ECHO pin of ultrasonic sensor
#define SENSOR_EN 5 // digital pin that enables power to ultrasonic sensor
#define RED A0 // pin connected to red LED
#define GRN A1 // pin connected to green LED
#define BLU A2 // pin connected to blue LED
#define BUZZER 4
#define BUTTON_INT 1 // user button on interrupt 1 (D3)
#define BUTTON_PIN 3 // user button on interrupt 1 (D3)
#define BUTTON_HOLD_MS 3000 // hold button this many ms before entering red zone adjust
#define MAX_DISTANCE 220 // maximum valid distance
#define MIN_DISTANCE 2 // minimum valid distance
#define GRN_LIMIT_UPPER 180+redZoneAdjust // upper limit distance for GREEN
#define YLW_LIMIT_UPPER 40+redZoneAdjust // upper limit distance for YELLOW
#define RED_LIMIT_UPPER 25+redZoneAdjust // upper limit distance for RED
#define MAX_ADJUST_DISTANCE (MAX_DISTANCE-GRN_LIMIT_UPPER) //this is the amount by which the RED_LIMIT_UPPER can by increased
//possible states of LED status
#define STATE_SOLID 0
#define STATE_BLINK 1
//possible states for LED color
#define STATE_OFF 0
#define STATE_GRN 1
#define STATE_YLW 2
#define STATE_RED 3
byte state_LED = STATE_SOLID;
byte state_LEDCOLOR = STATE_OFF;
byte state_LEDONOFF = LOW;
byte redZoneAdjust = 0; //this is adjustable via the button (press button for a few seconds, then take a reading)
#define LED_RED {digitalWrite(RED,HIGH);digitalWrite(GRN,LOW);}
#define LED_GRN {digitalWrite(RED,LOW);digitalWrite(GRN,HIGH);}
#define LED_YLW {digitalWrite(RED,HIGH);digitalWrite(GRN,HIGH);}
#define LED_OFF {digitalWrite(RED,LOW);digitalWrite(GRN,LOW);}
#define SERIAL_EN //uncomment if you want serial debugging output
#ifdef SERIAL_EN
#define SERIAL_BAUD 115200
#define DEBUG(input) {Serial.print(input);}
#define DEBUGln(input) {Serial.println(input);}
#define SERIALFLUSH() {Serial.flush();}
#else
#define DEBUG(input);
#define DEBUGln(input);
#define SERIALFLUSH();
#endif
#define SLEEP_MINILOOP SLEEP_15MS
#define SLEEP_LOOP SLEEP_250MS
#define SLEEP_LONG SLEEP_2S
#define SLEEP_LOBATT SLEEP_4S
#define SLEEP_HIBERNATE SLEEP_8S
#define BATT_MONITOR A7 // Sense VBAT_COND signal (when powered externally should read ~3.25v/3.3v (1000-1023), when external power is cutoff it should start reading around 2.85v/3.3v * 1023 ~= 883 (ratio given by 10k+4.7K divider from VBAT_COND = 1.47 multiplier)
#define BATT_CYCLES 120 // read and report battery voltage every this many sleep cycles (ex 30cycles * 8sec sleep = 240sec/4min). For 450 cyclesyou would get ~1 hour intervals
#define BATT_FORMULA(reading) reading * 0.00322 * 1.475 // >>> fine tune this parameter to match your voltage when fully charged
#define BATT_LOW 3.35
#ifdef OLED_ENABLE
U8GLIB_SSD1306_128X64 OLED(U8G_I2C_OPT_NONE); // I2C / TWI SSD1306 OLED 128x64
#endif
void checkBattery(byte samples=10); //take 10 samples by default
float batteryVolts = 5;
char buff[50];
void setup() {
#ifdef SERIAL_EN
Serial.begin(SERIAL_BAUD); // Open serial monitor at 115200 baud to see ping results.
#endif
pinMode(TRIG, OUTPUT);
pinMode(ECHO, INPUT);
pinMode(RED, OUTPUT);
pinMode(GRN, OUTPUT);
pinMode(BLU, OUTPUT);
pinMode(SENSOR_EN, OUTPUT);
pinMode(BUZZER, OUTPUT);
digitalWrite(SENSOR_EN, LOW);
pinMode(BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(BUTTON_INT, buttonInterrupt, FALLING);
#ifdef OLED_ENABLE
OLED.setRot180(); //flip screen
// assign default color value
if (OLED.getMode() == U8G_MODE_R3G3B2 )
OLED.setColorIndex(255); // white
else if (OLED.getMode() == U8G_MODE_GRAY2BIT)
OLED.setColorIndex(3); // max intensity
else if (OLED.getMode() == U8G_MODE_BW)
OLED.setColorIndex(1); // pixel on
else if (OLED.getMode() == U8G_MODE_HICOLOR)
OLED.setHiColorByRGB(255,255,255);
OLED.begin();
OLED.firstPage();
OLED.setFont(u8g_font_unifont);
do {
OLED.drawStr(0, 10, "SonarMote");
} while(OLED.nextPage());
#endif
#ifdef BUZZER_ENABLE
buzzer(50,2,100);
#endif
readDistance(); //first reading seems to always be low
}
#define FLAG_INTERRUPT 0x01
volatile int mainEventFlags = 0;
boolean buttonPressed = false;
void buttonInterrupt()
{
mainEventFlags |= FLAG_INTERRUPT;
}
long distance=0;
long lastDistance=0;
long lastSigDistance=0;
byte loops=0;
byte miniLoops=0;
byte skipBlinkingLoops=5;
unsigned long now=0;
period_t sleepTime = SLEEP_LONG; //period_t is an enum type defined in the LowPower library (LowPower.h)
void loop() {
if (mainEventFlags & FLAG_INTERRUPT)
{
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_ON);
mainEventFlags &= ~FLAG_INTERRUPT;
if (!digitalRead(BUTTON_PIN)) {
buttonPressed=true;
}
}
if (buttonPressed)
{
detachInterrupt(BUTTON_INT);
DEBUGln("BUTTON PRESS!");
unsigned long timestamp = millis();
while (millis() - timestamp < BUTTON_HOLD_MS)
{
if (digitalRead(BUTTON_PIN))
{
buttonPressed = false;
break;
}
DEBUG('.');SERIALFLUSH();
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_ON);
timestamp-=40;
}
//if it's still pressed after BUTTON_HOLD_MS then enter red zone adjust mode
if (buttonPressed)
{
DEBUG("STILL_PRESSED");SERIALFLUSH();
handleRedZoneAdjust();
}
else
{
DEBUG("ABORTED");SERIALFLUSH();
}
attachInterrupt(BUTTON_INT, buttonInterrupt, FALLING);
}
if (miniLoops>0)
{
miniLoops--; //miniloops starts at
sleepTime = SLEEP_MINILOOP;
//EARLY SENSOR WAKEUP
//When looping fast we need to wake the sensor about 60-75ms before doing a reading (about 5 miniloops assuming 1 miniloop=15ms)
//otherwise there will be a visible delay in the LED blinking
if (miniLoops == 6) digitalWrite(SENSOR_EN, HIGH);
else if (miniLoops == 1) sacrificialPing(); //need 5 miniloops (75ms) between wakeup and dummy reading and another 15ms (1 miniloop) to real reading
//reading will happen when miniLoops==0
}
else if (loops > 0)
{
loops--;
miniLoops=16; //16 "miniloops" form 1 "loop" (~240ms)
sleepTime = SLEEP_MINILOOP;
}
else
//sleep longer when no significant state changes happened
//if battery is low, sleep even longer to try to squeeze more life
sleepTime = (batteryVolts > BATT_LOW ? SLEEP_LONG : SLEEP_LOBATT);
if ((loops == 0 && miniLoops == 0) || ((miniLoops % skipBlinkingLoops) == 0))
{
DEBUG('*');
handleLEDState();
}
SERIALFLUSH(); //flush any characters in the serial buffer before sleeping otherwise they get lost or garbled
LowPower.powerDown(sleepTime, ADC_OFF, BOD_OFF); //put microcontroller to sleep to save battery life
if (miniLoops > 0) { DEBUG('.');return; } //as long as we still have miniloops we skip readings
//only proceed to a reading every loop
now = millis();
distance = readDistance();
byte d = millis()-now;
#ifdef OLED_ENABLE
draw(distance);
#endif
DEBUG("Read: ");
DEBUG(distance); // Convert ping time to distance in cm and print result (0 = outside set distance range)
DEBUG("cm");
DEBUG(" [");
DEBUG(d);
DEBUGln("]ms");
if (distance > MAX_DISTANCE || distance < MIN_DISTANCE)
{
DEBUGln("Out of range");
loops=0;
lastDistance = distance;
return;
}
if (distance < GRN_LIMIT_UPPER && distance > YLW_LIMIT_UPPER && abs(lastSigDistance-distance)>20)
{
if (distance < lastSigDistance)
loops=12; //begin looping fast only when object is approaching
lastSigDistance = distance;
}
else if (distance < YLW_LIMIT_UPPER && abs(lastSigDistance-distance)>5)
{
if (distance < lastSigDistance)
loops=12; //begin looping fast only when object is approaching
lastSigDistance = distance;
}
//if the looping was started, determine the state we're in
if (loops > 0)
{
if (distance >= YLW_LIMIT_UPPER) { state_LEDCOLOR = STATE_GRN; state_LED = STATE_SOLID; }
else if (distance >= RED_LIMIT_UPPER) { state_LEDCOLOR=STATE_YLW; state_LED = STATE_SOLID; }
else { state_LEDCOLOR=STATE_RED; state_LED = STATE_BLINK; }
}
else { state_LEDCOLOR=STATE_OFF; state_LED = STATE_SOLID; }
//adjust the blinking rate based on the distance to the object
if (state_LEDCOLOR==STATE_RED)
{
if (distance > RED_LIMIT_UPPER-2)
skipBlinkingLoops = 8;
else if (distance > RED_LIMIT_UPPER-6)
skipBlinkingLoops = 6;
else if (lastDistance > RED_LIMIT_UPPER-10)
skipBlinkingLoops = 4;
else if (lastDistance > RED_LIMIT_UPPER-14)
skipBlinkingLoops = 2;
else
{
skipBlinkingLoops = 1;
state_LED = STATE_SOLID;
}
}
else skipBlinkingLoops = 1;
lastDistance = distance; //remember the last reading
checkBattery();
if (batteryVolts < BATT_LOW)
Blink(BLU);
else Blink(LED);
DEBUG("Batt: ");
DEBUG(batteryVolts);
DEBUGln("v");
}
float readDistance()
{
//To save battery we need to sleep the HC-SR04 sonar sensor in between readings.
//However to get valid readings it requires special waking up and delay
//It needs to be powered up for ~75ms, then a dummy reading has to be made which is typically bogus
//Then another 15ms needs to pass before doing the real reading
//Because the main loop sleeps between readings and we want to avoid visible delays in the LED blinking
// we need to wake up the sensor in the main loop, look for "EARLY SENSOR WAKEUP".
// When looping fast in the main loop (aka minilooping, to allow fast LED blinking)
// each "miniloop" is about 15ms of sleep time. So we should wake up the sensor 5 miniloops before we do the reading
// and do the sacrificial dummy reading 1 miniloop before the real reading. See the "EARLY SENSOR WAKEUP" code in the main loop.
//when not looping fast in the main loop, just do the special sensor wakeup here
//first enable sensor power and sleep MCU while sensor settles (needs)75ms
if (loops == 0 && miniLoops == 0)
{
digitalWrite(SENSOR_EN, HIGH);
//need about 60-75ms after power up before HC-SR04 will be usable, so just sleep in the meantime
LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF);
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
// do a dummy reading first and wait another 15ms for the real reading
sacrificialPing();
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
}
// Now do the real reading
// The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
// Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
digitalWrite(TRIG, LOW);
delayMicroseconds(2);
digitalWrite(TRIG, HIGH);
delayMicroseconds(5);
digitalWrite(TRIG, LOW);
// The same pin is used to read the signal from the PING))): a HIGH
// pulse whose duration is the time (in microseconds) from the sending
// of the ping to the reception of its echo off of an object.
long duration = pulseIn(ECHO, HIGH);
digitalWrite(SENSOR_EN, LOW);
return microsecondsToCentimeters(duration);
}
void sacrificialPing()
{
digitalWrite(TRIG, LOW);
delayMicroseconds(2);
digitalWrite(TRIG, HIGH);
delayMicroseconds(5);
digitalWrite(TRIG, LOW);
pulseIn(ECHO, HIGH);
}
//handles the status and color of the LED depending what state we are in
void handleLEDState()
{
switch(state_LEDCOLOR)
{
case STATE_OFF: LED_OFF; break;
case STATE_GRN: LED_GRN; break;
case STATE_YLW: LED_YLW; break;
case STATE_RED:
if (state_LED == STATE_BLINK)
{
if (state_LEDONOFF == HIGH)
{
LED_OFF;
state_LEDONOFF = LOW;
}
else
{
LED_RED;
state_LEDONOFF = HIGH;
#ifdef BUZZER_ENABLE
buzzer(skipBlinkingLoops,0,0);
#endif
}
}
else LED_RED;
break;
}
}
void Blink(byte pin)
{
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
delay(2);
digitalWrite(pin, LOW);
}
byte cycleCount=BATT_CYCLES;
void checkBattery(byte samples)
{
if (cycleCount++ == BATT_CYCLES) //only read battery every BATT_CYCLES sleep cycles
{
unsigned int readings=0;
for (byte i=0; i<samples; i++) //take 10 samples, and average
readings+=analogRead(BATT_MONITOR);
batteryVolts = BATT_FORMULA((float)readings / samples);
cycleCount = 0;
}
}
//enter red zone adjust mode
void handleRedZoneAdjust()
{
DEBUGln("\nRED_ZONE_ADJUST");SERIALFLUSH();
LED_OFF; //turn off all other LEDs
unsigned long startTimestamp=millis();
float distance;
int adjustTime = 5000;
byte state = HIGH;
while (millis()-startTimestamp < adjustTime)
{
digitalWrite(BLU, state);
state = state ? LOW : HIGH; //flip state for next loop
distance = readDistance();
if (distance > MAX_ADJUST_DISTANCE + RED_LIMIT_UPPER-redZoneAdjust)
delay(300);
else if (distance <= RED_LIMIT_UPPER-redZoneAdjust)
state = HIGH; //keep LED on
else
delay (distance);
DEBUG(distance);DEBUGln("cm");SERIALFLUSH();
Blink(LED);
}
digitalWrite(BLU, LOW); //turn LED off
if (distance > RED_LIMIT_UPPER-redZoneAdjust && distance <= MAX_ADJUST_DISTANCE + RED_LIMIT_UPPER-redZoneAdjust)
{
redZoneAdjust = distance - RED_LIMIT_UPPER - redZoneAdjust;
DEBUG("New RED_ZONE_SHIFT = "); DEBUGln(redZoneAdjust);SERIALFLUSH();
}
}
float microsecondsToInches(long microseconds)
{
// According to Parallax's datasheet for the PING))), there are
// 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
// second). This gives the distance travelled by the ping, outbound
// and return, so we divide by 2 to get the distance of the obstacle.
// See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
return microseconds / 74.0 / 2.0f;
}
float microsecondsToCentimeters(long microseconds)
{
// The speed of sound is 340 m/s or 29 microseconds per centimeter.
// The ping travels out and back, so to find the distance of the
// object we take half of the distance travelled.
return (float)microseconds / 29.0f / 2.0f;
}
#ifdef OLED_ENABLE
void draw(byte distance) {
OLED.firstPage();
OLED.setFont(u8g_font_unifont);
sprintf(buff, "Reading: %dcm", distance);
do {
OLED.drawStr(0, 10, buff);
} while(OLED.nextPage());
}
#endif
#ifdef BUZZER_ENABLE
void buzzer(byte soundTime, byte repeats, byte repeatsDelay)
{
for (byte i=0;i<=repeats;i++)
{
tone(BUZZER, 4500); //4500hz makes a nice audible sound from a 3.3v Moteino digital pin
delay(soundTime);
noTone(BUZZER);
if (repeats>0) delay(repeatsDelay);
}
}
#endif

View File

@ -5,7 +5,7 @@
#define NODEID 1 #define NODEID 1
#define NETWORKID 100 #define NETWORKID 100
#define FREQUENCY RF69_433MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ) #define FREQUENCY RF69_433MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
#define KEY "thisIsEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less! #define KEY "sampleEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less!
#define LED 9 #define LED 9
#define SERIAL_BAUD 115200 #define SERIAL_BAUD 115200
#define ACK_TIME 30 // # of ms to wait for an ack #define ACK_TIME 30 // # of ms to wait for an ack
@ -14,7 +14,7 @@ RFM69 radio;
SPIFlash flash(8, 0xEF30); //EF40 for 16mbit windbond chip SPIFlash flash(8, 0xEF30); //EF40 for 16mbit windbond chip
bool promiscuousMode = false; //set to 'true' to sniff all packets on the same network bool promiscuousMode = false; //set to 'true' to sniff all packets on the same network
typedef struct { typedef struct {
int nodeId; //store this nodeId int nodeId; //store this nodeId
unsigned long uptime; //uptime in ms unsigned long uptime; //uptime in ms
float temp; //temperature maybe? float temp; //temperature maybe?
@ -88,10 +88,10 @@ void loop() {
Serial.print('[');Serial.print(radio.SENDERID, DEC);Serial.print("] "); Serial.print('[');Serial.print(radio.SENDERID, DEC);Serial.print("] ");
Serial.print(" [RX_RSSI:");Serial.print(radio.readRSSI());Serial.print("]"); Serial.print(" [RX_RSSI:");Serial.print(radio.readRSSI());Serial.print("]");
if (promiscuousMode) if (promiscuousMode)
{ {
Serial.print("to [");Serial.print(radio.TARGETID, DEC);Serial.print("] "); Serial.print("to [");Serial.print(radio.TARGETID, DEC);Serial.print("] ");
} }
if (radio.DATALEN != sizeof(Payload)) if (radio.DATALEN != sizeof(Payload))
Serial.print("Invalid payload received, not matching Payload struct!"); Serial.print("Invalid payload received, not matching Payload struct!");
else else

View File

@ -6,7 +6,7 @@
#define NETWORKID 100 #define NETWORKID 100
#define GATEWAYID 1 #define GATEWAYID 1
#define FREQUENCY RF69_433MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ) #define FREQUENCY RF69_433MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
#define KEY "thisIsEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less! #define KEY "sampleEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less!
#define LED 9 #define LED 9
#define SERIAL_BAUD 115200 #define SERIAL_BAUD 115200
#define ACK_TIME 30 // # of ms to wait for an ack #define ACK_TIME 30 // # of ms to wait for an ack
@ -17,7 +17,7 @@ boolean requestACK = false;
SPIFlash flash(8, 0xEF30); //EF40 for 16mbit windbond chip SPIFlash flash(8, 0xEF30); //EF40 for 16mbit windbond chip
RFM69 radio; RFM69 radio;
typedef struct { typedef struct {
int nodeId; //store this nodeId int nodeId; //store this nodeId
unsigned long uptime; //uptime in ms unsigned long uptime; //uptime in ms
float temp; //temperature maybe? float temp; //temperature maybe?

View File

@ -12,7 +12,7 @@
// On the sender, hook up a momentary tactile button to D3 like this: // On the sender, hook up a momentary tactile button to D3 like this:
// __-__ // __-__
// __| |___ // __| |___
// GND ----> BTN ----> D3 // GND ----> BTN ----> D3 (D11 on MoteinoMEGA)
// Load this sketch on the RECEIVER with NODEID=RECEIVER (adjust in config section below) // Load this sketch on the RECEIVER with NODEID=RECEIVER (adjust in config section below)
// Load this sketch on the SENDER with NODEID=SENDER (adjust in config section below) // Load this sketch on the SENDER with NODEID=SENDER (adjust in config section below)
// RFM69 library and code by Felix Rusu - felix@lowpowerlab.com // RFM69 library and code by Felix Rusu - felix@lowpowerlab.com
@ -65,11 +65,21 @@
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes! #define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
#define IS_RFM69HW //uncomment only for RFM69HW! Remove/comment if you have RFM69W! #define IS_RFM69HW //uncomment only for RFM69HW! Remove/comment if you have RFM69W!
//********************************************************************************************* //*********************************************************************************************
#define SERIAL_BAUD 115200 #define SERIAL_BAUD 115200
#define LED 9 //Moteinos have onboard LEDs on D9 #ifdef __AVR_ATmega1284P__
#define BUTTON_INT 1 //user button on interrupt 1 (D3) #define LED 15 // Moteino MEGAs have LEDs on D15
#define BUTTON_PIN 3 //user button on interrupt 1 (D3) #define BUTTON_INT 1 //user button on interrupt 1 (D3)
#define BUTTON_PIN 11 //user button on interrupt 1 (D3)
#else
#define LED 9 // Moteinos have LEDs on D9
#define BUTTON_INT 1 //user button on interrupt 1 (D3)
#define BUTTON_PIN 3 //user button on interrupt 1 (D3)
#endif
#define LED_GREEN 4 //GREEN LED on the SENDER
#define LED_RED 5 //RED LED on the SENDER
#define RX_TOGGLE_PIN 7 //GPIO to toggle on the RECEIVER
RFM69 radio; RFM69 radio;
void setup() { void setup() {
@ -86,6 +96,12 @@ void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP); pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED, OUTPUT); pinMode(LED, OUTPUT);
attachInterrupt(BUTTON_INT, handleButton, FALLING); attachInterrupt(BUTTON_INT, handleButton, FALLING);
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_RED, OUTPUT);
pinMode(RX_TOGGLE_PIN, OUTPUT);
digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_RED, HIGH);
} }
//******** THIS IS INTERRUPT BASED DEBOUNCING FOR BUTTON ATTACHED TO D3 (INTERRUPT 1) //******** THIS IS INTERRUPT BASED DEBOUNCING FOR BUTTON ATTACHED TO D3 (INTERRUPT 1)
@ -102,7 +118,7 @@ void loop() {
//******** THIS IS INTERRUPT BASED DEBOUNCING FOR BUTTON ATTACHED TO D3 (INTERRUPT 1) //******** THIS IS INTERRUPT BASED DEBOUNCING FOR BUTTON ATTACHED TO D3 (INTERRUPT 1)
if (mainEventFlags & FLAG_INTERRUPT) if (mainEventFlags & FLAG_INTERRUPT)
{ {
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_ON); LowPower.powerDown(SLEEP_120MS, ADC_OFF, BOD_ON);
mainEventFlags &= ~FLAG_INTERRUPT; mainEventFlags &= ~FLAG_INTERRUPT;
if (!digitalRead(BUTTON_PIN)) { if (!digitalRead(BUTTON_PIN)) {
buttonPressed=true; buttonPressed=true;
@ -113,6 +129,20 @@ void loop() {
{ {
Serial.println("Button pressed!"); Serial.println("Button pressed!");
buttonPressed = false; buttonPressed = false;
if(LEDSTATE==LOW)
{
LEDSTATE=HIGH;
digitalWrite(LED_GREEN, HIGH);
digitalWrite(LED_RED, LOW);
}
else
{
LEDSTATE=LOW;
digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_RED, HIGH);
}
if (radio.sendWithRetry(RECEIVER, "Hi", 2)) //target node Id, message as string or byte array, message length if (radio.sendWithRetry(RECEIVER, "Hi", 2)) //target node Id, message as string or byte array, message length
Blink(LED, 40, 3); //blink LED 3 times, 40ms between blinks Blink(LED, 40, 3); //blink LED 3 times, 40ms between blinks
} }
@ -133,6 +163,7 @@ void loop() {
LEDSTATE=HIGH; LEDSTATE=HIGH;
else LEDSTATE=LOW; else LEDSTATE=LOW;
digitalWrite(LED, LEDSTATE); digitalWrite(LED, LEDSTATE);
digitalWrite(RX_TOGGLE_PIN, LEDSTATE);
} }
//check if sender wanted an ACK //check if sender wanted an ACK
@ -157,4 +188,4 @@ void Blink(byte PIN, byte DELAY_MS, byte loops)
digitalWrite(PIN,LOW); digitalWrite(PIN,LOW);
delay(DELAY_MS); delay(DELAY_MS);
} }
} }

View File

@ -0,0 +1,269 @@
// **********************************************************************************************************
// WeatherShield sketch that works with Moteinos equipped with RFM69W/RFM69HW and WeatherShield
// It sends periodic highly accurate weather readings (temp, hum, atm pressure) from the
// WeatherShield to the base node/gateway Moteino
// Can be adapted to use Moteinos/Arduinos using RFM12B or other RFM69 variants (RFM69CW, RFM69HCW)
// For use with MoteinoMEGA you will have to revisit the pin definitions defined below
// http://www.LowPowerLab.com/WeatherShield
// Used in this project: http://lowpowerlab.com/blog/2015/07/24/attic-fan-cooling-tests/
// 2015-07-23 (C) Felix Rusu of http://www.LowPowerLab.com/
// **********************************************************************************************************
// License
// **********************************************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
// ***************************************************************************************************************************
#include <RFM69.h> //get it here: http://github.com/lowpowerlab/rfm69
#include <SPIFlash.h> //get it here: http://github.com/lowpowerlab/spiflash
#include <WirelessHEX69.h> //get it here: https://github.com/LowPowerLab/WirelessProgramming
#include <SPI.h> //comes with Arduino
#include <SFE_BMP180.h> //get it here: https://github.com/LowPowerLab/SFE_BMP180
#include <SI7021.h> //get it here: https://github.com/LowPowerLab/SI7021
#include <Wire.h> //comes with Arduino
#include <LowPower.h> //get library from: https://github.com/lowpowerlab/lowpower
//writeup here: http://www.rocketscream.com/blog/2011/07/04/lightweight-low-power-arduino-library/
//*****************************************************************************************************************************
// ADJUST THE SETTINGS BELOW DEPENDING ON YOUR HARDWARE/TRANSCEIVER SETTINGS/REQUIREMENTS
//*****************************************************************************************************************************
#define GATEWAYID 1
#define NODEID 164
#define NETWORKID 100
//#define FREQUENCY RF69_433MHZ
//#define FREQUENCY RF69_868MHZ
#define FREQUENCY RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
#define ENCRYPTKEY "sampleEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less!
//#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W!
#define SEND_LOOPS 15 //send data this many sleep loops (15 loops of 8sec cycles = 120sec ~ 2 minutes)
//*********************************************************************************************
#define SLEEP_FASTEST SLEEP_15MS
#define SLEEP_FAST SLEEP_250MS
#define SLEEP_SEC SLEEP_1S
#define SLEEP_LONG SLEEP_2S
#define SLEEP_LONGER SLEEP_4S
#define SLEEP_LONGEST SLEEP_8S
period_t sleepTime = SLEEP_LONGEST; //period_t is an enum type defined in the LowPower library (LowPower.h)
//*********************************************************************************************
#define BATT_MONITOR_EN A3 //enables battery voltage divider to get a reading from a battery, disable it to save power
#define BATT_MONITOR A7 //through 1Meg+470Kohm and 0.1uF cap from battery VCC - this ratio divides the voltage to bring it below 3.3V where it is scaled to a readable range
#define BATT_CYCLES 2 //read and report battery voltage every this many sleep cycles (ex 30cycles * 8sec sleep = 240sec/4min). For 450 cyclesyou would get ~1 hour intervals
#define BATT_FORMULA(reading) reading * 0.00322 * 1.475 // >>> fine tune this parameter to match your voltage when fully charged
#define BATT_LOW 3.6 //(volts)
#define BATT_READ_LOOPS SEND_LOOPS*10 // read and report battery voltage every this many sleep cycles (ex 30cycles * 8sec sleep = 240sec/4min). For 450 cycles you would get ~1 hour intervals between readings
//*****************************************************************************************************************************
#define LED 9 //pin connected to onboard LED on regular Moteinos
//#define BLINK_EN //uncomment to blink LED on every send
#define SERIAL_EN //comment out if you don't want any serial output
#ifdef SERIAL_EN
#define SERIAL_BAUD 115200
#define DEBUG(input) {Serial.print(input);}
#define DEBUGln(input) {Serial.println(input);}
#define SERIALFLUSH() {Serial.flush();}
#else
#define DEBUG(input);
#define DEBUGln(input);
#define SERIALFLUSH();
#endif
//*****************************************************************************************************************************
//global program variables
SI7021 weatherShield_SI7021;
SFE_BMP180 weatherShield_BMP180;
RFM69 radio;
char Pstr[10];
char buffer[50];
SPIFlash flash(8, 0xEF30); //WINDBOND 4MBIT flash chip on CS pin D8 (default for Moteino)
void setup(void)
{
#ifdef SERIAL_EN
Serial.begin(SERIAL_BAUD);
#endif
pinMode(LED, OUTPUT);
radio.initialize(FREQUENCY,NODEID,NETWORKID);
#ifdef IS_RFM69HW
radio.setHighPower(); //uncomment only for RFM69HW!
#endif
radio.encrypt(ENCRYPTKEY);
sprintf(buffer, "WeatherMote - transmitting at: %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
DEBUGln(buffer);
//initialize weather shield sensors
weatherShield_SI7021.begin();
if (weatherShield_BMP180.begin())
{ DEBUGln("BMP180 init success"); }
else { DEBUGln("BMP180 init fail\n"); }
radio.sendWithRetry(GATEWAYID, "START", 6);
Blink(LED, 100);Blink(LED, 100);Blink(LED, 100);
SERIALFLUSH();
readBattery();
}
unsigned long doorPulseCount = 0;
char input=0;
double P;
byte sendLoops=0;
byte battReadLoops=0;
float batteryVolts = 5;
char* BATstr="BAT:5.00v"; //longest battery voltage reading message = 9chars
byte sendLen;
void loop()
{
if (battReadLoops--<=0) //only read battery every BATT_READ_LOOPS cycles
{
readBattery();
battReadLoops = BATT_READ_LOOPS-1;
}
if (sendLoops--<=0) //send readings every SEND_LOOPS
{
sendLoops = SEND_LOOPS-1;
P = getPressure();
P*=0.0295333727; //transform to inHg
dtostrf(P, 3,2, Pstr);
sprintf(buffer, "BAT:%sv F:%d H:%d P:%s", BATstr, weatherShield_SI7021.getFahrenheitHundredths(), weatherShield_SI7021.getHumidityPercent(), Pstr);
sendLen = strlen(buffer);
radio.sendWithRetry(GATEWAYID, buffer, sendLen, 1); //retry one time
DEBUG(buffer); DEBUG(" (packet length:"); DEBUG(sendLen); DEBUGln(")");
#ifdef BLINK_EN
Blink(LED, 5);
#endif
}
//When this sketch is on a node where you can afford the power to keep the radio awake all the time
// you can make it receive messages and also make it wirelessly programmable
// otherwise this section can be removed
if (radio.receiveDone())
{
boolean reportStatusRequest=false;
DEBUG('[');DEBUG(radio.SENDERID);DEBUG("] ");
for (byte i = 0; i < radio.DATALEN; i++)
DEBUG((char)radio.DATA[i]);
// wireless programming token check - this only works when radio is kept awake to listen for WP tokens
CheckForWirelessHEX(radio, flash, true);
//first send any ACK to request
DEBUG(" [RX_RSSI:");DEBUG(radio.RSSI);DEBUG("]");
if (radio.ACKRequested())
{
radio.sendACK();
DEBUG(" - ACK sent.");
}
DEBUGln();
}
SERIALFLUSH();
radio.sleep(); //you can comment out this line if you want this node to listen for wireless programming requests
LowPower.powerDown(sleepTime, ADC_OFF, BOD_OFF);
DEBUGln("WAKEUP");
}
double getPressure()
{
char status;
double T,P,p0,a;
// If you want sea-level-compensated pressure, as used in weather reports,
// you will need to know the altitude at which your measurements are taken.
// We're using a constant called ALTITUDE in this sketch:
// If you want to measure altitude, and not pressure, you will instead need
// to provide a known baseline pressure. This is shown at the end of the sketch.
// You must first get a temperature measurement to perform a pressure reading.
// Start a temperature measurement:
// If request is successful, the number of ms to wait is returned.
// If request is unsuccessful, 0 is returned.
status = weatherShield_BMP180.startTemperature();
if (status != 0)
{
// Wait for the measurement to complete:
delay(status);
// Retrieve the completed temperature measurement:
// Note that the measurement is stored in the variable T.
// Function returns 1 if successful, 0 if failure.
status = weatherShield_BMP180.getTemperature(T);
if (status != 0)
{
// Start a pressure measurement:
// The parameter is the oversampling setting, from 0 to 3 (highest res, longest wait).
// If request is successful, the number of ms to wait is returned.
// If request is unsuccessful, 0 is returned.
status = weatherShield_BMP180.startPressure(3);
if (status != 0)
{
// Wait for the measurement to complete:
delay(status);
// Retrieve the completed pressure measurement:
// Note that the measurement is stored in the variable P.
// Note also that the function requires the previous temperature measurement (T).
// (If temperature is stable, you can do one temperature measurement for a number of pressure measurements.)
// Function returns 1 if successful, 0 if failure.
status = weatherShield_BMP180.getPressure(P,T);
if (status != 0)
{
return P;
}
}
}
}
return 0;
}
void readBattery()
{
unsigned int readings=0;
//enable battery monitor on WeatherShield (via mosfet controlled by A3)
pinMode(BATT_MONITOR_EN, OUTPUT);
digitalWrite(BATT_MONITOR_EN, LOW);
for (byte i=0; i<5; i++) //take several samples, and average
readings+=analogRead(BATT_MONITOR);
//disable battery monitor
pinMode(BATT_MONITOR_EN, INPUT); //highZ mode will allow p-mosfet to be pulled high and disconnect the voltage divider on the weather shield
batteryVolts = BATT_FORMULA(readings / 5.0);
dtostrf(batteryVolts,3,2, BATstr); //update the BATStr which gets sent every BATT_CYCLES or along with the MOTION message
if (batteryVolts <= BATT_LOW) BATstr = "LOW";
}
void Blink(byte PIN, byte DELAY_MS)
{
pinMode(PIN, OUTPUT);
digitalWrite(PIN,HIGH);
delay(DELAY_MS/2);
digitalWrite(PIN,LOW);
delay(DELAY_MS/2);
}

View File

@ -1,30 +1,49 @@
/* // **********************************************************************************
* Copyright (c) 2013 by Felix Rusu <felix@lowpowerlab.com>
*
* 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.
*/
// This sketch is an example of how wireless programming can be achieved with a Moteino // This sketch is an example of how wireless programming can be achieved with a Moteino
// that was loaded with a custom 1k Optiboot that is capable of loading a new sketch from // that was loaded with a custom 1k bootloader (DualOptiboot) that is capable of loading
// an external SPI flash chip // a new sketch from an external SPI flash chip
// This is the GATEWAY node, it does not need a custom Optiboot nor any external FLASH memory chip // This is the GATEWAY node, it does not need a custom Optiboot nor any external FLASH memory chip
// (ONLY the target node will need those) // (ONLY the target node will need those)
// The sketch includes logic to receive the new sketch from the serial port (from a host computer) and // The sketch includes logic to receive the new sketch from the serial port (from a host computer) and
// transmit it wirelessly to the target node // transmit it wirelessly to the target node
// The handshake protocol that receives the sketch from the serial port // The handshake protocol that receives the sketch from the serial port
// is handled by the SPIFLash/WirelessHEX69 library, which also relies on the RFM12B library // is handled by the SPIFLash/WirelessHEX69 library, which also relies on the RFM69 library
// These libraries and custom 1k Optiboot bootloader for the target node are at: http://github.com/lowpowerlab // These libraries and custom 1k Optiboot bootloader for the target node are at: http://github.com/lowpowerlab
// **********************************************************************************
#include <RFM69.h> // Copyright Felix Rusu, LowPowerLab.com
// Library and code by Felix Rusu - felix@lowpowerlab.com
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
// **********************************************************************************
#include <RFM69.h> //get it here: https://www.github.com/lowpowerlab/rfm69
#include <SPI.h> #include <SPI.h>
#include <SPIFlash.h> #include <SPIFlash.h> //get it here: https://www.github.com/lowpowerlab/spiflash
#include <WirelessHEX69.h> #include <WirelessHEX69.h> //get it here: https://github.com/LowPowerLab/WirelessProgramming/tree/master/WirelessHEX69
#define NETWORKID 250 //what network this node is on
#define NODEID 254 //this node's ID, should be unique among nodes on this NETWORKID #define NODEID 254 //this node's ID, should be unique among nodes on this NETWORKID
#define NETWORKID 250 //what network this node is on
//Match frequency to the hardware version of the radio on your Moteino (uncomment one): //Match frequency to the hardware version of the radio on your Moteino (uncomment one):
//#define FREQUENCY RF69_433MHZ //#define FREQUENCY RF69_433MHZ
//#define FREQUENCY RF69_868MHZ //#define FREQUENCY RF69_868MHZ

View File

@ -1,35 +1,54 @@
/* // **********************************************************************************
* Copyright (c) 2013 by Felix Rusu <felix@lowpowerlab.com>
*
* 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.
*/
// This sketch is an example of how wireless programming can be achieved with a Moteino // This sketch is an example of how wireless programming can be achieved with a Moteino
// that was loaded with a custom 1k Optiboot that is capable of loading a new sketch from // that was loaded with a custom 1k bootloader (DualOptiboot) that is capable of loading
// an external SPI flash chip // a new sketch from an external SPI flash chip
// The sketch includes logic to receive the new sketch 'over-the-air' and store it in // The sketch includes logic to receive the new sketch 'over-the-air' and store it in
// the FLASH chip, then restart the Moteino so the bootloader can continue the job of // the FLASH chip, then restart the Moteino so the bootloader can continue the job of
// actually reflashing the internal flash memory from the external FLASH memory chip flash image // actually reflashing the internal flash memory from the external FLASH memory chip flash image
// The handshake protocol that receives the sketch wirelessly by means of the RFM69 radio // The handshake protocol that receives the sketch wirelessly by means of the RFM69 radio
// is handled by the SPIFLash/WirelessHEX69 library, which also relies on the RFM69 library // is handled by the SPIFLash/WirelessHEX69 library, which also relies on the RFM69 library
// These libraries and custom 1k Optiboot bootloader are at: http://github.com/lowpowerlab // These libraries and custom 1k Optiboot bootloader are at: http://github.com/lowpowerlab
// **********************************************************************************
#include <RFM69.h> // Copyright Felix Rusu, LowPowerLab.com
// Library and code by Felix Rusu - felix@lowpowerlab.com
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
// **********************************************************************************
#include <RFM69.h> //get it here: https://www.github.com/lowpowerlab/rfm69
#include <SPI.h> #include <SPI.h>
#include <SPIFlash.h> #include <SPIFlash.h> //get it here: https://www.github.com/lowpowerlab/spiflash
#include <avr/wdt.h> #include <avr/wdt.h>
#include <WirelessHEX69.h> #include <WirelessHEX69.h> //get it here: https://github.com/LowPowerLab/WirelessProgramming/tree/master/WirelessHEX69
#define MYID 55 // node ID used for this unit #define NODEID 123 // node ID used for this unit
#define NETWORKID 250 #define NETWORKID 250
//Match frequency to the hardware version of the radio on your Moteino (uncomment one): //Match frequency to the hardware version of the radio on your Moteino (uncomment one):
//#define FREQUENCY RF69_433MHZ //#define FREQUENCY RF69_433MHZ
//#define FREQUENCY RF69_868MHZ //#define FREQUENCY RF69_868MHZ
#define FREQUENCY RF69_915MHZ #define FREQUENCY RF69_915MHZ
#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W! //#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W!
#define SERIAL_BAUD 115200 #define SERIAL_BAUD 115200
#define ACK_TIME 30 // # of ms to wait for an ack #define ACK_TIME 30 // # of ms to wait for an ack
#define ENCRYPTKEY "sampleEncryptKey" //(16 bytes of your choice - keep the same on all encrypted nodes) #define ENCRYPTKEY "sampleEncryptKey" //(16 bytes of your choice - keep the same on all encrypted nodes)
@ -59,7 +78,7 @@ SPIFlash flash(FLASH_SS, 0xEF30); //EF30 for windbond 4mbit flash
void setup(){ void setup(){
pinMode(LED, OUTPUT); pinMode(LED, OUTPUT);
Serial.begin(SERIAL_BAUD); Serial.begin(SERIAL_BAUD);
radio.initialize(FREQUENCY,MYID,NETWORKID); radio.initialize(FREQUENCY,NODEID,NETWORKID);
radio.encrypt(ENCRYPTKEY); //OPTIONAL radio.encrypt(ENCRYPTKEY); //OPTIONAL
#ifdef IS_RFM69HW #ifdef IS_RFM69HW
radio.setHighPower(); //only for RFM69HW! radio.setHighPower(); //only for RFM69HW!

View File

@ -1,16 +1,15 @@
RFM69 Library RFM69 Library
---------------- ----------------
By Felix Rusu (felix@lowpowerlab.com) By Felix Rusu, [LowPowerLab.com](http://LowPowerLab.com)
<br/> <br/>
RFM69 library for RFM69W, RFM69HW, RFM69CW, RFM69HCW (semtech SX1231, SX1231H) RFM69 library for RFM69W, RFM69HW, RFM69CW, RFM69HCW (semtech SX1231, SX1231H)
<br/>
The latest examples, new features and bug fixes are found in the [original repository](https://github.com/LowPowerLab/RFM69) of this library.
##License ##License
GPL 3.0, please see the License.txt file GPL 3.0, please see the [License.txt](https://github.com/LowPowerLab/RFM69/blob/master/License.txt) file for details. Be sure to include the same license with any fork or redistribution of this library.
##Features ##Features
Among others, this is a set of features implemented in this library:
- easy to use API with a few simple functions for basic usage - easy to use API with a few simple functions for basic usage
- 255 possible nodes on 256 possible networks - 255 possible nodes on 256 possible networks
- 61 bytes max message length (limited to 61 to support AES hardware encryption) - 61 bytes max message length (limited to 61 to support AES hardware encryption)
@ -21,40 +20,40 @@ Among others, this is a set of features implemented in this library:
- hardware preamble, synch recognition and CRC check - hardware preamble, synch recognition and CRC check
- digital RSSI can be read at any time with readRSSI() - digital RSSI can be read at any time with readRSSI()
- interrupt driven - interrupt driven
- tested on [Moteino R3, R4, R4-USB (ATMega328p)](http://lowpowerlab.com/shop/Moteino-R4) - tested on [Moteino R3, R4, R4-USB (ATMega328p), MEGA (ATMega1284p)](https://lowpowerlab.com/shop/Moteino-R4)
- works with RFM69W, RFM69HW, RFM69CW, RFM69HCW, Semtech SX1231/SX1231H transceivers - works with RFM69W, RFM69HW, RFM69CW, RFM69HCW, Semtech SX1231/SX1231H transceivers
- promiscuous mode allows any node to listen to any packet on same network - promiscuous mode allows any node to listen to any packet on same network
I consider this an initial beta release, it could contain bugs, but the provided Gateway and Node examples should work out of the box. Please let me know if you find issues. ###Library Installation (Arduino IDE)
###Installation
Copy the content of this library in the "Arduino/libraries/RFM69" folder. Copy the content of this library in the "Arduino/libraries/RFM69" folder.
<br /> <br />
To find your Arduino folder go to File>Preferences in the Arduino IDE. To find your Arduino folder go to File>Preferences in the Arduino IDE.
<br/> <br/>
See [this tutorial](http://learn.adafruit.com/arduino-tips-tricks-and-techniques/arduino-libraries) on Arduino libraries. See [this tutorial](http://learn.adafruit.com/arduino-tips-tricks-and-techniques/arduino-libraries) on Arduino libraries.
###MISC / possible issues ###Hardware & programming
- The library and examples are continuously improved as bugs and stability issues are discovered. Be sure to check back often for changes. The easiest way to get started is with the well documented and supported [Moteino](http://moteino.com) microcontroller platform which is [easily programmable](https://lowpowerlab.com/programming) from the Arduino IDE. This includes the [Moteino, MoteinoUSB & MoteinoMEGA](https://lowpowerlab.com/shop/Moteino). RFM69 transceivers were extensively tested on Moteinos for the purpose of building internet of things (IoT) devices that can be controlled wirelessly. This platform has matured over time and there is now a [dedicated page](https://lowpowerlab.com/gateway) where you can review how these devices can interact with each other via a RaspberryPi gateway interface. Here's a video overview:<br/>
- Moteino boards are loaded with fuses that will delay startup. This means that other boards like Duemilanove/UNO might need a delay() in the setup() function before doing anything - to allow the transceiver to power up. https://www.youtube.com/watch?v=YUUZ6k0pBHg
<br/>
https://www.youtube.com/watch?v=I9MNZQgqKHA
<br/>
https://www.youtube.com/watch?v=F15dEqZ4pMM
###Sample usage ###Basic sample usage
- [Node](https://github.com/LowPowerLab/RFM69/blob/master/Examples/Node/Node.ino) - The [Gateway](https://github.com/LowPowerLab/RFM69/blob/master/Examples/Gateway/Gateway.ino) example listens for incoming data from remote nodes and responds to any ACK requests
- [Gateway](https://github.com/LowPowerLab/RFM69/blob/master/Examples/Gateway/Gateway.ino) - The [Node](https://github.com/LowPowerLab/RFM69/blob/master/Examples/Node/Node.ino) example is a loop that sends increasingly longer packets to the gateway and waits for an ACK each time
- More examples are added from time to time, check all the [examples](https://github.com/LowPowerLab/RFM69/tree/master/Examples), visit the [LowPowerLab blog](http://lowpowerlab.com) for latest news and projects, and check out the [LowPowerLab forums](https://lowpowerlab.com/forum) for projects and discussion
##Blog writeup ##Blog writeup
http://lowpowerlab.com/blog/2013/06/20/rfm69-library/ See the [library release blog post](http://lowpowerlab.com/blog/2013/06/20/rfm69-library/)
##Why ##Why RFM69
- I have spent a lot of time developing this library for RFM69W/HW transceivers. I made it open source because I believe a lot of people can benefit from this new powerful transceiver. I hope people will also contribute and build on my work - I have spent a lot of time developing this library for RFM69W/HW transceivers. I made it open source because I believe a lot of people can benefit from this new powerful transceiver. I hope others will also contribute and build on my work
- I have long researched alternative transceivers for RFM12B which is still an excellent transceiver but it is much lower output power and has limited built in features which need to be implemented in firmware (PREAMBLE, SYNC, CRC, packet engine, encryption etc). - I have long researched alternative transceivers for RFM12B which is still an excellent transceiver but it is much lower output power and has limited built in features which need to be implemented in firmware (PREAMBLE, SYNC, CRC, packet engine, encryption etc).
- I wanted a transceiver that could still be very small, easy to use, but have the longer range that I wanted - I wanted a transceiver that could still be very small, easy to use, and have the longer range that I needed
- RFM69 comes in 2 variants that have the same layout/connections: RFM69W (13dBm, 45mA TX) and RFM69HW (20dBm, 130mA TX) - RFM69 comes in 2 variants that have the same pinout layout: RFM69W (13dBm, 45mA TX) and RFM69HW (20dBm, 130mA TX). Other variants include the RFM69CW (up to 13dBm power) which is pin compatible with RFM12B, and RFM69HCW (20dBm output power).
##RFM69W range ##RFM69W range and antennas
- I have tested open-air range on these transceivers (the W only) in various combinations. - I have tested open-air range on these transceivers in various combinations.
- I am happy to say that a range of upwards of 350m can be achieved. I went to local parks and in very large parking spaces and I ran out of space, so more than 350m is possible. Some users reported upwards of 500m by lowering the bitrate, and a forum user reported 1.5miles at 1.2Kbps: see http://lowpowerlab.com/forum/index.php/topic,112.msg288.html and http://lowpowerlab.com/moteino/#antennas - I am happy to say that a range of upwards of 350m can be achieved with the default settings provided in the library.Some users reported upwards of 500m by lowering the bitrate, and a forum user reported 1.5miles at 1.2Kbps: see [this forum post](http://lowpowerlab.com/forum/index.php/topic,112.msg288.html) and [this blog page](http://lowpowerlab.com/moteino/#antennas)
- The caveat with these higher RF power units is that they need more DC power when they transmit. For battery powered motes, you will need to keep them powered down and only transmit periodically. Use the sleep() function to put the radios in low power mode and use the [LowPower](https://github.com/rocketscream/Low-Power) or [Narcoleptic](https://code.google.com/p/narcoleptic/) libraries to power down your arduino - The caveat with these higher RF power units is that they need more DC power when they transmit. For battery powered motes, you will need to keep them powered down and only transmit periodically. Use the sleep() function to put the radios in low power mode and use the [LowPower](https://github.com/lowpowerlab/lowpower) or [Narcoleptic](https://code.google.com/p/narcoleptic/) libraries to power down your Moteino/Arduino
##License
GPL 3.0. See License.txt file.

420
RFM69.cpp
View File

@ -9,22 +9,21 @@
// This program is free software; you can redistribute it // This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General // and/or modify it under the terms of the GNU General
// Public License as published by the Free Software // Public License as published by the Free Software
// Foundation; either version 2 of the License, or // Foundation; either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will // This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the // be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A // implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public // PARTICULAR PURPOSE. See the GNU General Public
// License for more details. // License for more details.
// //
// You should have received a copy of the GNU General // You should have received a copy of the GNU General
// Public License along with this program; if not, write // Public License along with this program.
// to the Free Software Foundation, Inc., // If not, see <http://www.gnu.org/licenses/>.
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// //
// Licence can be viewed at // Licence can be viewed at
// http://www.fsf.org/licenses/gpl.txt // http://www.gnu.org/licenses/gpl-3.0.txt
// //
// Please maintain this license information along with authorship // Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code // and copyright notices in any redistribution of this code
@ -33,76 +32,78 @@
#include <RFM69registers.h> #include <RFM69registers.h>
#include <SPI.h> #include <SPI.h>
volatile byte RFM69::DATA[RF69_MAX_DATA_LEN]; volatile uint8_t RFM69::DATA[RF69_MAX_DATA_LEN];
volatile byte RFM69::_mode; // current transceiver state volatile uint8_t RFM69::_mode; // current transceiver state
volatile byte RFM69::DATALEN; volatile uint8_t RFM69::DATALEN;
volatile byte RFM69::SENDERID; volatile uint8_t RFM69::SENDERID;
volatile byte RFM69::TARGETID; //should match _address volatile uint8_t RFM69::TARGETID; // should match _address
volatile byte RFM69::PAYLOADLEN; volatile uint8_t RFM69::PAYLOADLEN;
volatile byte RFM69::ACK_REQUESTED; volatile uint8_t RFM69::ACK_REQUESTED;
volatile byte RFM69::ACK_RECEIVED; /// Should be polled immediately after sending a packet with ACK request volatile uint8_t RFM69::ACK_RECEIVED; // should be polled immediately after sending a packet with ACK request
volatile int RFM69::RSSI; //most accurate RSSI during reception (closest to the reception) volatile int16_t RFM69::RSSI; // most accurate RSSI during reception (closest to the reception)
RFM69* RFM69::selfPointer; RFM69* RFM69::selfPointer;
bool RFM69::initialize(byte freqBand, byte nodeID, byte networkID) bool RFM69::initialize(uint8_t freqBand, uint8_t nodeID, uint8_t networkID)
{ {
const byte CONFIG[][2] = const uint8_t CONFIG[][2] =
{ {
/* 0x01 */ { REG_OPMODE, RF_OPMODE_SEQUENCER_ON | RF_OPMODE_LISTEN_OFF | RF_OPMODE_STANDBY }, /* 0x01 */ { REG_OPMODE, RF_OPMODE_SEQUENCER_ON | RF_OPMODE_LISTEN_OFF | RF_OPMODE_STANDBY },
/* 0x02 */ { REG_DATAMODUL, RF_DATAMODUL_DATAMODE_PACKET | RF_DATAMODUL_MODULATIONTYPE_FSK | RF_DATAMODUL_MODULATIONSHAPING_00 }, //no shaping /* 0x02 */ { REG_DATAMODUL, RF_DATAMODUL_DATAMODE_PACKET | RF_DATAMODUL_MODULATIONTYPE_FSK | RF_DATAMODUL_MODULATIONSHAPING_00 }, // no shaping
/* 0x03 */ { REG_BITRATEMSB, RF_BITRATEMSB_55555}, //default:4.8 KBPS /* 0x03 */ { REG_BITRATEMSB, RF_BITRATEMSB_55555}, // default: 4.8 KBPS
/* 0x04 */ { REG_BITRATELSB, RF_BITRATELSB_55555}, /* 0x04 */ { REG_BITRATELSB, RF_BITRATELSB_55555},
/* 0x05 */ { REG_FDEVMSB, RF_FDEVMSB_50000}, //default:5khz, (FDEV + BitRate/2 <= 500Khz) /* 0x05 */ { REG_FDEVMSB, RF_FDEVMSB_50000}, // default: 5KHz, (FDEV + BitRate / 2 <= 500KHz)
/* 0x06 */ { REG_FDEVLSB, RF_FDEVLSB_50000}, /* 0x06 */ { REG_FDEVLSB, RF_FDEVLSB_50000},
/* 0x07 */ { REG_FRFMSB, (freqBand==RF69_315MHZ ? RF_FRFMSB_315 : (freqBand==RF69_433MHZ ? RF_FRFMSB_433 : (freqBand==RF69_868MHZ ? RF_FRFMSB_868 : RF_FRFMSB_915))) }, /* 0x07 */ { REG_FRFMSB, (uint8_t) (freqBand==RF69_315MHZ ? RF_FRFMSB_315 : (freqBand==RF69_433MHZ ? RF_FRFMSB_433 : (freqBand==RF69_868MHZ ? RF_FRFMSB_868 : RF_FRFMSB_915))) },
/* 0x08 */ { REG_FRFMID, (freqBand==RF69_315MHZ ? RF_FRFMID_315 : (freqBand==RF69_433MHZ ? RF_FRFMID_433 : (freqBand==RF69_868MHZ ? RF_FRFMID_868 : RF_FRFMID_915))) }, /* 0x08 */ { REG_FRFMID, (uint8_t) (freqBand==RF69_315MHZ ? RF_FRFMID_315 : (freqBand==RF69_433MHZ ? RF_FRFMID_433 : (freqBand==RF69_868MHZ ? RF_FRFMID_868 : RF_FRFMID_915))) },
/* 0x09 */ { REG_FRFLSB, (freqBand==RF69_315MHZ ? RF_FRFLSB_315 : (freqBand==RF69_433MHZ ? RF_FRFLSB_433 : (freqBand==RF69_868MHZ ? RF_FRFLSB_868 : RF_FRFLSB_915))) }, /* 0x09 */ { REG_FRFLSB, (uint8_t) (freqBand==RF69_315MHZ ? RF_FRFLSB_315 : (freqBand==RF69_433MHZ ? RF_FRFLSB_433 : (freqBand==RF69_868MHZ ? RF_FRFLSB_868 : RF_FRFLSB_915))) },
// looks like PA1 and PA2 are not implemented on RFM69W, hence the max output power is 13dBm // looks like PA1 and PA2 are not implemented on RFM69W, hence the max output power is 13dBm
// +17dBm and +20dBm are possible on RFM69HW // +17dBm and +20dBm are possible on RFM69HW
// +13dBm formula: Pout=-18+OutputPower (with PA0 or PA1**) // +13dBm formula: Pout = -18 + OutputPower (with PA0 or PA1**)
// +17dBm formula: Pout=-14+OutputPower (with PA1 and PA2)** // +17dBm formula: Pout = -14 + OutputPower (with PA1 and PA2)**
// +20dBm formula: Pout=-11+OutputPower (with PA1 and PA2)** and high power PA settings (section 3.3.7 in datasheet) // +20dBm formula: Pout = -11 + OutputPower (with PA1 and PA2)** and high power PA settings (section 3.3.7 in datasheet)
///* 0x11 */ { REG_PALEVEL, RF_PALEVEL_PA0_ON | RF_PALEVEL_PA1_OFF | RF_PALEVEL_PA2_OFF | RF_PALEVEL_OUTPUTPOWER_11111}, ///* 0x11 */ { REG_PALEVEL, RF_PALEVEL_PA0_ON | RF_PALEVEL_PA1_OFF | RF_PALEVEL_PA2_OFF | RF_PALEVEL_OUTPUTPOWER_11111},
///* 0x13 */ { REG_OCP, RF_OCP_ON | RF_OCP_TRIM_95 }, //over current protection (default is 95mA) ///* 0x13 */ { REG_OCP, RF_OCP_ON | RF_OCP_TRIM_95 }, // over current protection (default is 95mA)
// RXBW defaults are { REG_RXBW, RF_RXBW_DCCFREQ_010 | RF_RXBW_MANT_24 | RF_RXBW_EXP_5} (RxBw: 10.4khz) // RXBW defaults are { REG_RXBW, RF_RXBW_DCCFREQ_010 | RF_RXBW_MANT_24 | RF_RXBW_EXP_5} (RxBw: 10.4KHz)
/* 0x19 */ { REG_RXBW, RF_RXBW_DCCFREQ_010 | RF_RXBW_MANT_16 | RF_RXBW_EXP_2 }, //(BitRate < 2 * RxBw) /* 0x19 */ { REG_RXBW, RF_RXBW_DCCFREQ_010 | RF_RXBW_MANT_16 | RF_RXBW_EXP_2 }, // (BitRate < 2 * RxBw)
//for BR-19200: //* 0x19 */ { REG_RXBW, RF_RXBW_DCCFREQ_010 | RF_RXBW_MANT_24 | RF_RXBW_EXP_3 }, //for BR-19200: /* 0x19 */ { REG_RXBW, RF_RXBW_DCCFREQ_010 | RF_RXBW_MANT_24 | RF_RXBW_EXP_3 },
/* 0x25 */ { REG_DIOMAPPING1, RF_DIOMAPPING1_DIO0_01 }, //DIO0 is the only IRQ we're using /* 0x25 */ { REG_DIOMAPPING1, RF_DIOMAPPING1_DIO0_01 }, // DIO0 is the only IRQ we're using
/* 0x29 */ { REG_RSSITHRESH, 220 }, //must be set to dBm = (-Sensitivity / 2) - default is 0xE4=228 so -114dBm /* 0x26 */ { REG_DIOMAPPING2, RF_DIOMAPPING2_CLKOUT_OFF }, // DIO5 ClkOut disable for power saving
///* 0x2d */ { REG_PREAMBLELSB, RF_PREAMBLESIZE_LSB_VALUE } // default 3 preamble bytes 0xAAAAAA /* 0x28 */ { REG_IRQFLAGS2, RF_IRQFLAGS2_FIFOOVERRUN }, // writing to this bit ensures that the FIFO & status flags are reset
/* 0x2e */ { REG_SYNCCONFIG, RF_SYNC_ON | RF_SYNC_FIFOFILL_AUTO | RF_SYNC_SIZE_2 | RF_SYNC_TOL_0 }, /* 0x29 */ { REG_RSSITHRESH, 220 }, // must be set to dBm = (-Sensitivity / 2), default is 0xE4 = 228 so -114dBm
/* 0x2f */ { REG_SYNCVALUE1, 0x2D }, //attempt to make this compatible with sync1 byte of RFM12B lib ///* 0x2D */ { REG_PREAMBLELSB, RF_PREAMBLESIZE_LSB_VALUE } // default 3 preamble bytes 0xAAAAAA
/* 0x30 */ { REG_SYNCVALUE2, networkID }, //NETWORK ID /* 0x2E */ { REG_SYNCCONFIG, RF_SYNC_ON | RF_SYNC_FIFOFILL_AUTO | RF_SYNC_SIZE_2 | RF_SYNC_TOL_0 },
/* 0x2F */ { REG_SYNCVALUE1, 0x2D }, // attempt to make this compatible with sync1 byte of RFM12B lib
/* 0x30 */ { REG_SYNCVALUE2, networkID }, // NETWORK ID
/* 0x37 */ { REG_PACKETCONFIG1, RF_PACKET1_FORMAT_VARIABLE | RF_PACKET1_DCFREE_OFF | RF_PACKET1_CRC_ON | RF_PACKET1_CRCAUTOCLEAR_ON | RF_PACKET1_ADRSFILTERING_OFF }, /* 0x37 */ { REG_PACKETCONFIG1, RF_PACKET1_FORMAT_VARIABLE | RF_PACKET1_DCFREE_OFF | RF_PACKET1_CRC_ON | RF_PACKET1_CRCAUTOCLEAR_ON | RF_PACKET1_ADRSFILTERING_OFF },
/* 0x38 */ { REG_PAYLOADLENGTH, 66 }, //in variable length mode: the max frame size, not used in TX /* 0x38 */ { REG_PAYLOADLENGTH, 66 }, // in variable length mode: the max frame size, not used in TX
//* 0x39 */ { REG_NODEADRS, nodeID }, //turned off because we're not using address filtering ///* 0x39 */ { REG_NODEADRS, nodeID }, // turned off because we're not using address filtering
/* 0x3C */ { REG_FIFOTHRESH, RF_FIFOTHRESH_TXSTART_FIFONOTEMPTY | RF_FIFOTHRESH_VALUE }, //TX on FIFO not empty /* 0x3C */ { REG_FIFOTHRESH, RF_FIFOTHRESH_TXSTART_FIFONOTEMPTY | RF_FIFOTHRESH_VALUE }, // TX on FIFO not empty
/* 0x3d */ { REG_PACKETCONFIG2, RF_PACKET2_RXRESTARTDELAY_2BITS | RF_PACKET2_AUTORXRESTART_ON | RF_PACKET2_AES_OFF }, //RXRESTARTDELAY must match transmitter PA ramp-down time (bitrate dependent) /* 0x3D */ { REG_PACKETCONFIG2, RF_PACKET2_RXRESTARTDELAY_2BITS | RF_PACKET2_AUTORXRESTART_ON | RF_PACKET2_AES_OFF }, // RXRESTARTDELAY must match transmitter PA ramp-down time (bitrate dependent)
//for BR-19200: //* 0x3d */ { REG_PACKETCONFIG2, RF_PACKET2_RXRESTARTDELAY_NONE | RF_PACKET2_AUTORXRESTART_ON | RF_PACKET2_AES_OFF }, //RXRESTARTDELAY must match transmitter PA ramp-down time (bitrate dependent) //for BR-19200: /* 0x3D */ { REG_PACKETCONFIG2, RF_PACKET2_RXRESTARTDELAY_NONE | RF_PACKET2_AUTORXRESTART_ON | RF_PACKET2_AES_OFF }, // RXRESTARTDELAY must match transmitter PA ramp-down time (bitrate dependent)
//* 0x6F */ { REG_TESTDAGC, RF_DAGC_CONTINUOUS }, // run DAGC continuously in RX mode /* 0x6F */ { REG_TESTDAGC, RF_DAGC_IMPROVED_LOWBETA0 }, // run DAGC continuously in RX mode for Fading Margin Improvement, recommended default for AfcLowBetaOn=0
/* 0x6F */ { REG_TESTDAGC, RF_DAGC_IMPROVED_LOWBETA0 }, // run DAGC continuously in RX mode, recommended default for AfcLowBetaOn=0
{255, 0} {255, 0}
}; };
digitalWrite(_slaveSelectPin, HIGH);
pinMode(_slaveSelectPin, OUTPUT); pinMode(_slaveSelectPin, OUTPUT);
SPI.begin(); SPI.begin();
do writeReg(REG_SYNCVALUE1, 0xaa); while (readReg(REG_SYNCVALUE1) != 0xaa);
do writeReg(REG_SYNCVALUE1, 0x55); while (readReg(REG_SYNCVALUE1) != 0x55);
for (byte i = 0; CONFIG[i][0] != 255; i++) do writeReg(REG_SYNCVALUE1, 0xAA); while (readReg(REG_SYNCVALUE1) != 0xAA);
do writeReg(REG_SYNCVALUE1, 0x55); while (readReg(REG_SYNCVALUE1) != 0x55);
for (uint8_t i = 0; CONFIG[i][0] != 255; i++)
writeReg(CONFIG[i][0], CONFIG[i][1]); writeReg(CONFIG[i][0], CONFIG[i][1]);
// Encryption is persistent between resets and can trip you up during debugging. // Encryption is persistent between resets and can trip you up during debugging.
// Disable it during initialization so we always start from a known state. // Disable it during initialization so we always start from a known state.
encrypt(0); encrypt(0);
setHighPower(_isRFM69HW); //called regardless if it's a RFM69W or RFM69HW setHighPower(_isRFM69HW); // called regardless if it's a RFM69W or RFM69HW
setMode(RF69_MODE_STANDBY); setMode(RF69_MODE_STANDBY);
while ((readReg(REG_IRQFLAGS1) & RF_IRQFLAGS1_MODEREADY) == 0x00); // Wait for ModeReady while ((readReg(REG_IRQFLAGS1) & RF_IRQFLAGS1_MODEREADY) == 0x00); // wait for ModeReady
attachInterrupt(_interruptNum, RFM69::isr0, RISING); attachInterrupt(_interruptNum, RFM69::isr0, RISING);
selfPointer = this; selfPointer = this;
@ -110,66 +111,98 @@ bool RFM69::initialize(byte freqBand, byte nodeID, byte networkID)
return true; return true;
} }
void RFM69::setFrequency(uint32_t FRF) // return the frequency (in Hz)
uint32_t RFM69::getFrequency()
{ {
writeReg(REG_FRFMSB, FRF >> 16); return RF69_FSTEP * (((uint32_t) readReg(REG_FRFMSB) << 16) + ((uint16_t) readReg(REG_FRFMID) << 8) + readReg(REG_FRFLSB));
writeReg(REG_FRFMID, FRF >> 8);
writeReg(REG_FRFLSB, FRF);
} }
void RFM69::setMode(byte newMode) // set the frequency (in Hz)
void RFM69::setFrequency(uint32_t freqHz)
{ {
if (newMode == _mode) return; //TODO: can remove this? uint8_t oldMode = _mode;
if (oldMode == RF69_MODE_TX) {
setMode(RF69_MODE_RX);
}
freqHz /= RF69_FSTEP; // divide down by FSTEP to get FRF
writeReg(REG_FRFMSB, freqHz >> 16);
writeReg(REG_FRFMID, freqHz >> 8);
writeReg(REG_FRFLSB, freqHz);
if (oldMode == RF69_MODE_RX) {
setMode(RF69_MODE_SYNTH);
}
setMode(oldMode);
}
switch (newMode) { void RFM69::setMode(uint8_t newMode)
case RF69_MODE_TX: {
writeReg(REG_OPMODE, (readReg(REG_OPMODE) & 0xE3) | RF_OPMODE_TRANSMITTER); if (newMode == _mode)
return;
switch (newMode) {
case RF69_MODE_TX:
writeReg(REG_OPMODE, (readReg(REG_OPMODE) & 0xE3) | RF_OPMODE_TRANSMITTER);
if (_isRFM69HW) setHighPowerRegs(true); if (_isRFM69HW) setHighPowerRegs(true);
break; break;
case RF69_MODE_RX: case RF69_MODE_RX:
writeReg(REG_OPMODE, (readReg(REG_OPMODE) & 0xE3) | RF_OPMODE_RECEIVER); writeReg(REG_OPMODE, (readReg(REG_OPMODE) & 0xE3) | RF_OPMODE_RECEIVER);
if (_isRFM69HW) setHighPowerRegs(false); if (_isRFM69HW) setHighPowerRegs(false);
break; break;
case RF69_MODE_SYNTH: case RF69_MODE_SYNTH:
writeReg(REG_OPMODE, (readReg(REG_OPMODE) & 0xE3) | RF_OPMODE_SYNTHESIZER); writeReg(REG_OPMODE, (readReg(REG_OPMODE) & 0xE3) | RF_OPMODE_SYNTHESIZER);
break; break;
case RF69_MODE_STANDBY: case RF69_MODE_STANDBY:
writeReg(REG_OPMODE, (readReg(REG_OPMODE) & 0xE3) | RF_OPMODE_STANDBY); writeReg(REG_OPMODE, (readReg(REG_OPMODE) & 0xE3) | RF_OPMODE_STANDBY);
break; break;
case RF69_MODE_SLEEP: case RF69_MODE_SLEEP:
writeReg(REG_OPMODE, (readReg(REG_OPMODE) & 0xE3) | RF_OPMODE_SLEEP); writeReg(REG_OPMODE, (readReg(REG_OPMODE) & 0xE3) | RF_OPMODE_SLEEP);
break; break;
default: return; default:
} return;
}
// we are using packet mode, so this check is not really needed // we are using packet mode, so this check is not really needed
// but waiting for mode ready is necessary when going from sleep because the FIFO may not be immediately available from previous mode // but waiting for mode ready is necessary when going from sleep because the FIFO may not be immediately available from previous mode
while (_mode == RF69_MODE_SLEEP && (readReg(REG_IRQFLAGS1) & RF_IRQFLAGS1_MODEREADY) == 0x00); // Wait for ModeReady while (_mode == RF69_MODE_SLEEP && (readReg(REG_IRQFLAGS1) & RF_IRQFLAGS1_MODEREADY) == 0x00); // wait for ModeReady
_mode = newMode; _mode = newMode;
} }
//put transceiver in sleep mode to save battery - to wake or resume receiving just call receiveDone()
void RFM69::sleep() { void RFM69::sleep() {
setMode(RF69_MODE_SLEEP); setMode(RF69_MODE_SLEEP);
} }
void RFM69::setAddress(byte addr) //set this node's address
void RFM69::setAddress(uint8_t addr)
{ {
_address = addr; _address = addr;
writeReg(REG_NODEADRS, _address); writeReg(REG_NODEADRS, _address);
} }
// set output power: 0=min, 31=max //set this node's network id
// this results in a "weaker" transmitted signal, and directly results in a lower RSSI at the receiver void RFM69::setNetwork(uint8_t networkID)
void RFM69::setPowerLevel(byte powerLevel)
{ {
_powerLevel = powerLevel; writeReg(REG_SYNCVALUE2, networkID);
writeReg(REG_PALEVEL, (readReg(REG_PALEVEL) & 0xE0) | (_powerLevel > 31 ? 31 : _powerLevel)); }
// set *transmit/TX* output power: 0=min, 31=max
// this results in a "weaker" transmitted signal, and directly results in a lower RSSI at the receiver
// the power configurations are explained in the SX1231H datasheet (Table 10 on p21; RegPaLevel p66): http://www.semtech.com/images/datasheet/sx1231h.pdf
// valid powerLevel parameter values are 0-31 and result in a directly proportional effect on the output/transmission power
// this function implements 2 modes as follows:
// - for RFM69W the range is from 0-31 [-18dBm to 13dBm] (PA0 only on RFIO pin)
// - for RFM69HW the range is from 0-31 [5dBm to 20dBm] (PA1 & PA2 on PA_BOOST pin & high Power PA settings - see section 3.3.7 in datasheet, p22)
void RFM69::setPowerLevel(uint8_t powerLevel)
{
_powerLevel = (powerLevel > 31 ? 31 : powerLevel);
if (_isRFM69HW) _powerLevel /= 2;
writeReg(REG_PALEVEL, (readReg(REG_PALEVEL) & 0xE0) | _powerLevel);
} }
bool RFM69::canSend() bool RFM69::canSend()
{ {
if (_mode == RF69_MODE_RX && PAYLOADLEN == 0 && readRSSI() < CSMA_LIMIT) //if signal stronger than -100dBm is detected assume channel activity if (_mode == RF69_MODE_RX && PAYLOADLEN == 0 && readRSSI() < CSMA_LIMIT) // if signal stronger than -100dBm is detected assume channel activity
{ {
setMode(RF69_MODE_STANDBY); setMode(RF69_MODE_STANDBY);
return true; return true;
@ -177,90 +210,98 @@ bool RFM69::canSend()
return false; return false;
} }
void RFM69::send(byte toAddress, const void* buffer, byte bufferSize, bool requestACK) void RFM69::send(uint8_t toAddress, const void* buffer, uint8_t bufferSize, bool requestACK)
{ {
writeReg(REG_PACKETCONFIG2, (readReg(REG_PACKETCONFIG2) & 0xFB) | RF_PACKET2_RXRESTART); // avoid RX deadlocks writeReg(REG_PACKETCONFIG2, (readReg(REG_PACKETCONFIG2) & 0xFB) | RF_PACKET2_RXRESTART); // avoid RX deadlocks
long now = millis(); uint32_t now = millis();
while (!canSend() && millis()-now < RF69_CSMA_LIMIT_MS) receiveDone(); while (!canSend() && millis() - now < RF69_CSMA_LIMIT_MS) receiveDone();
sendFrame(toAddress, buffer, bufferSize, requestACK, false); sendFrame(toAddress, buffer, bufferSize, requestACK, false);
} }
// to increase the chance of getting a packet across, call this function instead of send // to increase the chance of getting a packet across, call this function instead of send
// and it handles all the ACK requesting/retrying for you :) // and it handles all the ACK requesting/retrying for you :)
// The only twist is that you have to manually listen to ACK requests on the other side and send back the ACKs // The only twist is that you have to manually listen to ACK requests on the other side and send back the ACKs
// The reason for the semi-automaton is that the lib is ingterrupt driven and // The reason for the semi-automaton is that the lib is interrupt driven and
// requires user action to read the received data and decide what to do with it // requires user action to read the received data and decide what to do with it
// replies usually take only 5-8ms at 50kbps@915Mhz // replies usually take only 5..8ms at 50kbps@915MHz
bool RFM69::sendWithRetry(byte toAddress, const void* buffer, byte bufferSize, byte retries, byte retryWaitTime) { bool RFM69::sendWithRetry(uint8_t toAddress, const void* buffer, uint8_t bufferSize, uint8_t retries, uint8_t retryWaitTime) {
long sentTime; uint32_t sentTime;
for (byte i=0; i<=retries; i++) for (uint8_t i = 0; i <= retries; i++)
{ {
send(toAddress, buffer, bufferSize, true); send(toAddress, buffer, bufferSize, true);
sentTime = millis(); sentTime = millis();
while (millis()-sentTime<retryWaitTime) while (millis() - sentTime < retryWaitTime)
{ {
if (ACKReceived(toAddress)) if (ACKReceived(toAddress))
{ {
//Serial.print(" ~ms:");Serial.print(millis()-sentTime); //Serial.print(" ~ms:"); Serial.print(millis() - sentTime);
return true; return true;
} }
} }
//Serial.print(" RETRY#");Serial.println(i+1); //Serial.print(" RETRY#"); Serial.println(i + 1);
} }
return false; return false;
} }
/// Should be polled immediately after sending a packet with ACK request // should be polled immediately after sending a packet with ACK request
bool RFM69::ACKReceived(byte fromNodeID) { bool RFM69::ACKReceived(uint8_t fromNodeID) {
if (receiveDone()) if (receiveDone())
return (SENDERID == fromNodeID || fromNodeID == RF69_BROADCAST_ADDR) && ACK_RECEIVED; return (SENDERID == fromNodeID || fromNodeID == RF69_BROADCAST_ADDR) && ACK_RECEIVED;
return false; return false;
} }
//check whether an ACK was requested in the last received packet (non-broadcasted packet) // check whether an ACK was requested in the last received packet (non-broadcasted packet)
bool RFM69::ACKRequested() { bool RFM69::ACKRequested() {
return ACK_REQUESTED && (TARGETID != RF69_BROADCAST_ADDR); return ACK_REQUESTED && (TARGETID != RF69_BROADCAST_ADDR);
} }
/// Should be called immediately after reception in case sender wants ACK // should be called immediately after reception in case sender wants ACK
void RFM69::sendACK(const void* buffer, byte bufferSize) { void RFM69::sendACK(const void* buffer, uint8_t bufferSize) {
byte sender = SENDERID; uint8_t sender = SENDERID;
while (!canSend()) receiveDone(); int16_t _RSSI = RSSI; // save payload received RSSI value
writeReg(REG_PACKETCONFIG2, (readReg(REG_PACKETCONFIG2) & 0xFB) | RF_PACKET2_RXRESTART); // avoid RX deadlocks
uint32_t now = millis();
while (!canSend() && millis() - now < RF69_CSMA_LIMIT_MS) receiveDone();
sendFrame(sender, buffer, bufferSize, false, true); sendFrame(sender, buffer, bufferSize, false, true);
RSSI = _RSSI; // restore payload RSSI
} }
void RFM69::sendFrame(byte toAddress, const void* buffer, byte bufferSize, bool requestACK, bool sendACK) // internal function
void RFM69::sendFrame(uint8_t toAddress, const void* buffer, uint8_t bufferSize, bool requestACK, bool sendACK)
{ {
setMode(RF69_MODE_STANDBY); //turn off receiver to prevent reception while filling fifo setMode(RF69_MODE_STANDBY); // turn off receiver to prevent reception while filling fifo
while ((readReg(REG_IRQFLAGS1) & RF_IRQFLAGS1_MODEREADY) == 0x00); // Wait for ModeReady while ((readReg(REG_IRQFLAGS1) & RF_IRQFLAGS1_MODEREADY) == 0x00); // wait for ModeReady
writeReg(REG_DIOMAPPING1, RF_DIOMAPPING1_DIO0_00); // DIO0 is "Packet Sent" writeReg(REG_DIOMAPPING1, RF_DIOMAPPING1_DIO0_00); // DIO0 is "Packet Sent"
if (bufferSize > RF69_MAX_DATA_LEN) bufferSize = RF69_MAX_DATA_LEN; if (bufferSize > RF69_MAX_DATA_LEN) bufferSize = RF69_MAX_DATA_LEN;
//write to FIFO // control byte
select(); uint8_t CTLbyte = 0x00;
SPI.transfer(REG_FIFO | 0x80);
SPI.transfer(bufferSize + 3);
SPI.transfer(toAddress);
SPI.transfer(_address);
//control byte
if (sendACK) if (sendACK)
SPI.transfer(0x80); CTLbyte = 0x80;
else if (requestACK) else if (requestACK)
SPI.transfer(0x40); CTLbyte = 0x40;
else SPI.transfer(0x00);
for (byte i = 0; i < bufferSize; i++)
SPI.transfer(((byte*)buffer)[i]);
unselect();
/* no need to wait for transmit mode to be ready since its handled by the radio */ // write to FIFO
setMode(RF69_MODE_TX); select();
while (digitalRead(_interruptPin) == 0); //wait for DIO0 to turn HIGH signalling transmission finish SPI.transfer(REG_FIFO | 0x80);
//while (readReg(REG_IRQFLAGS2) & RF_IRQFLAGS2_PACKETSENT == 0x00); // Wait for ModeReady SPI.transfer(bufferSize + 3);
SPI.transfer(toAddress);
SPI.transfer(_address);
SPI.transfer(CTLbyte);
for (uint8_t i = 0; i < bufferSize; i++)
SPI.transfer(((uint8_t*) buffer)[i]);
unselect();
// no need to wait for transmit mode to be ready since its handled by the radio
setMode(RF69_MODE_TX);
uint32_t txStart = millis();
while (digitalRead(_interruptPin) == 0 && millis() - txStart < RF69_TX_LIMIT_MS); // wait for DIO0 to turn HIGH signalling transmission finish
//while (readReg(REG_IRQFLAGS2) & RF_IRQFLAGS2_PACKETSENT == 0x00); // wait for ModeReady
setMode(RF69_MODE_STANDBY); setMode(RF69_MODE_STANDBY);
} }
// internal function - interrupt gets called when a packet is received
void RFM69::interruptHandler() { void RFM69::interruptHandler() {
//pinMode(4, OUTPUT); //pinMode(4, OUTPUT);
//digitalWrite(4, 1); //digitalWrite(4, 1);
@ -269,29 +310,32 @@ void RFM69::interruptHandler() {
//RSSI = readRSSI(); //RSSI = readRSSI();
setMode(RF69_MODE_STANDBY); setMode(RF69_MODE_STANDBY);
select(); select();
SPI.transfer(REG_FIFO & 0x7f); SPI.transfer(REG_FIFO & 0x7F);
PAYLOADLEN = SPI.transfer(0); PAYLOADLEN = SPI.transfer(0);
PAYLOADLEN = PAYLOADLEN > 66 ? 66 : PAYLOADLEN; //precaution PAYLOADLEN = PAYLOADLEN > 66 ? 66 : PAYLOADLEN; // precaution
TARGETID = SPI.transfer(0); TARGETID = SPI.transfer(0);
if(!(_promiscuousMode || TARGETID==_address || TARGETID==RF69_BROADCAST_ADDR)) //match this node's address, or broadcast address or anything in promiscuous mode if(!(_promiscuousMode || TARGETID == _address || TARGETID == RF69_BROADCAST_ADDR) // match this node's address, or broadcast address or anything in promiscuous mode
|| PAYLOADLEN < 3) // address situation could receive packets that are malformed and don't fit this libraries extra fields
{ {
PAYLOADLEN = 0; PAYLOADLEN = 0;
unselect(); unselect();
receiveBegin();
//digitalWrite(4, 0); //digitalWrite(4, 0);
return; return;
} }
DATALEN = PAYLOADLEN - 3; DATALEN = PAYLOADLEN - 3;
SENDERID = SPI.transfer(0); SENDERID = SPI.transfer(0);
byte CTLbyte = SPI.transfer(0); uint8_t CTLbyte = SPI.transfer(0);
ACK_RECEIVED = CTLbyte & 0x80; //extract ACK-requested flag ACK_RECEIVED = CTLbyte & 0x80; // extract ACK-received flag
ACK_REQUESTED = CTLbyte & 0x40; //extract ACK-received flag ACK_REQUESTED = CTLbyte & 0x40; // extract ACK-requested flag
for (byte i= 0; i < DATALEN; i++) for (uint8_t i = 0; i < DATALEN; i++)
{ {
DATA[i] = SPI.transfer(0); DATA[i] = SPI.transfer(0);
} }
if (DATALEN<RF69_MAX_DATA_LEN) DATA[DATALEN]=0; //add null at end of string if (DATALEN < RF69_MAX_DATA_LEN) DATA[DATALEN] = 0; // add null at end of string
unselect(); unselect();
setMode(RF69_MODE_RX); setMode(RF69_MODE_RX);
} }
@ -299,8 +343,10 @@ void RFM69::interruptHandler() {
//digitalWrite(4, 0); //digitalWrite(4, 0);
} }
// internal function
void RFM69::isr0() { selfPointer->interruptHandler(); } void RFM69::isr0() { selfPointer->interruptHandler(); }
// internal function
void RFM69::receiveBegin() { void RFM69::receiveBegin() {
DATALEN = 0; DATALEN = 0;
SENDERID = 0; SENDERID = 0;
@ -311,22 +357,23 @@ void RFM69::receiveBegin() {
RSSI = 0; RSSI = 0;
if (readReg(REG_IRQFLAGS2) & RF_IRQFLAGS2_PAYLOADREADY) if (readReg(REG_IRQFLAGS2) & RF_IRQFLAGS2_PAYLOADREADY)
writeReg(REG_PACKETCONFIG2, (readReg(REG_PACKETCONFIG2) & 0xFB) | RF_PACKET2_RXRESTART); // avoid RX deadlocks writeReg(REG_PACKETCONFIG2, (readReg(REG_PACKETCONFIG2) & 0xFB) | RF_PACKET2_RXRESTART); // avoid RX deadlocks
writeReg(REG_DIOMAPPING1, RF_DIOMAPPING1_DIO0_01); //set DIO0 to "PAYLOADREADY" in receive mode writeReg(REG_DIOMAPPING1, RF_DIOMAPPING1_DIO0_01); // set DIO0 to "PAYLOADREADY" in receive mode
setMode(RF69_MODE_RX); setMode(RF69_MODE_RX);
} }
// checks if a packet was received and/or puts transceiver in receive (ie RX or listen) mode
bool RFM69::receiveDone() { bool RFM69::receiveDone() {
// ATOMIC_BLOCK(ATOMIC_FORCEON) //ATOMIC_BLOCK(ATOMIC_FORCEON)
// { //{
noInterrupts(); //re-enabled in unselect() via setMode() or via receiveBegin() noInterrupts(); // re-enabled in unselect() via setMode() or via receiveBegin()
if (_mode == RF69_MODE_RX && PAYLOADLEN>0) if (_mode == RF69_MODE_RX && PAYLOADLEN > 0)
{ {
setMode(RF69_MODE_STANDBY); //enables interrupts setMode(RF69_MODE_STANDBY); // enables interrupts
return true; return true;
} }
else if (_mode == RF69_MODE_RX) //already in RX no payload yet else if (_mode == RF69_MODE_RX) // already in RX no payload yet
{ {
interrupts(); //explicitly re-enable interrupts interrupts(); // explicitly re-enable interrupts
return false; return false;
} }
receiveBegin(); receiveBegin();
@ -339,40 +386,41 @@ bool RFM69::receiveDone() {
// KEY HAS TO BE 16 bytes !!! // KEY HAS TO BE 16 bytes !!!
void RFM69::encrypt(const char* key) { void RFM69::encrypt(const char* key) {
setMode(RF69_MODE_STANDBY); setMode(RF69_MODE_STANDBY);
if (key!=0) if (key != 0)
{ {
select(); select();
SPI.transfer(REG_AESKEY1 | 0x80); SPI.transfer(REG_AESKEY1 | 0x80);
for (byte i = 0; i<16; i++) for (uint8_t i = 0; i < 16; i++)
SPI.transfer(key[i]); SPI.transfer(key[i]);
unselect(); unselect();
} }
writeReg(REG_PACKETCONFIG2, (readReg(REG_PACKETCONFIG2) & 0xFE) | (key ? 1 : 0)); writeReg(REG_PACKETCONFIG2, (readReg(REG_PACKETCONFIG2) & 0xFE) | (key ? 1 : 0));
} }
int RFM69::readRSSI(bool forceTrigger) { // get the received signal strength indicator (RSSI)
int rssi = 0; int16_t RFM69::readRSSI(bool forceTrigger) {
int16_t rssi = 0;
if (forceTrigger) if (forceTrigger)
{ {
//RSSI trigger not needed if DAGC is in continuous mode // RSSI trigger not needed if DAGC is in continuous mode
writeReg(REG_RSSICONFIG, RF_RSSI_START); writeReg(REG_RSSICONFIG, RF_RSSI_START);
while ((readReg(REG_RSSICONFIG) & RF_RSSI_DONE) == 0x00); // Wait for RSSI_Ready while ((readReg(REG_RSSICONFIG) & RF_RSSI_DONE) == 0x00); // wait for RSSI_Ready
} }
rssi = -readReg(REG_RSSIVALUE); rssi = -readReg(REG_RSSIVALUE);
rssi >>= 1; rssi >>= 1;
return rssi; return rssi;
} }
byte RFM69::readReg(byte addr) uint8_t RFM69::readReg(uint8_t addr)
{ {
select(); select();
SPI.transfer(addr & 0x7F); SPI.transfer(addr & 0x7F);
byte regval = SPI.transfer(0); uint8_t regval = SPI.transfer(0);
unselect(); unselect();
return regval; return regval;
} }
void RFM69::writeReg(byte addr, byte value) void RFM69::writeReg(uint8_t addr, uint8_t value)
{ {
select(); select();
SPI.transfer(addr | 0x80); SPI.transfer(addr | 0x80);
@ -380,51 +428,55 @@ void RFM69::writeReg(byte addr, byte value)
unselect(); unselect();
} }
/// Select the transceiver // select the RFM69 transceiver (save SPI settings, set CS low)
void RFM69::select() { void RFM69::select() {
noInterrupts(); noInterrupts();
//save current SPI settings // save current SPI settings
_SPCR = SPCR; _SPCR = SPCR;
_SPSR = SPSR; _SPSR = SPSR;
//set RFM69 SPI settings // set RFM69 SPI settings
SPI.setDataMode(SPI_MODE0); SPI.setDataMode(SPI_MODE0);
SPI.setBitOrder(MSBFIRST); 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.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
digitalWrite(_slaveSelectPin, LOW); digitalWrite(_slaveSelectPin, LOW);
} }
/// UNselect the transceiver chip // unselect the RFM69 transceiver (set CS high, restore SPI settings)
void RFM69::unselect() { void RFM69::unselect() {
digitalWrite(_slaveSelectPin, HIGH); digitalWrite(_slaveSelectPin, HIGH);
//restore SPI settings to what they were before talking to RFM69 // restore SPI settings to what they were before talking to RFM69
SPCR = _SPCR; SPCR = _SPCR;
SPSR = _SPSR; SPSR = _SPSR;
interrupts(); interrupts();
} }
// ON = disable filtering to capture all frames on network // true = disable filtering to capture all frames on network
// OFF = enable node+broadcast filtering to capture only frames sent to this/broadcast address // false = enable node/broadcast filtering to capture only frames sent to this/broadcast address
void RFM69::promiscuous(bool onOff) { void RFM69::promiscuous(bool onOff) {
_promiscuousMode=onOff; _promiscuousMode = onOff;
//writeReg(REG_PACKETCONFIG1, (readReg(REG_PACKETCONFIG1) & 0xF9) | (onOff ? RF_PACKET1_ADRSFILTERING_OFF : RF_PACKET1_ADRSFILTERING_NODEBROADCAST)); //writeReg(REG_PACKETCONFIG1, (readReg(REG_PACKETCONFIG1) & 0xF9) | (onOff ? RF_PACKET1_ADRSFILTERING_OFF : RF_PACKET1_ADRSFILTERING_NODEBROADCAST));
} }
// for RFM69HW only: you must call setHighPower(true) after initialize() or else transmission won't work
void RFM69::setHighPower(bool onOff) { void RFM69::setHighPower(bool onOff) {
_isRFM69HW = onOff; _isRFM69HW = onOff;
writeReg(REG_OCP, _isRFM69HW ? RF_OCP_OFF : RF_OCP_ON); writeReg(REG_OCP, _isRFM69HW ? RF_OCP_OFF : RF_OCP_ON);
if (_isRFM69HW) //turning ON if (_isRFM69HW) // turning ON
writeReg(REG_PALEVEL, (readReg(REG_PALEVEL) & 0x1F) | RF_PALEVEL_PA1_ON | RF_PALEVEL_PA2_ON); //enable P1 & P2 amplifier stages writeReg(REG_PALEVEL, (readReg(REG_PALEVEL) & 0x1F) | RF_PALEVEL_PA1_ON | RF_PALEVEL_PA2_ON); // enable P1 & P2 amplifier stages
else else
writeReg(REG_PALEVEL, RF_PALEVEL_PA0_ON | RF_PALEVEL_PA1_OFF | RF_PALEVEL_PA2_OFF | _powerLevel); //enable P0 only writeReg(REG_PALEVEL, RF_PALEVEL_PA0_ON | RF_PALEVEL_PA1_OFF | RF_PALEVEL_PA2_OFF | _powerLevel); // enable P0 only
} }
// internal function
void RFM69::setHighPowerRegs(bool onOff) { void RFM69::setHighPowerRegs(bool onOff) {
writeReg(REG_TESTPA1, onOff ? 0x5D : 0x55); writeReg(REG_TESTPA1, onOff ? 0x5D : 0x55);
writeReg(REG_TESTPA2, onOff ? 0x7C : 0x70); writeReg(REG_TESTPA2, onOff ? 0x7C : 0x70);
} }
void RFM69::setCS(byte newSPISlaveSelect) { // set the slave select (CS) pin
void RFM69::setCS(uint8_t newSPISlaveSelect) {
_slaveSelectPin = newSPISlaveSelect; _slaveSelectPin = newSPISlaveSelect;
digitalWrite(_slaveSelectPin, HIGH);
pinMode(_slaveSelectPin, OUTPUT); pinMode(_slaveSelectPin, OUTPUT);
} }
@ -447,23 +499,23 @@ void SerialPrint_P(PGM_P str, void (*f)(uint8_t) = SerialWrite ) {
void RFM69::readAllRegs() void RFM69::readAllRegs()
{ {
byte regVal; uint8_t regVal;
#if REGISTER_DETAIL #if REGISTER_DETAIL
int capVal; int capVal;
//... State Variables for intelligent decoding //... State Variables for intelligent decoding
byte modeFSK = 0; uint8_t modeFSK = 0;
int bitRate = 0; int bitRate = 0;
int freqDev = 0; int freqDev = 0;
long freqCenter = 0; long freqCenter = 0;
#endif #endif
SerialPrint ( "Address - HEX - BIN\n" ); SerialPrint ( "Address - HEX - BIN\n" );
for (byte regAddr = 1; regAddr <= 0x4F; regAddr++) for (uint8_t regAddr = 1; regAddr <= 0x4F; regAddr++)
{ {
select(); select();
SPI.transfer(regAddr & 0x7f); // send address + r/w bit SPI.transfer(regAddr & 0x7F); // send address + r/w bit
regVal = SPI.transfer(0); regVal = SPI.transfer(0);
unselect(); unselect();
@ -474,7 +526,8 @@ void RFM69::readAllRegs()
Serial.println(regVal,BIN); Serial.println(regVal,BIN);
#if REGISTER_DETAIL #if REGISTER_DETAIL
switch ( regAddr ) { switch ( regAddr )
{
case 0x1 : { case 0x1 : {
SerialPrint ( "Controls the automatic Sequencer ( see section 4.2 )\nSequencerOff : " ); SerialPrint ( "Controls the automatic Sequencer ( see section 4.2 )\nSequencerOff : " );
if ( 0x80 & regVal ) { if ( 0x80 & regVal ) {
@ -705,33 +758,22 @@ void RFM69::readAllRegs()
default : { default : {
} }
} }
#endif #endif
}
}
unselect(); unselect();
} }
byte RFM69::readTemperature(byte calFactor) //returns centigrade uint8_t RFM69::readTemperature(uint8_t calFactor) // returns centigrade
{ {
setMode(RF69_MODE_STANDBY); setMode(RF69_MODE_STANDBY);
writeReg(REG_TEMP1, RF_TEMP1_MEAS_START); writeReg(REG_TEMP1, RF_TEMP1_MEAS_START);
while ((readReg(REG_TEMP1) & RF_TEMP1_MEAS_RUNNING)) Serial.print('*'); while ((readReg(REG_TEMP1) & RF_TEMP1_MEAS_RUNNING));
return ~readReg(REG_TEMP2) + COURSE_TEMP_COEF + calFactor; //'complement'corrects the slope, rising temp = rising val return ~readReg(REG_TEMP2) + COURSE_TEMP_COEF + calFactor; // 'complement' corrects the slope, rising temp = rising val
} // COURSE_TEMP_COEF puts reading in the ballpark, user can add additional correction } // COURSE_TEMP_COEF puts reading in the ballpark, user can add additional correction
void RFM69::rcCalibration() void RFM69::rcCalibration()
{ {
writeReg(REG_OSC1, RF_OSC1_RCCAL_START); writeReg(REG_OSC1, RF_OSC1_RCCAL_START);
while ((readReg(REG_OSC1) & RF_OSC1_RCCAL_DONE) == 0x00); while ((readReg(REG_OSC1) & RF_OSC1_RCCAL_DONE) == 0x00);
} }

122
RFM69.h
View File

@ -9,73 +9,81 @@
// This program is free software; you can redistribute it // This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General // and/or modify it under the terms of the GNU General
// Public License as published by the Free Software // Public License as published by the Free Software
// Foundation; either version 2 of the License, or // Foundation; either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will // This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the // be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A // implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public // PARTICULAR PURPOSE. See the GNU General Public
// License for more details. // License for more details.
// //
// You should have received a copy of the GNU General // You should have received a copy of the GNU General
// Public License along with this program; if not, write // Public License along with this program.
// to the Free Software Foundation, Inc., // If not, see <http://www.gnu.org/licenses/>.
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// //
// Licence can be viewed at // Licence can be viewed at
// http://www.fsf.org/licenses/gpl.txt // http://www.gnu.org/licenses/gpl-3.0.txt
// //
// Please maintain this license information along with authorship // Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code // and copyright notices in any redistribution of this code
// ********************************************************************************** // **********************************************************************************
#ifndef RFM69_h #ifndef RFM69_h
#define RFM69_h #define RFM69_h
#include <Arduino.h> //assumes Arduino IDE v1.0 or greater #include <Arduino.h> // assumes Arduino IDE v1.0 or greater
#define RF69_MAX_DATA_LEN 61 // to take advantage of the built in AES/CRC we want to limit the frame size to the internal FIFO size (66 bytes - 3 bytes overhead) #define RF69_MAX_DATA_LEN 61 // to take advantage of the built in AES/CRC we want to limit the frame size to the internal FIFO size (66 bytes - 3 bytes overhead - 2 bytes crc)
#define RF69_SPI_CS SS // SS is the SPI slave select pin, for instance D10 on atmega328 #define RF69_SPI_CS SS // SS is the SPI slave select pin, for instance D10 on ATmega328
// INT0 on AVRs should be connected to RFM69's DIO0 (ex on Atmega328 it's D2, on Atmega644/1284 it's D2) // INT0 on AVRs should be connected to RFM69's DIO0 (ex on ATmega328 it's D2, on ATmega644/1284 it's D2)
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega88) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega88__) || defined(__AVR_ATmega32U4__) #if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega88) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega88__)
#define RF69_IRQ_PIN 2 #define RF69_IRQ_PIN 2
#define RF69_IRQ_NUM 0 #define RF69_IRQ_NUM 0
#elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__) #elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
#define RF69_IRQ_PIN 2 #define RF69_IRQ_PIN 2
#define RF69_IRQ_NUM 2 #define RF69_IRQ_NUM 2
#elif defined(__AVR_ATmega32U4__)
#define RF69_IRQ_PIN 3
#define RF69_IRQ_NUM 0
#else
#define RF69_IRQ_PIN 2
#define RF69_IRQ_NUM 0
#endif #endif
#define CSMA_LIMIT -90 // upper RX signal sensitivity threshold in dBm for carrier sense access
#define RF69_MODE_SLEEP 0 // XTAL OFF
#define RF69_MODE_STANDBY 1 // XTAL ON
#define RF69_MODE_SYNTH 2 // PLL ON
#define RF69_MODE_RX 3 // RX MODE
#define RF69_MODE_TX 4 // TX MODE
//available frequency bands #define CSMA_LIMIT -90 // upper RX signal sensitivity threshold in dBm for carrier sense access
#define RF69_315MHZ 31 // non trivial values to avoid misconfiguration #define RF69_MODE_SLEEP 0 // XTAL OFF
#define RF69_433MHZ 43 #define RF69_MODE_STANDBY 1 // XTAL ON
#define RF69_868MHZ 86 #define RF69_MODE_SYNTH 2 // PLL ON
#define RF69_915MHZ 91 #define RF69_MODE_RX 3 // RX MODE
#define RF69_MODE_TX 4 // TX MODE
// available frequency bands
#define RF69_315MHZ 31 // non trivial values to avoid misconfiguration
#define RF69_433MHZ 43
#define RF69_868MHZ 86
#define RF69_915MHZ 91
#define null 0 #define null 0
#define COURSE_TEMP_COEF -90 // puts the temperature reading in the ballpark, user can fine tune the returned value #define COURSE_TEMP_COEF -90 // puts the temperature reading in the ballpark, user can fine tune the returned value
#define RF69_BROADCAST_ADDR 255 #define RF69_BROADCAST_ADDR 255
#define RF69_CSMA_LIMIT_MS 1000 #define RF69_CSMA_LIMIT_MS 1000
#define RF69_TX_LIMIT_MS 1000
#define RF69_FSTEP 61.03515625 // == FXOSC / 2^19 = 32MHz / 2^19 (p13 in datasheet)
class RFM69 { class RFM69 {
public: public:
static volatile byte DATA[RF69_MAX_DATA_LEN]; // recv/xmit buf, including hdr & crc bytes static volatile uint8_t DATA[RF69_MAX_DATA_LEN]; // recv/xmit buf, including header & crc bytes
static volatile byte DATALEN; static volatile uint8_t DATALEN;
static volatile byte SENDERID; static volatile uint8_t SENDERID;
static volatile byte TARGETID; //should match _address static volatile uint8_t TARGETID; // should match _address
static volatile byte PAYLOADLEN; static volatile uint8_t PAYLOADLEN;
static volatile byte ACK_REQUESTED; static volatile uint8_t ACK_REQUESTED;
static volatile byte ACK_RECEIVED; /// Should be polled immediately after sending a packet with ACK request static volatile uint8_t ACK_RECEIVED; // should be polled immediately after sending a packet with ACK request
static volatile int RSSI; //most accurate RSSI during reception (closest to the reception) static volatile int16_t RSSI; // most accurate RSSI during reception (closest to the reception)
static volatile byte _mode; //should be protected? static volatile uint8_t _mode; // should be protected?
RFM69(byte slaveSelectPin=RF69_SPI_CS, byte interruptPin=RF69_IRQ_PIN, bool isRFM69HW=false, byte interruptNum=RF69_IRQ_NUM) { RFM69(uint8_t slaveSelectPin=RF69_SPI_CS, uint8_t interruptPin=RF69_IRQ_PIN, bool isRFM69HW=false, uint8_t interruptNum=RF69_IRQ_NUM) {
_slaveSelectPin = slaveSelectPin; _slaveSelectPin = slaveSelectPin;
_interruptPin = interruptPin; _interruptPin = interruptPin;
_interruptNum = interruptNum; _interruptNum = interruptNum;
@ -85,49 +93,51 @@ class RFM69 {
_isRFM69HW = isRFM69HW; _isRFM69HW = isRFM69HW;
} }
bool initialize(byte freqBand, byte ID, byte networkID=1); bool initialize(uint8_t freqBand, uint8_t ID, uint8_t networkID=1);
void setAddress(byte addr); void setAddress(uint8_t addr);
void setNetwork(uint8_t networkID);
bool canSend(); bool canSend();
void send(byte toAddress, const void* buffer, byte bufferSize, bool requestACK=false); void send(uint8_t toAddress, const void* buffer, uint8_t bufferSize, bool requestACK=false);
bool sendWithRetry(byte toAddress, const void* buffer, byte bufferSize, byte retries=2, byte retryWaitTime=40); //40ms roundtrip req for 61byte packets bool sendWithRetry(uint8_t toAddress, const void* buffer, uint8_t bufferSize, uint8_t retries=2, uint8_t retryWaitTime=40); // 40ms roundtrip req for 61byte packets
bool receiveDone(); bool receiveDone();
bool ACKReceived(byte fromNodeID); bool ACKReceived(uint8_t fromNodeID);
bool ACKRequested(); bool ACKRequested();
void sendACK(const void* buffer = "", uint8_t bufferSize=0); void sendACK(const void* buffer = "", uint8_t bufferSize=0);
void setFrequency(uint32_t FRF); uint32_t getFrequency();
void setFrequency(uint32_t freqHz);
void encrypt(const char* key); void encrypt(const char* key);
void setCS(byte newSPISlaveSelect); void setCS(uint8_t newSPISlaveSelect);
int readRSSI(bool forceTrigger=false); int16_t readRSSI(bool forceTrigger=false);
void promiscuous(bool onOff=true); void promiscuous(bool onOff=true);
void setHighPower(bool onOFF=true); //have to call it after initialize for RFM69HW void setHighPower(bool onOFF=true); // has to be called after initialize() for RFM69HW
void setPowerLevel(byte level); //reduce/increase transmit power level void setPowerLevel(uint8_t level); // reduce/increase transmit power level
void sleep(); void sleep();
byte readTemperature(byte calFactor=0); //get CMOS temperature (8bit) uint8_t readTemperature(uint8_t calFactor=0); // get CMOS temperature (8bit)
void rcCalibration(); //calibrate the internal RC oscillator for use in wide temperature variations - see datasheet section [4.3.5. RC Timer Accuracy] void rcCalibration(); // calibrate the internal RC oscillator for use in wide temperature variations - see datasheet section [4.3.5. RC Timer Accuracy]
// allow hacking registers by making these public // allow hacking registers by making these public
byte readReg(byte addr); uint8_t readReg(uint8_t addr);
void writeReg(byte addr, byte val); void writeReg(uint8_t addr, uint8_t val);
void readAllRegs(); void readAllRegs();
protected: protected:
static void isr0(); static void isr0();
void virtual interruptHandler(); void virtual interruptHandler();
void sendFrame(byte toAddress, const void* buffer, byte size, bool requestACK=false, bool sendACK=false); void sendFrame(uint8_t toAddress, const void* buffer, uint8_t size, bool requestACK=false, bool sendACK=false);
static RFM69* selfPointer; static RFM69* selfPointer;
byte _slaveSelectPin; uint8_t _slaveSelectPin;
byte _interruptPin; uint8_t _interruptPin;
byte _interruptNum; uint8_t _interruptNum;
byte _address; uint8_t _address;
bool _promiscuousMode; bool _promiscuousMode;
byte _powerLevel; uint8_t _powerLevel;
bool _isRFM69HW; bool _isRFM69HW;
byte _SPCR; uint8_t _SPCR;
byte _SPSR; uint8_t _SPSR;
void receiveBegin(); void receiveBegin();
void setMode(byte mode); void setMode(uint8_t mode);
void setHighPowerRegs(bool onOff); void setHighPowerRegs(bool onOff);
void select(); void select();
void unselect(); void unselect();

File diff suppressed because it is too large Load Diff

View File

@ -23,15 +23,21 @@ receiveDone KEYWORD2
ACKReceived KEYWORD2 ACKReceived KEYWORD2
sendACK KEYWORD2 sendACK KEYWORD2
setFrequency KEYWORD2 setFrequency KEYWORD2
getFrequency KEYWORD2
encrypt KEYWORD2 encrypt KEYWORD2
setCS KEYWORD2 setCS KEYWORD2
readRSSI KEYWORD2 readRSSI KEYWORD2
promiscuous KEYWORD2 promiscuous KEYWORD2
setHighPower KEYWORD2 setHighPower KEYWORD2
CryptFunction KEYWORD2
sleep KEYWORD2 sleep KEYWORD2
readReg KEYWORD2 readReg KEYWORD2
writeReg KEYWORD2 writeReg KEYWORD2
setNetwork KEYWORD2
ACKRequested KEYWORD2
setPowerLevel KEYWORD2
readTemperature KEYWORD2
rcCalibration KEYWORD2
readAllRegs KEYWORD2
####################################### #######################################
# Constants (LITERAL1) # Constants (LITERAL1)
@ -49,4 +55,5 @@ SENDERID LITERAL2
TARGETID LITERAL2 TARGETID LITERAL2
PAYLOADLEN LITERAL2 PAYLOADLEN LITERAL2
ACK_REQUESTED LITERAL2 ACK_REQUESTED LITERAL2
ACK_RECEIVED LITERAL2 ACK_RECEIVED LITERAL2
RSSI LITERAL2

12
library.json Normal file
View File

@ -0,0 +1,12 @@
{
"name": "RFM69",
"keywords": "rf, radio, wireless, spi",
"description": "RFM69 library for RFM69W, RFM69HW, RFM69CW, RFM69HCW (semtech SX1231, SX1231H)",
"repository":
{
"type": "git",
"url": "https://github.com/LowPowerLab/RFM69.git"
},
"frameworks": "arduino",
"platforms": "atmelavr"
}