Tapatalk

Low power slave and master code

Low power slave and master code

14

PostNov 28, 2017#1

Guys,

Here's some code to try out. A slave and a master. The slave goes to sleep, and wakes up every 64 seconds, reads VIN and VBAT and sends them to the master.

It's working nicely. However:

The fields cmd and idx in the message frame are not updating correctly. In the slave code, I set cmd to 0xAA (I presume this is a command code, so I set it to 0xAA which the master would look for), but it is received at the master as 2.
I increment idx each time I transmit a message, however, I'm not seeing that on the master side. It looks like these values are being internally over-written somewhere. Maybe I'm using them wrong?

Sample output from master:

Code: Select all

adio initialised successfully. 
Waiting for incoming transmission... 
Incoming message detected... 
------- MSG Dump: START ------- 
Headers: HEX [DEC] 
    Idx: 2 [2] 
    Cmd: 2 [2] 
    Sdx: 0 [0] 
    Src: 1 [1] 
    Dst: 0 [0] 
    Rtr: 0 [0] 
 Length: 4 
C:83:0:E: 
-------- MSG Dump: END -------- 
Battery voltage (mV):3203 
    VIn voltage (mV):14 
Waiting for incoming transmission... 
Incoming message detected... 
------- MSG Dump: START ------- 
Headers: HEX [DEC] 
    Idx: 3 [3] 
    Cmd: 2 [2] 
    Sdx: 0 [0] 
    Src: 1 [1] 
    Dst: 0 [0] 
    Rtr: 0 [0] 
 Length: 4 
C:83:0:15: 
-------- MSG Dump: END -------- 
Battery voltage (mV):3203 
    VIn voltage (mV):21 
Waiting for incoming transmission... 
Incoming message detected... 
------- MSG Dump: START ------- 
Headers: HEX [DEC] 
    Idx: 2 [2] 
    Cmd: 2 [2] 
    Sdx: 0 [0] 
    Src: 1 [1] 
    Dst: 0 [0] 
    Rtr: 0 [0] 
 Length: 4 
C:83:0:15: 
-------- MSG Dump: END -------- 
Battery voltage (mV):3203 
    VIn voltage (mV):21 
Waiting for incoming transmission... 
Incoming message detected... 
------- MSG Dump: START ------- 
Headers: HEX [DEC] 
    Idx: 3 [3] 
    Cmd: 2 [2] 
    Sdx: 0 [0] 
    Src: 1 [1] 
    Dst: 0 [0] 
    Rtr: 0 [0] 
 Length: 4 
C:8A:0:7: 
-------- MSG Dump: END -------- 
Battery voltage (mV):3210 
    VIn voltage (mV):7 
Waiting for incoming transmission... 
Incoming message detected... 
------- MSG Dump: START ------- 
Headers: HEX [DEC] 
    Idx: 6 [6] 
    Cmd: 2 [2] 
    Sdx: 0 [0] 
    Src: 1 [1] 
    Dst: 0 [0] 
    Rtr: 0 [0] 
 Length: 4 
C:83:0:E: 
-------- MSG Dump: END -------- 
Battery voltage (mV):3203 
    VIn voltage (mV):14 
Waiting for incoming transmission... 
Incoming message detected... 
------- MSG Dump: START ------- 
Headers: HEX [DEC] 
    Idx: 7 [7] 
    Cmd: 2 [2] 
    Sdx: 0 [0] 
    Src: 1 [1] 
    Dst: 0 [0] 
    Rtr: 0 [0] 
 Length: 4 
C:7C:0:E: 
-------- MSG Dump: END -------- 
Battery voltage (mV):3196 
    VIn voltage (mV):14 
Waiting for incoming transmission... 
Incoming message detected... 
------- MSG Dump: START ------- 
Headers: HEX [DEC] 
    Idx: 6 [6] 
    Cmd: 2 [2] 
    Sdx: 0 [0] 
    Src: 1 [1] 
    Dst: 0 [0] 
    Rtr: 0 [0] 
 Length: 4 
C:74:0:7: 
-------- MSG Dump: END -------- 
Battery voltage (mV):3188 
    VIn voltage (mV):7 
Waiting for incoming transmission... 
Note idx and cmd above, are incorrect. idx should be incrementing, and cmd should be 0xAA.

Master code:

Code: Select all

#include <T2WhisperNode.h>
#include <LowPower.h>


