From bd50fa29dfc1b1db18a5e53b431d84c60fe6cdb5 Mon Sep 17 00:00:00 2001 From: LowPowerLab Date: Tue, 10 Nov 2015 22:57:38 -0500 Subject: [PATCH] Add Automatic Transmission Control (ATC) --- Examples/Gateway/Gateway.ino | 33 +++- Examples/MotionMote/MotionMote.ino | 68 +++++--- Examples/Node/Node.ino | 41 ++++- RFM69_ATC.cpp | 254 +++++++++++++++++++++++++++++ RFM69_ATC.h | 70 ++++++++ keywords.txt | 2 + 6 files changed, 429 insertions(+), 39 deletions(-) create mode 100644 RFM69_ATC.cpp create mode 100644 RFM69_ATC.h diff --git a/Examples/Gateway/Gateway.ino b/Examples/Gateway/Gateway.ino index bd1dd17..3550c04 100644 --- a/Examples/Gateway/Gateway.ino +++ b/Examples/Gateway/Gateway.ino @@ -1,13 +1,17 @@ -// Sample RFM69 receiver/gateway sketch, with ACK and optional encryption +// Sample RFM69 receiver/gateway sketch, with ACK and optional encryption, and Automatic Transmission Control // Passes through any wireless received messages to the serial port & responds to ACKs // It also looks for an onboard FLASH chip, if present -// Library and code by Felix Rusu - felix@lowpowerlab.com -// Get the RFM69 and SPIFlash library at: https://github.com/LowPowerLab/ +// RFM69 library and sample code by Felix Rusu - http://LowPowerLab.com/contact +// Copyright Felix Rusu (2015) #include //get it here: https://www.github.com/lowpowerlab/rfm69 -#include +#include //get it here: https://www.github.com/lowpowerlab/rfm69 +#include //comes with Arduino IDE (www.arduino.cc) #include //get it here: https://www.github.com/lowpowerlab/spiflash +//********************************************************************************************* +//************ IMPORTANT SETTINGS - YOU MUST CHANGE/CONFIGURE TO FIT YOUR HARDWARE ************* +//********************************************************************************************* #define NODEID 1 //unique for each node on same network #define NETWORKID 100 //the same on all nodes that talk to each other //Match frequency to the hardware version of the radio on your Moteino (uncomment one): @@ -16,6 +20,9 @@ //#define FREQUENCY RF69_915MHZ #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 ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL +//********************************************************************************************* + #define SERIAL_BAUD 115200 #ifdef __AVR_ATmega1284P__ @@ -26,7 +33,12 @@ #define FLASH_SS 8 // and FLASH SS on D8 #endif -RFM69 radio; +#ifdef ENABLE_ATC + RFM69_ATC radio; +#else + RFM69 radio; +#endif + SPIFlash flash(FLASH_SS, 0xEF30); //EF30 for 4mbit Windbond chip (W25X40CL) bool promiscuousMode = false; //set to 'true' to sniff all packets on the same network @@ -39,7 +51,7 @@ void setup() { #endif radio.encrypt(ENCRYPTKEY); radio.promiscuous(promiscuousMode); - //radio.setFrequency(919000000); + //radio.setFrequency(919000000); //set frequency to some custom frequency char buff[50]; sprintf(buff, "\nListening at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915); Serial.println(buff); @@ -61,10 +73,13 @@ void setup() { // Serial.print(MAC[i], HEX); // Serial.print(' '); //} - } else - Serial.println("SPI Flash Init FAIL! (is chip present?)"); + Serial.println("SPI Flash MEM not found (is chip soldered?)..."); + +#ifdef ENABLE_ATC + Serial.println("RFM69_ATC Enabled (Auto Transmission Control)"); +#endif } byte ackCount=0; @@ -169,4 +184,4 @@ void Blink(byte PIN, int DELAY_MS) digitalWrite(PIN,HIGH); delay(DELAY_MS); digitalWrite(PIN,LOW); -} +} \ No newline at end of file diff --git a/Examples/MotionMote/MotionMote.ino b/Examples/MotionMote/MotionMote.ino index edb0847..35cb4a8 100644 --- a/Examples/MotionMote/MotionMote.ino +++ b/Examples/MotionMote/MotionMote.ino @@ -7,8 +7,8 @@ // 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 +// Copyright Felix Rusu of LowPowerLab.com, 2015-11-10 +// RFM69 library and sample code by Felix Rusu - lowpowerlab.com/contact // ********************************************************************************** // License // ********************************************************************************** @@ -34,14 +34,14 @@ // Please maintain this license information along with authorship // and copyright notices in any redistribution of this code // ********************************************************************************** - #include //get it here: https://www.github.com/lowpowerlab/rfm69 -#include +#include //get it here: https://www.github.com/lowpowerlab/rfm69 +#include //comes with Arduino IDE (www.arduino.cc) #include //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 ************* +//************ IMPORTANT SETTINGS - YOU MUST CHANGE/CONFIGURE TO FIT YOUR HARDWARE ************* //********************************************************************************************* #define NODEID 88 //unique for each node on same network #define NETWORKID 100 //the same on all nodes that talk to each other @@ -50,17 +50,19 @@ //#define FREQUENCY RF69_433MHZ //#define FREQUENCY RF69_868MHZ #define FREQUENCY RF69_915MHZ -#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 ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes! +#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL //********************************************************************************************* - -#define ACK_TIME 30 // max # of ms to wait for an ack +#define ACK_TIME 30 // max # of ms to wait for an ack #define ONBOARDLED 9 // Moteinos have LEDs on D9 -#define EXTLED 5 // MotionOLEDMote has an external LED on D5 -#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 ~= 880 (ratio given by 10k+4.7K divider from VBAT_COND = 1.47 multiplier) -#define BATT_CYCLES 30 //read and report battery voltage every this many wakeup cycles (ex 30cycles * 8sec sleep = 240sec/4min) -#define MOTIONPIN 1 //hardware interrupt 1 (D3) - +#define LED 5 // MotionOLEDMote has an external LED on D5 +#define MOTION_PIN 3 // D3 +#define MOTION_IRQ 1 // hardware interrupt 1 (D3) - where motion sensors OUTput is connected, this will generate an interrupt every time there is MOTION +#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 450 // 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.51 // >>> fine tune this parameter to match your voltage when fully charged + // details on how this works: https://lowpowerlab.com/forum/index.php/topic,1206.0.html //#define SERIAL_EN //comment this out when deploying to an installed SM to save a few KB of sketch size #define SERIAL_BAUD 115200 #ifdef SERIAL_EN @@ -71,13 +73,20 @@ #define DEBUGln(input); #endif -RFM69 radio; +#ifdef ENABLE_ATC + RFM69_ATC radio; +#else + RFM69 radio; +#endif volatile boolean motionDetected=false; float batteryVolts = 5; -char* BATstr="BAT:5.00v"; +char BATstr[20]; //longest battery voltage reading message = 9chars char sendBuf[32]; byte sendLen; +void motionIRQ(void); +void checkBattery(void); + void setup() { Serial.begin(SERIAL_BAUD); radio.initialize(FREQUENCY,NODEID,NETWORKID); @@ -85,18 +94,33 @@ void setup() { radio.setHighPower(); //uncomment only for RFM69HW! #endif radio.encrypt(ENCRYPTKEY); - pinMode(MOTIONPIN, INPUT); - attachInterrupt(MOTIONPIN, motionIRQ, RISING); + +//Auto Transmission Control - dials down transmit power to save battery (-100 is the noise floor, -90 is still pretty good) +//For indoor nodes that are pretty static and at pretty stable temperatures (like a MotionMote) -90dBm is quite safe +//For more variable nodes that can expect to move or experience larger temp drifts a lower margin like -70 to -80 would probably be better +//Always test your ATC mote in the edge cases in your own environment to ensure ATC will perform as you expect +#ifdef ENABLE_ATC + radio.enableAutoPower(-90); +#endif + + pinMode(MOTION_PIN, INPUT); + attachInterrupt(MOTION_IRQ, motionIRQ, RISING); char buff[50]; sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915); DEBUGln(buff); pinMode(ONBOARDLED, OUTPUT); - pinMode(EXTLED, OUTPUT); + pinMode(LED, OUTPUT); + radio.sendWithRetry(GATEWAYID, "START", 5); + +#ifdef ENABLE_ATC + DEBUGln("RFM69_ATC Enabled (Auto Transmission Control)\n"); +#endif } void motionIRQ() { motionDetected=true; + DEBUGln("IRQ"); } byte batteryReportCycles=0; @@ -104,7 +128,7 @@ void loop() { checkBattery(); if (motionDetected) { - digitalWrite(EXTLED, HIGH); + digitalWrite(LED, HIGH); sprintf(sendBuf, "MOTION BAT:%sv", BATstr); sendLen = strlen(sendBuf); @@ -120,13 +144,13 @@ void loop() { DEBUGln(BATstr); radio.sleep(); - digitalWrite(EXTLED, LOW); + digitalWrite(LED, LOW); } else if (batteryReportCycles == BATT_CYCLES) { sprintf(sendBuf, "BAT:%sv", BATstr); sendLen = strlen(sendBuf); - radio.send(GATEWAYID, sendBuf, sendLen); + radio.sendWithRetry(GATEWAYID, sendBuf, sendLen); radio.sleep(); batteryReportCycles=0; } @@ -143,7 +167,7 @@ void checkBattery() unsigned int readings=0; for (byte i=0; i<10; i++) //take 10 samples, and average readings+=analogRead(BATT_MONITOR); - batteryVolts = (readings / 10.0) * 0.00322 * 1.42; + 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 cycleCount = 0; } diff --git a/Examples/Node/Node.ino b/Examples/Node/Node.ino index 2ea21aa..bc09055 100644 --- a/Examples/Node/Node.ino +++ b/Examples/Node/Node.ino @@ -1,15 +1,19 @@ -// Sample RFM69 sender/node sketch, with ACK and optional encryption +// Sample RFM69 sender/node sketch, with ACK and optional encryption, and Automatic Transmission Control // Sends periodic messages of increasing length to gateway (id=1) // It also looks for an onboard FLASH chip, if present -// Library and code by Felix Rusu - felix@lowpowerlab.com -// Get the RFM69 and SPIFlash library at: https://github.com/LowPowerLab/ +// RFM69 library and sample code by Felix Rusu - http://LowPowerLab.com/contact +// Copyright Felix Rusu (2015) #include //get it here: https://www.github.com/lowpowerlab/rfm69 +#include //get it here: https://www.github.com/lowpowerlab/rfm69 #include #include //get it here: https://www.github.com/lowpowerlab/spiflash -#define NODEID 2 //unique for each node on same network -#define NETWORKID 100 //the same on all nodes that talk to each other +//********************************************************************************************* +//************ IMPORTANT SETTINGS - YOU MUST CHANGE/CONFIGURE TO FIT YOUR HARDWARE ************* +//********************************************************************************************* +#define NODEID 2 //must be unique for each node on same network (range up to 254, 255 is used for broadcast) +#define NETWORKID 100 //the same on all nodes that talk to each other (range up to 255) #define GATEWAYID 1 //Match frequency to the hardware version of the radio on your Moteino (uncomment one): #define FREQUENCY RF69_433MHZ @@ -17,6 +21,9 @@ //#define FREQUENCY RF69_915MHZ #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 ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL +//********************************************************************************************* + #ifdef __AVR_ATmega1284P__ #define LED 15 // Moteino MEGAs have LEDs on D15 #define FLASH_SS 23 // and FLASH SS on D23 @@ -33,7 +40,12 @@ char buff[20]; byte sendSize=0; boolean requestACK = false; SPIFlash flash(FLASH_SS, 0xEF30); //EF30 for 4mbit Windbond chip (W25X40CL) -RFM69 radio; + +#ifdef ENABLE_ATC + RFM69_ATC radio; +#else + RFM69 radio; +#endif void setup() { Serial.begin(SERIAL_BAUD); @@ -43,6 +55,15 @@ void setup() { #endif radio.encrypt(ENCRYPTKEY); //radio.setFrequency(919000000); //set frequency to some custom frequency + +//Auto Transmission Control - dials down transmit power to save battery (-100 is the noise floor, -90 is still pretty good) +//For indoor nodes that are pretty static and at pretty stable temperatures (like a MotionMote) -90dBm is quite safe +//For more variable nodes that can expect to move or experience larger temp drifts a lower margin like -70 to -80 would probably be better +//Always test your ATC mote in the edge cases in your own environment to ensure ATC will perform as you expect +#ifdef ENABLE_ATC + radio.enableAutoPower(-70); +#endif + char buff[50]; sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915); Serial.println(buff); @@ -59,7 +80,11 @@ void setup() { Serial.println(); } else - Serial.println("SPI Flash Init FAIL! (is chip present?)"); + Serial.println("SPI Flash MEM not found (is chip soldered?)..."); + +#ifdef ENABLE_ATC + Serial.println("RFM69_ATC Enabled (Auto Transmission Control)\n"); +#endif } long lastPeriod = 0; @@ -168,4 +193,4 @@ void Blink(byte PIN, int DELAY_MS) digitalWrite(PIN,HIGH); delay(DELAY_MS); digitalWrite(PIN,LOW); -} +} \ No newline at end of file diff --git a/RFM69_ATC.cpp b/RFM69_ATC.cpp new file mode 100644 index 0000000..ce556db --- /dev/null +++ b/RFM69_ATC.cpp @@ -0,0 +1,254 @@ +// ********************************************************************************** +// Automatic Transmit Power Control class derived from RFM69 library. +// Discussion and details in this forum post: https://lowpowerlab.com/forum/index.php/topic,688.0.html +// ********************************************************************************** +// Copyright Thomas Studwell (2014,2015) +// Adjustments by Felix Rusu, 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 . +// +// 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 +#include // include the RFM69 library files as well +#include +#include + +volatile uint8_t RFM69_ATC::ACK_RSSI_REQUESTED; // new type of flag on ACK_REQUEST + +//============================================================================= +// initialize() - some extra initialization before calling base class +//============================================================================= +bool RFM69_ATC::initialize(uint8_t freqBand, uint8_t nodeID, uint8_t networkID) { + _targetRSSI = 0; // TomWS1: default to disabled + _ackRSSI = 0; // TomWS1: no existing response at init time + ACK_RSSI_REQUESTED = 0; // TomWS1: init to none + //_powerBoost = false; // TomWS1: require someone to explicitly turn boost on! + _transmitLevel = 31; // TomWS1: match default value in PA Level register + return RFM69::initialize(freqBand, nodeID, networkID); // use base class to initialize most everything +} + +//============================================================================= +// setMode() - got to set updated transmit power level before switching to TX mode +//============================================================================= +void RFM69_ATC::setMode(uint8_t newMode) { + if (newMode == _mode) return; + //_powerBoost = (_transmitLevel >= 50); // this needs to be set before changing mode just in case setHighPowerRegs is called + RFM69::setMode(newMode); // call base class first + + if (newMode == RF69_MODE_TX) // special stuff if switching to TX mode + { + if (_targetRSSI) setPowerLevel(_transmitLevel); // TomWS1: apply most recent transmit level if auto power + //if (_isRFM69HW) setHighPowerRegs(true); + } +} + +//============================================================================= +// sendAck() - updated to call new sendFrame with additional parameters +//============================================================================= +// should be called immediately after reception in case sender wants ACK +void RFM69_ATC::sendACK(const void* buffer, uint8_t bufferSize) { + ACK_REQUESTED = 0; // TomWS1 added to make sure we don't end up in a timing race and infinite loop sending Acks + uint8_t sender = SENDERID; + int16_t _RSSI = RSSI; // save payload received RSSI value + bool sendRSSI = ACK_RSSI_REQUESTED; + 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(); + SENDERID = sender; // TomWS1: Restore SenderID after it gets wiped out by receiveDone() + sendFrame(sender, buffer, bufferSize, false, true, sendRSSI, _RSSI); // TomWS1: Special override on sendFrame with extra params + RSSI = _RSSI; // restore payload RSSI +} + +//============================================================================= +// sendFrame() - the basic version is used to match the RFM69 prototype so we can extend it +//============================================================================= +// this sendFrame is generally called by the internal RFM69 functions. Simply transfer to our modified version. +void RFM69_ATC::sendFrame(uint8_t toAddress, const void* buffer, uint8_t bufferSize, bool requestACK, bool sendACK) { + sendFrame(toAddress, buffer, bufferSize, requestACK, sendACK, false, 0); // default sendFrame +} + +//============================================================================= +// sendFrame() - the new one with additional parameters. This packages recv'd RSSI with the packet, if required. +//============================================================================= +void RFM69_ATC::sendFrame(uint8_t toAddress, const void* buffer, uint8_t bufferSize, bool requestACK, bool sendACK, bool sendRSSI, int16_t lastRSSI) { + setMode(RF69_MODE_STANDBY); // turn off receiver to prevent reception while filling fifo + while ((readReg(REG_IRQFLAGS1) & RF_IRQFLAGS1_MODEREADY) == 0x00); // wait for ModeReady + writeReg(REG_DIOMAPPING1, RF_DIOMAPPING1_DIO0_00); // DIO0 is "Packet Sent" + + bufferSize += (sendACK && sendRSSI)?1:0; // if sending ACK_RSSI then increase data size by 1 + if (bufferSize > RF69_MAX_DATA_LEN) bufferSize = RF69_MAX_DATA_LEN; + + // write to FIFO + select(); + SPI.transfer(REG_FIFO | 0x80); + SPI.transfer(bufferSize + 3); + SPI.transfer(toAddress); + SPI.transfer(_address); + + // control byte + if (sendACK) { // TomWS1: adding logic to return ACK_RSSI if requested + SPI.transfer(RFM69_CTL_SENDACK | (sendRSSI?RFM69_CTL_RESERVE1:0)); // TomWS1 TODO: Replace with EXT1 + if (sendRSSI) { + SPI.transfer(abs(lastRSSI)); //RSSI dBm is negative expected between [-100 .. -20], convert to positive and pass along as single extra header byte + bufferSize -=1; // account for the extra ACK-RSSI 'data' byte + } + } + else if (requestACK) { // TODO: add logic to request ackRSSI with ACK - this is when both ends of a transmission would dial power down. May not work well for gateways in multi node networks + SPI.transfer(_targetRSSI ? RFM69_CTL_REQACK | RFM69_CTL_RESERVE1 : RFM69_CTL_REQACK); + } + else SPI.transfer(0x00); + + 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); +} + +//============================================================================= +// interruptHook() - gets called by the base class interrupt handler right after the header is fetched. +//============================================================================= +void RFM69_ATC::interruptHook(uint8_t CTLbyte) { + ACK_RSSI_REQUESTED = CTLbyte & RFM69_CTL_RESERVE1; // TomWS1: extract the ACK RSSI request bit (could potentially merge with ACK_REQUESTED) + // TomWS1: now see if this was an ACK with an ACK_RSSI response + if (ACK_RECEIVED && ACK_RSSI_REQUESTED) { + // the next two bytes contain the ACK_RSSI (assuming the datalength is valid) + if (DATALEN >= 1) { + _ackRSSI = -1 * SPI.transfer(0); //rssi was sent as single byte positive value, get the real value by * -1 + DATALEN -= 1; // and compensate data length accordingly + // TomWS1: Now dither transmitLevel value (register update occurs later when transmitting); + if (_targetRSSI != 0) { + // if (_isRFM69HW) { + // if (_ackRSSI < _targetRSSI && _transmitLevel < 51) _transmitLevel++; + // else if (_ackRSSI > _targetRSSI && _transmitLevel > 32) _transmitLevel--; + // } else { + if (_ackRSSI < _targetRSSI && _transmitLevel < 31) { _transmitLevel++; /*Serial.println("\n ======= _transmitLevel ++ ======");*/ } + else if (_ackRSSI > _targetRSSI && _transmitLevel > 0) { _transmitLevel--; /*Serial.println("\n ======= _transmitLevel -- ======");*/ } + //} + } + } + } +} + +//============================================================================= +// receiveBegin() - need to clear out our flag before calling base class. +//============================================================================= +void RFM69_ATC::receiveBegin() { + ACK_RSSI_REQUESTED = 0; + RFM69::receiveBegin(); +} + +//============================================================================= +// setPowerLevel() - outright replacement for base class. Provides finer granularity for RFM69HW. +//============================================================================= +// set output power: 0=min, 31=max (for RFM69W or RFM69CW), 0-31 or 32->51 for RFM69HW (see below) +// this results in a "weaker" transmitted signal, and directly results in a lower RSSI at the receiver +// allows power level selections above 31 (as in original base RFM69 lib) & selects appropriate PA based on the value +// more discussion and details in this forum post: https://lowpowerlab.com/forum/index.php/topic,688.0.html +// void RFM69_ATC::setPowerLevel(uint8_t powerLevel) { + // _transmitLevel = powerLevel; // save this for later in case we do auto power control. + // _powerBoost = (powerLevel >= 50); + // if (!_isRFM69HW || powerLevel < 32) { // use original code without change + // _powerLevel = powerLevel; + // writeReg(REG_PALEVEL, (readReg(REG_PALEVEL) & 0xE0) | (_powerLevel > 31 ? 31 : _powerLevel)); + // } else { + // // the allowable range of power level value, if >31 is: 32 -> 51, where... + // // 32->47 use PA2 only and sets powerLevel register 0-15, + // // 48->49 uses both PAs, and sets powerLevel register 14-15, + // // 50->51 uses both PAs, sets powerBoost, and sets powerLevel register 14-15. + // if (powerLevel < 48) { + // _powerLevel = powerLevel & 0x0f; // just use 4 lower bits when in high power mode + // _PA_Reg = 0x20; + // } else { + // _PA_Reg = 0x60; + // if (powerLevel < 50) { + // _powerLevel = powerLevel - 34; // leaves 14-15 + // } else { + // if (powerLevel > 51) + // powerLevel = 51; // saturate + // _powerLevel = powerLevel - 36; // leaves 14-15 + // } + // } + // writeReg(REG_OCP, (_PA_Reg==0x60) ? RF_OCP_OFF : RF_OCP_ON); + // writeReg(REG_PALEVEL, _powerLevel | _PA_Reg); + // } +// } + +//============================================================================= +// setHighPower() - only set High power bits on RFM69HW IFF the power level is set to MAX. Otherwise it is kept off. +//============================================================================= +// void RFM69_ATC::setHighPower(bool onOff, byte PA_ctl) { + // _isRFM69HW = onOff; + // writeReg(REG_OCP, (_isRFM69HW && PA_ctl==0x60) ? RF_OCP_OFF : RF_OCP_ON); + // if (_isRFM69HW) { //turning ON based on module type + // _powerLevel = readReg(REG_PALEVEL) & 0x1F; // make sure internal value matches reg + // _powerBoost = (PA_ctl == 0x60); + // _PA_Reg = PA_ctl; + // writeReg(REG_PALEVEL, _powerLevel | PA_ctl ); //TomWS1: enable selected P1 & P2 amplifier stages + // } + // else { + // _PA_Reg = RF_PALEVEL_PA0_ON; // TomWS1: save to reflect register value + // writeReg(REG_PALEVEL, RF_PALEVEL_PA0_ON | RF_PALEVEL_PA1_OFF | RF_PALEVEL_PA2_OFF | _powerLevel); //enable P0 only + // } +// } + +//============================================================================= +// ditto from above. +//============================================================================= +// void RFM69_ATC::setHighPowerRegs(bool onOff) { + // if ((0x60 != (readReg(REG_PALEVEL) & 0xe0)) || !_powerBoost) // TomWS1: only set to high power if we are using both PAs... and boost range is requested. + // onOff = false; + // writeReg(REG_TESTPA1, onOff ? 0x5D : 0x55); + // writeReg(REG_TESTPA2, onOff ? 0x7C : 0x70); +// } + +//============================================================================= +// enableAutoPower() - call with target RSSI, use 0 to disable (default), any other value with turn on autotransmit control. +//============================================================================= +// TomWS1: New methods to address autoPower control +void RFM69_ATC::enableAutoPower(int targetRSSI){ // TomWS1: New method to enable/disable auto Power control + _targetRSSI = targetRSSI; // no logic here, just set the value (if non-zero, then enabled), caller's responsibility to use a reasonable value +} + +//============================================================================= +// getAckRSSI() - returns the RSSI value ack'd by the far end. +//============================================================================= +int RFM69_ATC::getAckRSSI(void){ // TomWS1: New method to retrieve the ack'd RSSI (if any) + return (_targetRSSI==0?0:_ackRSSI); +} + +//============================================================================= +// setLNA() - used for power level testing. +//============================================================================= +byte RFM69_ATC::setLNA(byte newReg) { // TomWS1: New method used to disable LNA AGC for testing purposes + byte oldReg; + oldReg = readReg(REG_LNA); + writeReg(REG_LNA, ((newReg & 7) | (oldReg & ~7))); // just control the LNA Gain bits for now + return oldReg; // return the original value in case we need to restore it +} \ No newline at end of file diff --git a/RFM69_ATC.h b/RFM69_ATC.h new file mode 100644 index 0000000..d4b677f --- /dev/null +++ b/RFM69_ATC.h @@ -0,0 +1,70 @@ +// ********************************************************************************** +// Automatic Transmit Power Control class derived from RFM69 library. +// Discussion and details in this forum post: https://lowpowerlab.com/forum/index.php/topic,688.0.html +// ********************************************************************************** +// Copyright Thomas Studwell (2014,2015) +// Adjustments by Felix Rusu, 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 . +// +// 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 +// ********************************************************************************** +#ifndef RFM69_ATC_h +#define RFM69_ATC_h +#include + +#define RFM69_CTL_RESERVE1 0x20 + +class RFM69_ATC: public RFM69 { + public: + static volatile uint8_t ACK_RSSI_REQUESTED; // new flag in CTL byte to request RSSI with ACK (could potentially be merged with ACK_REQUESTED) + + RFM69_ATC(uint8_t slaveSelectPin=RF69_SPI_CS, uint8_t interruptPin=RF69_IRQ_PIN, bool isRFM69HW=false, uint8_t interruptNum=RF69_IRQ_NUM) : + RFM69(slaveSelectPin, interruptPin, isRFM69HW, interruptNum) { + } + + bool initialize(uint8_t freqBand, uint8_t ID, uint8_t networkID=1); + void sendACK(const void* buffer = "", uint8_t bufferSize=0); + //void setHighPower(bool onOFF=true, uint8_t PA_ctl=0x60); //have to call it after initialize for RFM69HW + //void setPowerLevel(uint8_t level); // reduce/increase transmit power level + void enableAutoPower(int targetRSSI=-90); // TWS: New method to enable/disable auto Power control + void setMode(uint8_t mode); // TWS: moved from protected to try to build block()/unblock() wrapper + + int16_t getAckRSSI(void); // TWS: New method to retrieve the ack'd RSSI (if any) + uint8_t setLNA(uint8_t newReg); // TWS: function to control LNA reg for power testing purposes + int16_t _targetRSSI; // if non-zero then this is the desired end point RSSI for our transmission + uint8_t _transmitLevel; // saved powerLevel in case we do auto power adjustment, this value gets dithered + + protected: + void interruptHook(uint8_t CTLbyte); + void sendFrame(uint8_t toAddress, const void* buffer, uint8_t size, bool requestACK=false, bool sendACK=false); // Need this one to match the RFM69 prototype. + void sendFrame(uint8_t toAddress, const void* buffer, uint8_t size, bool requestACK, bool sendACK, bool sendRSSI, int lastRSSI); + void receiveBegin(); + //void setHighPowerRegs(bool onOff); + + int16_t _ackRSSI; // this contains the RSSI our destination Ack'd back to us (if we enabledAutoPower) + //bool _powerBoost; // this controls whether we need to turn on the highpower regs based on the setPowerLevel input + uint8_t _PA_Reg; // saved and derived PA control bits so we don't have to spend time reading back from SPI port +}; + +#endif \ No newline at end of file diff --git a/keywords.txt b/keywords.txt index cda3bd4..7cdd46f 100644 --- a/keywords.txt +++ b/keywords.txt @@ -10,6 +10,7 @@ # Instances (KEYWORD2) ####################################### RFM69 KEYWORD2 +RFM69_ATC KEYWORD2 ####################################### # Methods and Functions (KEYWORD2) @@ -38,6 +39,7 @@ setPowerLevel KEYWORD2 readTemperature KEYWORD2 rcCalibration KEYWORD2 readAllRegs KEYWORD2 +enableAutoPower KEYWORD2 ####################################### # Constants (LITERAL1)