Using Sparrow v3 and Devicehub for indoor and outdoor monitoring

Marin Alexandru-Gabriel

About the project

When it comes to Internet of Things, Wireless Sensor Nodes are one of the most popular devices. They have small dimensions, low power consumption and may come equipped with lots of sensors, in accordance with one's needs.

We are presenting here our own Wireless Sensor Node solution for both indoor and outdoor monitoring: Sparrow v3. It can be used for temperature, light and relative humidity monitoring, being capable of many years of running if proper duty-cycling and power saving techniques are used.

In order to be able to see the data collected by our WSNs in real time, we used Devicehub.net. It was very useful to us not only because we could see how the monitored parameters evolved in real time, but it also stored our data for a period long enough to be able to see some trends.

More about Sparrow v3

Sparrow v3 is a WSN equipped with three types of sensors: temperature, relative humidity and ambient light. It has an Atmega128RFA1 micro controller, which incorporates a 2.4GHz radio transceiver, compatible with IEEE standard 802.15.4, over which the popular high-level communication protocol ZigBee is implemented. However, ZigBee was not used in our project.

ATmega128RFA1 is an 8-bit architecture micro controller. It has low power consumption (starting from 250nA while in deep sleep and up to 18,6mA when transmitting data), but still offers high computational performance (almost 1MIPS/MHz). On memory terms, it has 16KB of Flash for code download, 4KB of EEPROM and 16KB of RAM. Moreover, ATmega128RFA1 operates at CPU speeds up to 16 MHZ and at voltages between 1.8V and 3.6V.

<imgcaption sparrowv3 | Sparrow v3.2 Wireless Sensor Node> </imgcaption>