/* You need to configure the Whisper Node Version */
#define T2_WPN_BOARD T2_WPN_VER_RF69
//define T2_WPN_BOARD T2_WPN_VER_LORA

#if T2_WPN_BOARD == T2_WPN_VER_RF69
  #include <RH_RF69.h>
  RH_RF69 myRadio;
#elif T2_WPN_BOARD == T2_WPN_VER_LORA
  #include <RH_RF95.h>
  RH_RF95 myRadio;
#endif


// flash memory handler object
T2Flash myFlash;


// Radio
uint8_t packetBuffer[(T2_MESSAGE_HEADERS_LEN + T2_MESSAGE_MAX_DATA_LEN)];
#define RADIO_ENCRYPTION_KEY { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F } // Only used by RF69
#define RADIO_FREQUENCY 868.1
#define RADIO_TX_POWER 2
#define HIGH_POWER true


// T2 Message
T2Message myMsg;
#define MASTER_ADDRESS 0x00
#define SLAVE_ADDRESS  0x01


void setup()
{
  Serial.begin(115200);
  pinMode(6,OUTPUT); // for driving the blue LED
  digitalWrite(6,LOW);
  
  // initialise radio
  if(myRadio.init()) {
    myRadio.setFrequency(RADIO_FREQUENCY);
    myRadio.setTxPower(RADIO_TX_POWER, HIGH_POWER); // we're using the high power Talk2 module
    #if T2_WPN_BOARD == T2_WPN_VER_RF69
      uint8_t myRadioEncryptionKey[] = RADIO_ENCRYPTION_KEY;
      myRadio.setEncryptionKey(myRadioEncryptionKey);
    #endif
    Serial.println(F("Radio initialised successfully."));
  }
  displayMessage();
}


void loop()
{
  uint8_t radioBufLen = sizeof(packetBuffer);
  if(myRadio.recv(packetBuffer, &radioBufLen))
  {
    digitalWrite(6,HIGH); // blue led on
    Serial.println(F("Incoming message detected..."));
    myMsg.setSerializedMessage(packetBuffer, radioBufLen);
    // Uncomment below to print every received message, just be careful as
    // delays here can cause messages to be lost.
    myMsg.printMessage();

    // check that the command type is 0xAA (report supply voltages) and that the
    // destination address is 0 (which is the address of the master)
    if(myMsg.dst == MASTER_ADDRESS)
    { 
      // we can process this packet
      uint16_t vBat;
      uint16_t vIn;

      vBat = myMsg.data[0] << 8 | myMsg.data[1];
       vIn = myMsg.data[2] << 8 | myMsg.data[3];

      if(myMsg.src > 0 && myMsg.src <= 8) {
        Serial.print(F("Battery voltage (mV):"));
        Serial.println(vBat);
        Serial.print(F("    VIn voltage (mV):"));
        Serial.println(vIn);
      } else
        Serial.println(F("Illegal slave ID detected in message."));
    } else
      Serial.println(F("Message detected, but not for this device, or un-supported command"));
    displayMessage();
    digitalWrite(6,LOW); // blue led off
  }
}

void displayMessage() {
  Serial.println(F("Waiting for incoming transmission..."));
}
Slave code:

Code: Select all


/*
 * Talk2 Example: Wake periodic, transmit data, and go to sleep.
 *
 * This example demonstrates how you can use the Talk2 Library to transmit
 * data over the air, and power down all peripherals and the radio between 
 * transmissions.
 * 
 * The CPU wakes up every 8 seconds and increments a counter. When the counter
 * has been incremented 8 times (thus, every 64 seconds) the CPU will read
 * the VBAT and VIN supply voltages and send them over the air.
 * When the transmission is complete the CPU powers the radio down and then 
 * puts itself back to sleep.
 * 
 * The Sketch will uses a digitalWrite to blink the LEDs while transmitting
 *
 * This sketch is written specifically for the Talk2 Whisper Node, available
 * from http://talk2.wisen.com.au
 * 
 * Copyright 2017 by Mark Wills (markwills1970@gmail.com)
 *
 * This sketch 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/>.
 *
 */
#include <T2WhisperNode.h>
#include <LowPower.h>


/* You need to configure the Whisper Node Version */
#define T2_WPN_BOARD T2_WPN_VER_RF69
//define T2_WPN_BOARD T2_WPN_VER_LORA

