Tuesday, February 24, 2015

Arduino Wireless Temperature Master

Part 2 - The Master 
Part 1 - The Nodes

In this post - I'm going to look at the wireless node master that will handle the input from the 3 wireless nodes I have around the house (from the previous post) using NRF24L01+ 2.4ghz transceivers.

The wiring of the Arduino for this part is relatively simple as I'm going to use a full size Arduino Uno.

Wiring


The NRF24L01+ wired to the Aruino Uno

Uno NRF24L01+
13 SCLK
12 MOSI
11 MISO
7 CSN
6 CE
2 IRQ
3.3v Vcc
GND GND

The wiring here differs slightly in the pin numbering from the previous project because I'm leaving room on Pin 8 for a 433Mhz receiver (for another stage of the project). I'm also using Pin 2 connected to the IRQ pin of the transceiver - this will allow us to use an interrupt to process the data as soon as the transceiver receives it, without having to poll the unit constantly.

Interacting with the RF24 Module

My node master code is on Github.

I use the RF24 Library for my Arduino code. Download the Library and include it in your code.

The first thing I wanted to do was figure out what information I wanted to transmit. Basically, I wanted to get the temperature the node was reporting, the internal voltage, the battery voltage, the uptime of node, and the address of the node. To do this, I build a struct data type on both the nodes and the master - this makes it easy to transmit a complete package of information: Structs.h
#ifndef structs_h
#define structs_h

typedef struct {
  volatile unsigned long _micros;
  volatile float temp;
  volatile long Vcc;
  volatile int Vbatt;
  volatile byte address;
  volatile long uptime;
} dataPacket;
#endif

The volatile keyword in the data structure means that when the Arduino accesses that variable, it always uses the value in RAM. This allows the value to be updated during an interrupt, and be reflected no matter where or when the value is read.

Now, in the main program, I import all the relevant packages
#include "SPI.h"
#include "nRF24L01.h"
#include "RF24.h"
#include "printf.h"
#include "structs.h"

I store the addresses for the nodes in an array, and create the node variables I will store the info in:

byte address[][6] = {"0node","1node","2node","3node"};
// master radio in address[0]
byte master = 0;
int myAddress;

dataPacket dataPackage;
dataPacket node1;
dataPacket node2;
dataPacket node3;
dataPacket empty;

Next - initialize the radio
const int rPin1 = 6; // radio pins, SPI plus 7,6
const int rPin2 = 7; 
RF24 radio(rPin1,rPin2);

In setup():
1. Begin the radio interface
2. Disable auto acknowledge - this means that the sending and receiving radios won't acknowledge packets. It will increase packet loss but save battery life
3. Open up read ports for each address - this will allow each node to write to this unit
4. Open up a writing port for the master address - this isn't being used currently but would allow us to talk back to each node
5. Power the radio on
6. Enable the listening interface
7. Print the radio details for debugging 8. Enable the pin mode interrupt on Pin 0 (digital pin 2 on the Uno) - when this pin is pulled low, the radio has received a packet and it will call the check_radio function

void setup(){
  #if defined DEBUG
    Serial.begin(38400);
  #endif
  Serial.println("Start");
  printf_begin();
  radio.begin();
  radio.setAutoAck(false);
  
  // open pipes for communication
  for (int i=1; i < sizeof(address) - 1; i++) {
    radio.openReadingPipe(i, address[i]);
  }
  radio.openWritingPipe(address[master]);
  // power up radio
  // this takes up to 5ms
  radio.powerUp();
  delay(10);
  radio.startListening();
  
  radio.printDetails();
  
  //interrupt on pin 2 (interrupt 0)
  // Will be pulled LOW when receiving
  attachInterrupt(0, check_radio, LOW);
  
}

The check_radio function is called whenever an interrupt is received on Digital pin 2 - which means the radio has done something.

  1. It checks to see what happened - packet sent, or received? This will be received in our case.
  2. It reads the data in to the dataPackage variable - this contains all the information we want from the node.
  3. It parses the dataPackage, and depending on the address puts it in one of 3 node variables.

void check_radio(void)       
{
  #ifdef DEBUG
    Serial.println("IRQ received");
  #endif
  
  bool tx,fail,rx;
  radio.whatHappened(tx,fail,rx);    // What happened?
  
  if ( tx ) {                        // Have we successfully transmitted?
      // Do nothing
  }
  
  if ( fail ) {                      // Have we failed to transmit?
      printf("Failure\n");
  }
  
  if ( rx || radio.available()){     // Did we receive a message?
    radio.read(&dataPackage, sizeof(dataPackage));
    unsigned long time = millis() / 1000;
    if (dataPackage.address == 1) {
      node1 = dataPackage;
    } else if (dataPackage.address == 2) {
      node2 = dataPackage;
    } else if (dataPackage.address == 3) {
      node3 = dataPackage;
    }
  }
}