Adding the Sparrow Board to the Arduino IDE

  1. Download Arduino
  2. Download and unzip the patch in a separate folder
  3. Locate the installation folder of Arduino on your drive (for Windows it's usually Program Files)
  4. Copy in Arduino\hardware\arduino\cores the contents of the folder sparrow_patch\Arduino\hardware\arduino\cores\sparrow
  5. Replace the file Arduino\hardware\arduino\boards.txt with sparrow_patch\Arduino\hardware\arduino\boards.txt
  6. Copy in Arduino\hardware\arduino\variants the folder named sparrow from sparrow_patch\Arduino\hardware\arduino\variants
  7. Restart the Arduino IDE

After a successful patch, you should be able to select a new board named Sparrow from the Tools>Board menu. Also select the appropriate COM port from the Tools>Port menu You're all set up!

Optional Step: If Your Nodes Don't Come With a Preinstalled Bootloader

The binary will be installed on flash memory through a bootloader. Most probably Sparrow v3 will come with the bootloader already installed, but in case it won't or in case it somehow gets erased, one will need to install it. You can download the bootloader from here.

In order to install the bootloader, a flash programmer will be needed (we used AVRISP mkII). We then installed AtmelStudio and used its built in Flash Programmer utility (Tools→Device Programming in AtmelStudio menu). Fusebits: EXT 0XFE, HI 0xD0, LO 0xF7

Simple test program

We'll now check that everything is alright with our setup by writing a simple test program that blinks a led on Sparrow:

1. Create a new Arduino IDE project (File→New) and copy the code below

2. In Tools→Programmer choose USBasp

3. In Tools→Board select Sparrow

4. From Tools→Serial Port choose the COM port to which the node was connected

5. Compile and upload the binary, using Upload button

#include <util/delay.h>
 
int main(void)
{
    // Serial needs interrupts.	
    sei();
 
    DDRB = (1<<PB5);
    PORTB = 0x00;
 
    // Print a message on the Serial interface
    Serial.begin(9600);
    Serial.println("Demo of how to use Serial and blink LEDs");
    Serial.end();
 
    // Make the LED blink
    while(1)
    {
      // Change the LED's state
      PORTB ^= (1<<PB5);
      // Wait 1 sec
      _delay_ms(1000);
    }
 
    return 0;
}

SHT21 library

There is one more thing that we must take care of before we can start playing with our WSN monitoring project. We'll need a library for reading values from SHT21, our relative humidity and temperature sensor, connected to the micro controller through an I2C interface. You'll need to use the files stored in this archive in the following way:

1. In folder Arduino\libraries create a new folder, named sht21

2. Copy the files from the attached archive in it

After this it will be possible to import sht21.h header into a project, as you'll see below.

NOTE: If Arduino IDE was already started, a restart will be needed.

Monitoring project

The topology of our Wireless Sensor Network is a simple one, where a single node is the gateway one and the rest are sending data to it. The gateway node is the only one that is listening for packages, there is no communication between non-gateway nodes.

The gateway waits to receive a complete package from one of the other nodes. This event is represented by interrupt TRX24_RX_END, the received package being parsed in the handler of this interrupt. The gateway writes the parsed data through the serial interface, then waits for the next package.

All non-gateway nodes are duty-cycling, in order to consume as less energy as possible. After each package sending, every non-gateway node enters in a deep sleep state, where all sensors and micro controller peripherals are deactivated. For waking up from these deep sleep state, MAC symbol counter is used together with SCNT_CMP1_vect interrupt. This interrupt is triggered when Symbol Counter Register has the same value as Symbol Counter Output Compare Register, the latter being previously initialized for the sleep period wanted.

You can see below a representation of the topology we've used, followed by our source code. Multiple nodes are sending the data they've read from their sensors towards the gateway node, which further sends them through serial interface towards a Raspberry Pi board, on which a Devicehub mqtt script is running. The Raspberry Pi board may be connected to Internet either by an Ethernet cable or by a Wifi adapter, which is further connecting to a Wifi network.

<imgcaption sparrowv3_topology | Monitoring topology> </imgcaption>

#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#define F_CPU 16000000UL
#include <util/delay.h>
#include <stdio.h>
#include "sht21.h"
 
#define _DEBUG_ 0
 
#define TRX_FRAME_BUFFER(index) (*(volatile uint8_t *)(0x180 + (index)))
 
// Modify these addresses accordingly before uploading the code on each node
uint8_t nod1_address __attribute__((section(".data"))) = 1;
uint8_t node_address __attribute__((section(".data"))) = 1;
 
short light, voltage;
float temp, humid;
byte seq = 0;
 
// Interrupt triggered when the Symbol Counter reaches to 0.
ISR(SCNT_CMP1_vect)
{
  //Do nothing
}
 
uint32_t symbol_threshold = 0x00000000;
void initializeSymbolCounter()
{
  // enable asynchronous mode, with external oscillator (32.768kHz in our case)
  ASSR |= _BV(AS2);
 
  SCOCR1HH = (symbol_threshold >> 24);
  SCOCR1HL = (symbol_threshold & 0x00ff0000) >> 16;
  SCOCR1LH = (symbol_threshold & 0x0000ff00) >>  8;
  SCOCR1LL = (symbol_threshold & 0x000000ff);
  SCCR0 = _BV(SCEN);
  SCCNTHH = 0x00;
  SCCNTHL = 0x00;
  SCCNTLH = 0x00;
  SCCNTLL = 0x00;
 
  while (SCSR & _BV(SCBSY));
  // enable compare match 1 IRQ
  SCIRQM = _BV(IRQMCP1);
}
 
void delaySymbolCounter(uint8_t seconds)
{
  symbol_threshold += (((uint32_t)seconds) * 62500);
 
  SCOCR1HH = (symbol_threshold >> 24);
  SCOCR1HL = (symbol_threshold & 0x00ff0000) >> 16;
  SCOCR1LH = (symbol_threshold & 0x0000ff00) >>  8;
  SCOCR1LL = (symbol_threshold & 0x000000ff);
 
  while (SCSR & _BV(SCBSY));
}
 
void setState(uint8_t state)
{
  TRX_STATE = CMD_FORCE_TRX_OFF;
  TRX_STATE = state;
  while (state != TRX_STATUS_struct.trx_status);
}
 
// send a short frame with: current node id and the values read from the sensors.
void sendFrame()
{
  setState(CMD_PLL_ON);
 
  TRX_FRAME_BUFFER(0) = 9;	//length - minimum length is 3
  TRX_FRAME_BUFFER(1) = node_address;
  TRX_FRAME_BUFFER(2) = seq++;
  TRX_FRAME_BUFFER(3) = (byte)temp;
  TRX_FRAME_BUFFER(4) = (byte)humid;
  TRX_FRAME_BUFFER(5) = (byte)light;
  TRX_FRAME_BUFFER(6) = (byte)voltage;
 
  // start transmission
  TRX_STATE = CMD_TX_START;
}
 
volatile byte receivedId = 0;
volatile byte receivedS;
volatile byte receivedT;
volatile byte receivedH;
volatile byte receivedL;
volatile byte receivedV;
 
// Interrupt triggered after detecting the end of a transmission
// Here we parse the received data
ISR(TRX24_RX_END_vect)
{
  receivedId = TRX_FRAME_BUFFER(0);
 
  PORTB ^= (1<<PB5);
 
  receivedS = TRX_FRAME_BUFFER(1);
  receivedT = TRX_FRAME_BUFFER(2);
  receivedH = TRX_FRAME_BUFFER(3);
  receivedL = TRX_FRAME_BUFFER(4);
  receivedV = TRX_FRAME_BUFFER(5);
 
  setState(CMD_RX_ON);
}
 
void rfInit(void)
{
  setState(CMD_TRX_OFF);
  IRQ_STATUS = 0xff;
  IRQ_MASK_struct.rx_end_en = 1;
}
 
int main(void)
{	
  sei();
 
  if (node_address == 0)
  {
    DDRB |= _BV(PD0);
    PORTB = 0x00;
  }
  else
  {
    DDRE = 0xff;
    PORTE = 0xff;
    PRR0 &= ~_BV(PRADC);
    initializeSymbolCounter();
  }
 
  rfInit();
 
  // Init UART for node 0 (the gateway)
  if (node_address == 0)
    Serial.begin(9600);
 
  while(1)
  {
    // Node 0 is the gateway
    if (node_address != 0)
    {
      // Set symbol counter for 1 second
      delaySymbolCounter(1);
 
      // Start sensors
      DDRE |= _BV(PE7);
      PORTE &= ~_BV(PE7);
 
      // No power reduction for ADC
      PRR0 &= ~_BV(PRADC);
 
      // Init twi
      twiInit();
 
      // Wait for sensor startup
      _delay_ms(10);
 
      // Read data from sht21 sensor (humid and temp)      
      uint16_t value16temp = SHTReadValue(0xE3);
      TWCR = 0;
      uint16_t value16humid = SHTReadValue(0xE5);
      TWCR = 0;
 
      // Turn sensors off
      DDRE &= ~_BV(PE7);
      PORTE &= ~_BV(PE7);
 
      // Adjust sht21 readed values
      temp = ((float)value16temp) / 374.23 - 46.85;
      humid = ((float) value16humid) / 524.288 - 6;
 
      // Read voltage value from PF0. For Atmega128RFA1 PF0 is an input for ADC0
      // Enable ADC and set ADC prescalar to 128 - 125KHz sample rate @ 16MHz
      ADCSRA = _BV(ADEN) | _BV(ADPS0) | _BV(ADPS2) | _BV(ADPS1); 
      // Left adjust ADC result to allow easy 8 bit reading and Set ADC reference to AVCC
      ADMUX = _BV(ADLAR) | _BV(REFS0);
      // Poll to see if AVDD has been powered-up.
      loop_until_bit_is_set(ADCSRB, AVDDOK);
      // Start the conversion
      ADCSRA |= _BV(ADSC);
      // Poll to see if A/D conversion is completed
      loop_until_bit_is_set(ADCSRA, ADIF);
      voltage = ADCH;
 
      // Read light value from PF2. For Atmega128RFA1 PF2 is an input for ADC2
      // Left adjust ADC result to allow easy 8 bit reading; Set ADC reference to AVCC; Select the input from ADC2
      ADMUX = _BV(ADLAR) | _BV(REFS0) | _BV(MUX1);
      // Poll to see if AVDD has been powered-up.
      loop_until_bit_is_set(ADCSRB, AVDDOK);
      // Start the conversion
      ADCSRA |= _BV(ADSC);
      // Poll to see if A/D conversion is completed
      loop_until_bit_is_set(ADCSRA, ADIF);
      light = ADCH;
 
      // TWI and ADC power reduction; reset ADCSRA
      ADCSRA = 0;
      PRR0 |= _BV(PRADC);
      PRR0 |= _BV(PRTWI);
 
#if _DEBUG_
      // Send data on the serial port. Useful for debugging non-gateway nodes
      // NOTE: I observed that you can't use a single Serial.begin, at the beggining of main function,
      // and reuse that Serial after waking up from sleep. Junk is printed on the serial in that case.
      // So I use a Serial.begin each time I want to print something and a Serial.end after I finished.
      Serial.begin(9600);
      Serial.print("humid, temp, light, voltage: ");
      Serial.print((byte)humid);
      Serial.print(", ");
      Serial.print((byte)temp);
      Serial.print(", ");
      Serial.print((byte)light);
      Serial.print(", ");
      Serial.println((byte)voltage);
      Serial.flush();
      Serial.end();
#endif
 
      // Wake transceiver; Send data through the transceiver to a listening gateway
      TRXPR &= ~_BV(SLPTR);
      sendFrame();
 
      // Wait for transmisson complete
      while(TRX_STATUS_struct.trx_status == BUSY_TX);
        _delay_us(100);
 
      // Put transceiver down
      setState(CMD_TRX_OFF);
      TRXPR = 1 << SLPTR;
 
      // Sleep and wait to be waken when the symbol counter interrupt is triggered
      set_sleep_mode(SLEEP_MODE_PWR_SAVE);
      sleep_enable();
      sleep_cpu();
      sleep_disable();
    }
    else
    {
      setState(CMD_RX_ON);
      // Do nothing while you have not received a new data package
      while (receivedId == 0) asm("nop");
 
      //send data on the serial port
      Serial.print(receivedId);
      Serial.print(":");
      Serial.print(TST_RX_LENGTH_struct.rx_length);
      Serial.print(":");
      Serial.print(receivedS);
      Serial.print(":");
      Serial.print(receivedT);
      Serial.print(":");
      Serial.print(receivedL);
      Serial.print(":");
      Serial.print(receivedH);
      Serial.print(":");
      Serial.println(receivedV);
 
      // Blink a Led, for debugging purposes
      PORTB ^= (1<<PB5);
 
      // Reset receivedId
      receivedId = 0;
    }
  }
}

Real time monitoring with Devicehub

Below you can see the Devicehub API we've used to monitor the parameters read by our Wireless Sensor Nodes. For simplicity, the code below is an example for only one monitoring node. If you'll use multiple nodes you'll need to add sensors / devices on Devicehub for each of them, then add code in the script for each of them. Of course, you'll also need to add your own PROJECT_ID, DEVICE_UUID and API_KEY as shown in the script below.

One thing which should be mentioned is that we've empirically saw that the voltage ADC reported value is directly proportional in terms of volts with 1/71, hence we are dividing the ADC value to 71.

#!/usr/bin/env python
 
__author__ = 'Alex Marin'
import random
import time
from time import sleep
 
from devicehub.devicehub import Sensor, Actuator, Device, Project
 
import serial
 
ser = serial.Serial('COM8', 9600, timeout=36000)
 
PROJECT_ID = '4738'
DEVICE_UUID = '7c2068f3-4427-4085-aeb0-22aad30f268e'
API_KEY = '08a04870-4a47-4578-b290-4e49c5202e88'
 
project = Project(PROJECT_ID, persistent=False)
device = Device(project, DEVICE_UUID, API_KEY)
 
temp_s = Sensor(Sensor.ANALOG, 'temp')
light_s = Sensor(Sensor.ANALOG, 'light')
rh_s = Sensor(Sensor.ANALOG, 'rh')
battery_s = Sensor(Sensor.ANALOG, 'voltage')
 
device.addSensor(temp_s)
device.addSensor(light_s)
device.addSensor(rh_s)
device.addSensor(battery_s)
 
while 1:
	line = ser.readline()
 
	id_v = int(line.split(':')[0])
	temp_v = int(line.split(':')[3])
	light_v = int(line.split(':')[4])
	rh_v = int(line.split(':')[5])
	battery_v = int(line.split(':')[6])
 
	print "id:",id_v," temp:",temp_v," light:",light_v," rh:",rh_v," battery:",float(battery_v/71.0)
 
	temp_s.addValue(temp_v)
	light_s.addValue(light_v)
	rh_s.addValue(rh_v)
	battery_s.addValue(float(battery_v/71.0))
 
	device.send()	

You can see below some snapshots from Devicehub.net for one of our nodes.

<imgcaption sensors | Sensor on Devicehub for each node> </imgcaption> <imgcaption sensors1 | Light sensor> </imgcaption> <imgcaption sensors2 | Relative humidity sensor> </imgcaption> <imgcaption sensors3 | Temperature Sensor> </imgcaption>

iothings/proiecte/2014/sparrowv3-devicehub.txt · Last modified: 2021/12/06 22:28 by dan.tudose
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0