#if T2_WPN_BOARD == T2_WPN_VER_RF69
  #include <RH_RF69.h>
  RH_RF69 myRadio;
#elif T2_WPN_BOARD == T2_WPN_VER_LORA
  #include <RH_RF95.h>
  RH_RF95 myRadio;
#endif


// flash memory handler object
T2Flash myFlash;


// Radio
uint8_t packetBuffer[(T2_MESSAGE_HEADERS_LEN + T2_MESSAGE_MAX_DATA_LEN)];
#define RADIO_ENCRYPTION_KEY { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F } // Only used by RF69
#define RADIO_FREQUENCY 868.1
#define RADIO_TX_POWER 2
#define HIGH_POWER true


// T2 Message
T2Message myMsg;
#define MASTER_ADDRESS 0x00
#define SLAVE_ADDRESS  0x01


/* number of transmissions this slave has sent. This could be used on the Master side  
 *  to determine if the master has missed any transmissions */
uint16_t txCount=0;

/* counter for the number of times we've woken up. when we've woken up 8 times (64 seconds)
 *  we'll read the voltages and transmit them */
uint16_t wakeCount;


void setup()
{
  Serial.begin(115200);
  Serial.println(F("Example: Send data over radio and put radio and CPU to sleep "));
  Serial.println(F("This program sends the battery voltage (VBAT) and the main power"));
  Serial.println(F("supply voltage (VIN) over the air every 64 seconds (approximately)."));
  Serial.println(F("The CPU wakes up every 8 seconds and checks if 64 seconds has"));
  Serial.println(F("has elasped. If not, the CPU, flash, and radio go back to sleep."));
  Serial.println(F("If 64 seconds HAS elapsed, the voltages are checked, and then"));
  Serial.println(F("transmitted over the air, then everything goes back to sleep again.\n"));
  
  // Radio - Initialize the radio and put it to sleep to save energy
  if(myRadio.init()) {
    myRadio.setFrequency(RADIO_FREQUENCY);
    myRadio.setTxPower(RADIO_TX_POWER, HIGH_POWER); // we're using the high power Talk2 module
    #if T2_WPN_BOARD == T2_WPN_VER_RF69
      uint8_t myRadioEncryptionKey[] = RADIO_ENCRYPTION_KEY;
      myRadio.setEncryptionKey(myRadioEncryptionKey);
    #endif
    
    // Flash - We're not using, so just power it down to save energy
    myFlash.init(T2_WPN_FLASH_SPI_CS);
    myFlash.powerDown();
    
    pinMode(6,OUTPUT); // set led pin as an output 
    Serial.println(F("Radio successfully initialised."));
  } else
      Serial.println(F("** Fatal error: Cannot intialise radio. **"));
}



void loop()
{
  // code starts running from here when the CPU wakes up
  
  if((++wakeCount)%8==0) {
    // ~64 seconds has elapsed... time to do some work
    Serial.println(F("I'm awake!"));
    transmitVoltages();
  } else
      Serial.println("Woke up. Nothing to do!");
  
  // put the radio and the CPU back to sleep
  Serial.println(F("Going back to sleep! ZZZZzzzz....\n"));
  Serial.flush();
  myRadio.sleep();
  LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF); 
}



void transmitVoltages() {
  uint16_t vBat, vIn;

  
  digitalWrite(6,HIGH); // led on
  
  // read the battery and supply voltages
  vBat=T2Utils::readVoltage(T2_WPN_VBAT_VOLTAGE, T2_WPN_VBAT_CONTROL);
   vIn=T2Utils::readVoltage(T2_WPN_VIN_VOLTAGE, T2_WPN_VIN_CONTROL);
  
  Serial.print(F("Battery voltage (mV):"));
  Serial.println(vBat);
  Serial.print(F(" Supply voltage (mV):"));
  Serial.println(vIn);
  
  uint8_t packetBufferLen = 0;
  
  // prepare a message to transmit over the air
  myMsg.cmd = 0xAA;
  myMsg.idx = txCount++;
  myMsg.src = SLAVE_ADDRESS;
  myMsg.dst = MASTER_ADDRESS;
  myMsg.data[0] = vBat >> 8;  // most significant 8 bits
  myMsg.data[1] = vBat;       // lower 8 bits
  myMsg.data[2] = vIn >> 8;   // most significant 8 bits
  myMsg.data[3] = vIn;        // lower 8 bits
  myMsg.len = 4; // set number of data items in message
  
  // Encode Message into a radio packet and get the full length
  myMsg.getSerializedMessage(packetBuffer, &packetBufferLen);
  
  // Send it (this turns the radio on automatically)
  myRadio.send(packetBuffer, packetBufferLen);
  bool success=myRadio.waitPacketSent(1000);

  if(success)
    Serial.println(F("Message sent succesfully."));
  else
    Serial.println(F("Timeout while sending message."));
  
  digitalWrite(6,LOW); // led off
}