Currently, as this is just a proof of concept/debug setup - the loop() function simply prints the latest information it has every minute.
void printInfo(dataPacket data) {
  Serial.print("Up: ");Serial.print(data.uptime);Serial.print("s, ");
    Serial.print(data._micros);Serial.print(", ");
    Serial.print(data.temp);Serial.print(", ");
    Serial.print(dataPackage.Vbatt);Serial.print(", ");
    Serial.print(data.Vcc);Serial.print(", ");
    Serial.println(data.address);
    return;
}

void loop() {
  startTime = millis()/1000;
  int elapsed = startTime - endTime;
  if (elapsed == interval) {
    Serial.print("heartbeat (s): ");
    Serial.println(startTime);
    endTime = startTime;
    
    // every interval, display current information
    if (node1.address) {
      Serial.print("Node 1 -  ");
      //Serial.println(time);
      printInfo(node1);
    }
    if (node2.address) {
      Serial.print("Node 2 -  ");
      //Serial.println(time);
      printInfo(node2);
    }
  }
}

This gives us this output every minute:
Current information 52140
Node 1 - Up: 3320s, 34056211, 13.00, 7.82, 5.00, 1
Node 2 - Up: 3231s, 24546830, 21.50, 10.04, 5.02, 2

The next step is going to be to clean up the interface, and add in a 433Mhz receiver to receive from a LaCrosse TX9U wireless weather station.

Programming an Arduino Mini with an Arduino Uno

The Arduino Pro Mini is a fantastic bit of kit - but it lacks an on-board USB driver which means programming it has to be done through a serial interface.

Most guides use an FTDI cable/board.
Which is probably the easiest way to do it - however if you don't have one handy you can also use another Arduino you might have laying around. In my case, I had an Arduino Uno.

Wiring

Wiring the Mini to the Uno


Uno Pro Mini
RX RX1
TX TX0
GND GND
5v Vcc

This tripped me up a bit originally - the Uno RX goes to the Mini RX1 pin, and the Uno TX goes to the Mini TX0 pin. It's opposite what you might expect - except that when using the Uno as a programming interface you are bypassing the ATMega328 on the Uno and essentially the Mini becomes the ATMega that the USB interface uses. Which means you want the RX/TX pins to line up the same.

From here - you have two options for programming the Mini. In both cases - I had to manually press the reset button on the Mini as soon as the upload started so the bootloader was in the proper state to accept new programming. You can wire the Uno RESET to the Mini's BLK pin and the IDE is supposed to be able to reset the Mini on demand - but I wasn't able to get it working.

Option 1

Remove the ATMega328 chip from the Uno board. When you upload the sketch, it will go directly to the Mini with no problems.

Option 2

If you don't want to remove the ATMega chip from your board, or you have an SMD Arduino Uno where you can't remove the chip, you can bypass it. To do so:
1. Unplug the Uno from USB
2. Hold the reset button down on the Uno
3. Plug in the USB cable to the Uno, keeping the reset button down
4. Press upload in the Arduino IDE
5. Once upload is complete, release the reset switch.

I like option 2 because there is less risk of damaging the Uno's ATMega328 pins when removing and reinstalling the chip.

Monday, February 23, 2015

Arduino Wireless Temperature Nodes

I've started working on a wireless network of temperature nodes - using a cheap 2.4Ghz transceiver, an arduino mini, and a cheap DS18B20 digital one-wire temperature sensor. The wireless nodes will run on batteries, so I want to reduce power consumption as much as possible.

Currently, I'm running 3 wireless nodes, communicating back to a main Arduino. In the near future I'll be building a logging interface so that I can track everything.

I selected the Arduino Mini because I wanted something small enough to go directly on to a breadboard, and the Mini was cheap from Deal Extreme - cheaper in . Total cost breakdown per node is:
1x Arduino Mini - $4.39
1x Small 400 tie breadboard - $3.21
1x DS18B20 Temperature Sensor - $1.99
1x NRF24L01+ 2.4Ghz transceiver - $2.22 (not pictured)
2x 1MOhm, 1x 4.7kOhm resistor - $0.02
1x 2AA Battery Holder - $1.61
3x 1N4001 Diode - $0.45 (not pictured)

Total: $13.89

I really like the Arduino Mini for its size. The 5v version I got is pretty much identical to the larger Arduino Unos - with the exception that they don't have a USB interface. You'll need an FTDI USB Cable, or another Arduino to program it. (I used an arduino uno - pictured - to program it. You can either remove the ATMega from the Arduino Uno, or just hold the reset button down while plugging the USB cable in and for the entire time you upload the sketch)
The Mini in front of an Arduino Uno

Now - for the wiring of the nodes. This is a Fritzing sketch that outlines how the Mini is wired, before adding the NRF24L01+ module:
Bare schematic with just the mini, and the temperature sensor

The top power rail is GND/5v, the bottom power rail is Battery v/GND. I'm using 2x 3.7v Trustfire Li-Ion batteries with 900mAh power. This is because 2x standard 1.5v batteries wouldn't give me enough voltage to run the 5v arduino. One of the nodes is running with a 4 battery pack though - and it's working just fine.

