in arduino side project english ~ read.

Home data: Arduino (part 1)

This is the first "real" post about my current side project. The first thing I will talk about is the Arduino I use to collect some date from my living room and outside of it. I will use two sensors one HDC1000 and a BME280. The BME will be placed outside because measuring the pressure in my flat does not make that much sense. Both sensors have a I²C interface so I can easily read their values from the Arduino. The Arduino I use is a simple Uno. I will be connected to my RaspberryPi via USB (more on that in the next post of the series).

Lets first take a look at how everything is wired together. Since both sensors support I²C there is not much needed here. Simply connected SDA and SCL from the Arduino to the respecting pins on the two sensors. Wired up VCC. Both sensors support 3.3V, so simply hooked them up the Arduino 3.3V output. And finally GND to ground of the Arduino. I'm not using the DREY pin of the HDC1000. It could be hook it up to one input of the Arduino and be used to detect when the measurement is completed. But the Arduino library support a mode where it simply waits some ms after it sent the read request.

Now that the hardware wired up, some code is needed on the Arduino that does something with the sensors. The data that is collected from the sensors will be formatted into JSON and send over USB to the RaspberryPi.

First of all two variables for the to sensors are created in the sketch:

#include <SparkFunBME280.h>
#include <Adafruit_HDC1000.h>
BME280 bme;  
Adafruit_HDC1000 hdc = Adafruit_HDC1000();  

After that the setup function of the sketch is use to configure and initialize the sensors correctly:

void setup(void) {  
  Serial.begin(57600);
  Serial.print("Program Started\n");
  if (!hdc.begin(0x43)) {
    Serial.println("Couldn't find sensor!");
    while (1);
  }
  initBME();
}

For the HDC1000 sensor it is fairly simple. Everything needed to do is passing the I²C address to the begin call. For the BME280 it's a bit more complex because it has lots of config options. So they have been put into an extra function:

void initBME(void) {  
  bme.settings.commInterface = I2C_MODE;
  bme.settings.I2CAddress = 0x77;

  bme.settings.runMode = 3; //Normal mode
  bme.settings.tStandby = 0; //0,5ms
  bme.settings.filter = 0; //filter off
  bme.settings.tempOverSample = 1; //1x oversampling
  bme.settings.pressOverSample = 1; //1x oversampling
  bme.settings.humidOverSample = 1; //1x oversampling

  delay(10);  //Make sure sensor had enough time to turn on. BME280 requires 2ms to start up.
  bme.begin()
}

Since the sensor also supports SPI the communication interface has to be set first. In this case I²C and the address. The other settings are pretty much the defaults. Normal mode means that the sensor takes measurements regularly and not only on request. In forced mode it would only measure once and then sleep till the next request. The other settings disable all filters and oversampling.

Now that everything has been setup sensor data can be used:

void measureData(void) {  
  Serial.print("{ \"outside\" : {\"temperature\" :");
  Serial.print(bme.readTempC(), 6);
  Serial.print(", \"humidity\" : ");
  Serial.print(bme.readFloatHumidity(), 2);
  Serial.print(", \"pressure\" : ");
  Serial.print(bme.readFloatPressure() / 100, 2);
  Serial.print("}, \"inside\" : {\"temperature\" : ");
  Serial.print(hdc.readTemperature(), 6);
  Serial.print(", \"humidity\" : ");
  Serial.print(hdc.readHumidity(), 6);
  Serial.print("} }");
  Serial.println();
}

The function is fairly simple. It reads all the individual values from both sensors and sends them over the serial/USB port.

Now the measure function still needs to be called. Todo so the loop function in added the sketch:

void loop() {  
  if(seconds_asleep >= 60)
  {
    seconds_asleep = 0;
    measureData();
    delay(100);
  }
  enterSleep();
}

So the loop function calls out measureData() function every time the seconds_asleep variable is greater or equal to 60 and otherwise calls enterSleep. But where does the seconds_asleep thing come from and what does enterSleep do?

The enterSleep function looks like this:

void enterSleep(void)  
{
  set_sleep_mode(SLEEP_MODE_PWR_SAVE);   /* EDIT: could also use SLEEP_MODE_PWR_DOWN for lowest power consumption. */
  sleep_enable();

  /* Now enter sleep mode. */
  sleep_mode();

  /* The program will continue from here after the WDT timeout*/
  sleep_disable(); /* First thing to do is disable sleep. */

  /* Re-enable the peripherals. */
  power_all_enable();
}

What is does, as the name suggests, it puts the controller into sleep mode. Why is this done? Because in sleepmode the power consumption is significantly lower than normal. Instead of keeping the CPU busy doing nothing we turn it off. But if the CPU if off how does it get back on? The CPU will wake up when interrupts are triggered. So we need to set something up that can wake up the CPU. The watchdog timers overflow will be used to wake the CPU up. To do so the following code is added to the setup function:

void setup(void) {  
  Serial.begin(57600);
  Serial.print("Program Started\n");
  if (!hdc.begin(0x43)) {
    Serial.println("Couldn't find sensor!");
    while (1);
  }
  initBME();

  /* Clear the reset flag. */
  MCUSR &= ~(1<<WDRF);

  /* In order to change WDE or the prescaler, we need to
   * set WDCE (This will allow updates for 4 clock cycles).
   */
  WDTCSR |= (1<<WDCE) | (1<<WDE);

  /* set new watchdog timeout prescaler value */
  WDTCSR = 1<<WDP2 | 1<<WDP1; /* 1.0 seconds */

  /* Enable the WD interrupt (note no reset). */
  WDTCSR |= _BV(WDIE);

}

This sets the watchdog timer to overflow every second. That way the controllers wakes up again when it was sent to sleep. But where does out seconds_asleep counter come from? As mentioned when the watchdog timer overflows a interrupt is triggered and a handler can be installed for this interrupt. The handler will simply count up the seconds_asleep variable:

ISR(WDT_vect)  
{
  seconds_asleep++;
  if (seconds_asleep > 60)
  {
      Serial.println("Error: event was not handled!");
  }
}

There is also some logging that if for some reason the interrupt was not handled correctly it is logged over the serial port.

You can find the complete sketch here.

In the next port of the series we will create a F# program that reads the data from the Arduino and then posts it to a MQTT server.