1885
1885

PostNov 28, 2017#2

Hi Mark,

Just to clarify, the T2Message myMsg is just a payload wrapper we decided to implement, so users could see how a data structure can be used to create a simple protocol. The format tries to implement meaningful headers and at the same time optimizing the message for low-power by using limited payload.

About the headers, please have a look on the definition (T2Message.cpp):

Code: Select all

  this->idx = (msg[0] & 0x1C) >> 2;
  this->cmd = msg[0] & 0x03;
  this->sdx = msg[1];
  this->src = msg[2];
  this->dst = msg[3];
  this->rtr = (msg[4] & 0x80) >> 7;
  this->len = msg[4] & 0x7F;
If you have a look above, the idx is 3 bits long and cmd is 2 bits long. In other words, the maximum value for those are:

idx: 7 (int)
cmd: 3 (int)

All other bits from idx and cmd are "chopped out", explaining the behavior. The other fields: sdx, src and dst are all 8 bits long, up to 255 (int)

Just for curiosity: The headers have been formatted like that so they are compatible with CAN 2.0b 29bits ID (3 + 2 + 8 + 8 + 8).

Saying that, you don't have to use the Talk2 Message format if you feel more comfortable. Just use the RadioHead function myRadio.send(packetBuffer, packetBufferLen) works with any byte_array (see documentation: http://www.airspayce.com/mikem/arduino/ ... _RF69.html)

313

PostDec 03, 2017#3

// flash memory handler object
T2Flash myFlash;

// Flash - We're not using, so just power it down to save energy    
myFlash.init(T2_WPN_FLASH_SPI_CS);     
myFlash.powerDown();
any idea how much more power this helps save and if this is set do we have to do anything to reenable it if we need it later? I ask as I vaguely recall seeing a post about Flash at some point in the past and before I add this into my code figured I'd verify and document it so I remember if I have to "fix" something later.

I have been running similar code, as Mark posted, for about 55-60 days right now off a single C battery and it is down to to 1.2170 and 1.2590 on each of the 2 devices at this point.  I caught a blog post awhile back about running off a 3V cell battery for a year, so I'm still missing something in my code to help save power and this may be part of it.  Still better than 10 days with first round of code with no power saving in it!  And I do realize the board runs a bit more efficient off 3V than 1.5V, but I wouldn't assume 10 months better!  :)

Next round of tests I'll have to see what my actual power pull is when in sleep and do different tests, but I want to finish the current round of tests before I do that.  (also current tests are on the 0.3V board, not 1.0V board and trying not to intermix them for now)

1885
1885

PostDec 04, 2017#4

Hi xnih13,

The SPI Flash memory does not consume much. Would need to check the datasheet, but it should be less than 10uA when stand-by and under 1uA in sleeping mode. You do need to issue a "Wake-up" command once you have put the SPI Flash to sleep.

Now, if you're trying to reach the lowest power consumption, you do need to have a Multimeter (Ampmeter) in series with your power supply to measure the consumption.

The ideal is to have a very simple code, like the example "PowerDown.wakePeriodic" and measure consumption from there. Also, it's important to always measure the current when connecting an additional device or sensor to the board. Put everything to sleep and check the consumption... using the original measurement as base-line you'll be able to determine the component consumption.

Monitoring the consumption and doing little changes will point to the part of code/hardware which can be improved.

14

PostAug 14, 2018#5

For what it's worth guys, I set up two whisper nodes, one a transmitter, running on two Alkaline AA cells (running the code above), and one a receiver, running on a USB power supply. The transmitter, running the code above, has been running continuously since 28th November 2017, and the battery voltage (measured bu the Whisper Node is still 3075mV !).

FULL DISCLOSURE: The transmitter is inside (nice warm, stable environment) and on a low transmission power. However, I am still impressed. We're looking at way over two years the way this thing is going!