From the left side - there are 2 1M Ohm resistors, going from the Battery to ground - this acts as a voltage divider - reducing the voltage by half and going in to Analog 0. This is so that I can measure the battery voltage, which will be over the 5v that the Arduino can handle. The max voltage of my li-ion batteries is 4.25v, so with 2 of them and the voltage divider - the highest voltage the Arduino will see on this input is 4.25v.

I use 1M Ohm resistors because the higher the resistor value, the lower the constant drain on the batteries is. 1M ohm is the highest I had on hand - so that will give me a current draw of about 3 µA (0.003 mA) for this circuit.

Next to the left is the DS18B20 temperature sensor. It is wired to the Arduino's digital pin 4 - with a 4.7k Ohm resitor on the data pin to ground. This is a great little sensor - capable of making measurements in 0.5C increments in as little as 90ms.

The two orange wires from D9 and D5 are optional address select pins. This allows me to use the same code on all the nodes, and just connect different pins to ground to choose a different address.

This is what it looks like in the real world - minus the battery pack:

The Mini all wired up, minus the 3 diodes, the 2.4Ghz transceiver, and the battery case
As it stands here - this will let us measure the temperature. However, we still need to add a transmitter.
NRF24L01+

I chose the NRF24L01 partially because it was cheap, but also because it's very low power. When the radio is powered down it consumes about 1 µA - and only 9mA when actively transmitting. The only problem is, it runs on ~3.3v. The data pins can be 5v, but it has to be powered with ~3.3v (actually 1.9v-3.6v). With the Arduino mini - there is no simple 3.3v output like on the larger Uno. I also didn't have an 3.3v step down converters on hand - but I did have several 1N4001 diodes - which have about a ~0.8v drop across them. Wiring up 3 of them in serial means that a 5v input is dropped to about 3v (the voltage drop is dependent on current, and the NRF24L01 is fairly low current so there is less voltage drop). This isn't ideal from a power consumption standpoint - you'd be better served with an efficient converter.
Note the addition of the 3 diodes

The NRF24L01 uses the SPI interface - plus 2 digital pins and are connected as follows.

The schematic is ugly, but there isn't a good way to get this module on a breadboard due to the layout of the pins. You'll need female->male jumper cables.
Wired up with the NRF24L01+

  MISO -> 12
  MOSI -> 11
  SCK -> 13

  CSN -> 8
  CE -> 7

  Vcc -> End of the diode chain
  GND -> GND

Next Steps - Programming the Node and Power consumption

As mentioned above, power consumption is the critical component here. This doesn't do much if you have to replace the batteries in it every few days.

There are a few things to note:

1. The Arduino has an LED wired directly to the power rail, which means it's consuming power 100% of the time. This increases the total usage of the board considerably.

and

2. The Arduino mini, just running idle consumes about 50mA of power (measured from a multimeter). This means, if you have a 900mAh battery you'll get ~18 hours of run time before they are empty. 

The solution to the first point is simple - I removed the power LED from the board using a pair of angle clippers. No LED, no power loss.

The solution to the second point is a bit more complicated. In broad terms - we want to put the arduino in to a deep sleep state any time we are not actively going to be reading the temperature and transmitting it to the base station. In this low power state the arduino consumes several orders of magnitude less power. Some quick research shows that you can get power consumption down to about 1.7µA.

Looking at our various components -

DS18B20

At default precision can take upwards of 700ms to read and return the temperature. The built in libraries assume this time and block the arduino from doing anything else during that time. This means 700-800ms of 50mA power usage.

The data sheet however says that with 9 bit precision, a read only takes 90ms - which uses 12% of the power. Further to that, we can send the command asynchronously - which lets us wake up just long enough to send the temperature read command, sleep for the next 70ms, then wake up again to read the value.

NRF24L01

According to the datasheet - Automatically enters a standby mode, which consumes 26 µA. Pretty good - however it also has a power down mode which consumes 900nA (0.9 µA, or 0.0009mA). So, the idea here would be to power down the radio until we need it. Powering on the radio takes ~1.5ms, so we can send the power on command, and then sleep for the next 1.5ms.

Together, it looks something like this, during the loop in the Arduino:

1. Wake up arduino
2. Send T command to DS18B20
3. Send power on command to NRF24L01
4. Low power Sleep for 90ms (to give the radio and sensor time to power up)
5. Read temperature from DS18B20
6. Assemble broadcast packet
7. Transmit packet
8. Power down radio
9. Sleep arduino for X minutes

Power usage here should be a few mA for just a few milliseconds every X minutes. I'd need an oscilloscope to do any real job of measuring the power usage but hopefully once I go through a set of batteries I'll have a better idea.

I use the Jeelib Sleepy Library to make it easy to run low power timers.

The entire code can be found on Github.

In the next iteration I will likely only power on the radio after 88ms sleep has elapsed from the temperature read - that'll save ~88ms of 26 µA usage. Every little bit counts!

For absolute power savings - you'll want a bare bones ATMega 328 chip - and provide regulated 3.3v power with a high efficiency power regulator. You could use the internal clock and get power usage down incredibly low.


Search.

Google