RSS

Adding Another Micrcontroller

July 9th, 2016 • Electronics, ESP8266, STM8Comments Off on Adding Another Micrcontroller

Last time the prototype was moved from breadboard to perfboard. The schematic had a couple of components for the rain gauge and one additional ADC to compensate for the fact that the ESP8266 only has one ADC on board. These three components added a reasonable cost to the board and it was hinted that another microcontroller maybe added to the system to replace these components.

Enter the STM8S.

Hardware Modifications

The previous schematic looked as follows:

Weather Station Schematic

Weather Station Schematic

Three components are being targeted for replacement:

  1. 74HC4040 – counter
  2. MCP23017 – I2C output expander
  3. ADS1115 – 4 channel ADC

These components come to several pounds (dollars) worth of components. It may be possible to replace these with the STM8S and a little software.

ESP8266 Low Power Mode

A long term goal of the project is to reduce the power consumed by the system in order to allow the system to work from a battery / solar energy source. In order to work towards this goal the system should be looking to shut off as much of the system as possible when they are not needed.

Initial thoughts are that the system should collect data every 5 minutes. This means that most of the system is only required for a few seconds (say 10 seconds) every five minutes.

One exception is the rain gauge. This needs to be counting all day, every day as a sample taken at a point in time is not representative of the rain fall for a full day.

Another exception is the Real Time Clock (RTC). This needs to be running all of the time for two reasons, firstly it keeps track of the current date and time. Secondly, the system can generate an interrupt to wake the system on a regular basis.

Adding Another Microcontroller

Regular readers will no doubt be aware that I have been a regular user of the STM8S. This microcontroller is available in packages starting at around £0.72 (about $1). Even the smallest packages have several ADCs and multiple digital input / outputs potentially reducing the system costs.

One other area that a microcontroller can help is with the chosen RTC, the DS3234. This RTC can generate an interrupt when an alarm time is reached. The interrupt generated is a simple high to low transition. The ESP8266 however requires a pulse. The STM8S can help by providing this conversion, detecting the falling edge and generating a pulse for the ESP8266.

Sounds perfect.

Updated Schematic

Several previous posts have shown that it is possible to perform analog conversions and also capture interrupts. This makes it possible for the STM8S to gather several sensor readings for the Oak and replace some components, namely:

  1. ADS1115 4 channel ADC – The STM8S has several ADC channels and the system only requires 2 so far
  2. MCP23017 I2C Port Expander – Several digital channels remain available even after using some pins for the ADC
  3. 74HC4040 Counter – This functionality can be provided through software and a digital channel

Another attractive feature of this microcontroller is the ability to act as an I2C slave device. This will work well with the other digital sensors as they all use the I2C protocol, one protocol to rule them all.

A few changes to the schematic results in the following:

Weather Station Schematic Revision 0.01

Weather Station Schematic Revision 0.01

Focusing in on the STM8S:

STM8S On The Weather Station

STM8S On The Weather Station

The STM8S should be able to take over the reading values from the following sensors:

  1. Ultraviolet light
  2. Wind direction
  3. Wind speed
  4. Rain Gauge

All that is needed now is some software. Time to break out the compilers.

Conclusion

Adding a new microcontroller adds some complexity to the software development whilst reducing the cost of the final solution.

Next steps:

  • Putting the Oak to sleep and using the RTC to determine when to wake the Oak
  • Reading the sensors on the STM8S
  • Communication between the Oak and the STM8S over I2C

Weather Station Update (5 June 2016)

June 5th, 2016 • Electronics, ESP8266Comments Off on Weather Station Update (5 June 2016)

The proof of concept for the weather station is progressing well but the breadboard is a little fragile:

Breadboard Weather Station

Breadboard Weather Station

Time to move on to the next stage, protoboard.

Quick schematic courtesy of KiCAD:

Weather Station Schematic

Weather Station Schematic

One thing to note in the schematic is that the project is rapidly using up the limited number of pins on the Oak. On with the protoboard, five hours of soldering later:

Protoboard Weather Station

Protoboard Weather Station

The project is already using an output expander to read the rain gauge. It might be time to think about the expansion options. To this end, the bottom right corner of the protoboard has space to experiment with a second microcontroller. It may be possible to replace the output expander and the additional ADC board with a cheaper option.

Conclusion

Putting the project in a more permanent state is a step forward. There are still some unanswered questions such as the use of the ADC or a second microcontroller but progress is being made.

DS3234 Real Time Clock

June 1st, 2016 • Electronics, Software DevelopmentComments Off on DS3234 Real Time Clock

One of the long term aims of the weather station is to take the system off grid. This will mean using an alternative energy source, something other than mains electricity. This usually means using solar energy to charge batteries in order to keep the system running.

Another scenario that will need to be considered is the case where the weather station is collecting data but is unable to connect to the internet. In this case it is desirable for the weather station to continue to collect data, record the date and time the observation was made and also store the readings. The system can then upload the data when the network connection is once available once more.

This article presents a possible solution to one problem and a possible solution to another:

  1. Reducing power by putting the system to sleep
  2. Recording the date / time when the system is not connected to the Internet

The solution being considered is a Real Time Clock (RTC) module based upon the DS3234 chip.

DS3234 RTC

The DS3234 is a real time clock module with a built in temperature controlled oscillator. It is a relatively self-contained module requiring few external components. The chip is programmed over the built in SPI interface.

A summary of the important features being considered are:

  1. Built in alarm(s)
  2. Battery back up
  3. Built in oscillator
  4. Interrupt on alarm

One additional feature that may be useful is the provision of 256 bytes of battery backed up RAM.

The interrupt generated by the alarms can be used to trigger the Oak to take action. In this way it may be possible for the Oak to sleep for a period of time waiting for a signal from the RTC. The RTC will run in much lower power mode than the Oak.

One disadvantage of this module, at the time of writing, was the lack of code that would either provide the functioality required or work reliably on the Oak.

Time to break out the C++ compiler.

Controlling the DS3234

Before we look at the software, it is worth spending a little time to review how the DS3234 is controlled. The RTC chip uses write operations to a series of registers in order to control the functionality of the chip. The register map is as follows:

Register Read Address Contents
0x00 Current time seconds
0x01 Current time minutes
0x02 Current time hour
0x03 Current time day
0x04 Current time date
0x05 Current time month
0x06 Current time year
0x07 Alarm 1 seconds
0x08 Alarm 1 minutes
0x09 Alarm 1 hour
0x0a Alarm 1 day/date
0x0b Alarm 2 minutes
0x0c Alarm 2 hour
0x0d Alarm 2 day/date
0x0e Control register
0x0f Control / status register
0x10 Crystal aging offset
0x11 Temperature (MSB)
0x12 Temperature (LSB)
0x13 Disable temperature conversions
0x14 Reserved
0x15 Reserved
0x16 Reserved
0x17 Reserved
0x18 SRAM address
0x19 SRAM data

Applications can write to the registers by setting bit 7 (i.e. add 0x80) in the address.

The register address is automatically incremented after a read or write operation. So to write the current time into the hours, minutes and seconds registers an application would simply write four bytes to the SPI bus; namely address 0x80 (write address for the seconds register) followed by the seconds, minutes and hour values.

The bit fields for the above registers are well defined in the DS3234 datasheet and are not covered in too much detail here and you are encouraged to download the datasheet for reference.

DS3234 Software – Private Interface

The full source code for this module can be downloaded from at the end of this article, the discussion about the software and it’s functionality will concentrate on the class definition in the header file. Those interested in the detail can jump into the source code at the end of the article.

So let’s have a look at the full header file:

#ifndef _DS3234_h
#define _DS3234_h

#include <BitBang\BitBang.h>

#if defined(ARDUINO) && ARDUINO >= 100
	#include "arduino.h"
#else
	#include "WProgram.h"
#endif

#define MAX_BUFFER_SIZE     256

//
//  Time structure.
//
struct ts
{
    uint8_t seconds;    // Number of seconds, 0-59
    uint8_t minutes;    // Number of minutes, 0-59
    uint8_t hour;       // Number of hours, 0-23
    uint8_t day;        // Day of the month, 1-31
    uint8_t month;      // Month of the year, 1-12
    uint16_t year;      // Year >= 1900
    uint8_t wday;       // Day of the week, 1-7
};

//
//  Define the methods required to communicate with the DS3234 Real Tiem Clock.
//
class DS3234RealTimeClock
{
    public:
        //
        //  Enums.
        //
        enum Alarm { Alarm1, Alarm2, Both, Unknown };
        enum AlarmType { OncePerSecond, WhenSecondsMatch, WhenMinutesSecondsMatch,  // Alarm 1 options.
                         WhenHoursMinutesSecondsMatch, WhenDateHoursMinutesSecondsMatch, WhenDayHoursMinutesSecondsMatch,
                         OncePerMinute, WhenMinutesMatch, WhenHoursMinutesMatch,    // Alarm 2 options.
                         WhenDateHoursMinutesMatch, WhenDayHoursMinutesMatch };
        enum ControlRegisterBits { A1IE = 0x01, A2IE = 0x02, INTCON = 0x04, RS1 = 0x08, RS2 = 0x10, Conv = 0x20, BBSQW = 0x40, NotEOSC = 0x80 };
        enum StatusRegisterBits { A1F = 0x02, A2F = 0x02, BSY = 0x04, EN32Khz = 0x08, Crate0 = 0x10, Crate1 = 0x20, BB32kHz = 0x40, OSF = 0x80 };
        enum RateSelect { OneHz = 0, OnekHz = 1, FourkHz = 2, EightkHz = 3 };
        enum Registers { ReadAlarm1 = 0x07, WriteAlarm1 = 0x87,
                         ReadAlarm2 = 0x0b, WriteAlarm2 = 0x8b,
                         ReadControl = 0x0e, WriteControl = 0x8e,
                         ReadControlStatus = 0x0f, WriteControlStatus = 0x8f,
                         ReadAgingOffset = 0x10, WriteAgingOffset = 0x90, 
                         ReadSRAMAddress = 0x18, WriteSRAMAddress = 0x98,
                         ReadSRAMData = 0x19, WriteSRAMData = 0x99 };
        enum DayOfWeek { Sunday = 1, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };
        //
        //  Construction and destruction.
        //
        DS3234RealTimeClock(const uint8_t);
        DS3234RealTimeClock(const uint8_t, const uint8_t, const uint8_t, const uint8_t);
        ~DS3234RealTimeClock();
        //
        //  Methods.
        //
        uint8_t GetAgingOffset();
        void SetAgingOffset(uint8_t);
        float GetTemperature();
        uint8_t GetControlStatusRegister();
        void SetControlStatusRegister(uint8_t);
        uint8_t GetControlRegister();
        void SetControlRegister(uint8_t);
        ts *GetDateTime();
        Alarm WhichAlarm();
        void SetDateTime(ts *);
        String DateTimeString(ts *);
        void SetAlarm(Alarm, ts *, AlarmType);
        void InterruptHandler();
        void ClearInterrupt(Alarm);
        void EnableDisableAlarm(Alarm, bool);
        void WriteToSRAM(uint8_t, uint8_t);
        uint8_t ReadFromSRAM(uint8_t);
        void ReadAllRegisters();
        void DumpRegisters(const uint8_t *);
        void ReadSRAM();
        void DumpSRAM();

    private:
        //
        //  Constants.
        //
        static const int REGISTER_SIZE = 0x14;
        static const int DATE_TIME_REGISTERS_SIZE = 0x07;
        static const int MEMORY_SIZE = 0xff;
        //
        //  Variables
        //
        int m_ChipSelect = -1;
        int m_MOSI = -1;
        int m_MISO = -1;
        int m_Clock = -1;
        uint8_t m_Registers[REGISTER_SIZE];
        uint8_t m_SRAM[MEMORY_SIZE];
        BitBang *m_Port;
        //
        //  Make the default constructor private so it cannot be called explicitly.
        //
        DS3234RealTimeClock();
        //
        //  Methods.
        //
        void Initialise(const uint8_t, const uint8_t, const uint8_t, const uint8_t);
        void SetRegisterValue(const Registers, uint8_t);
        uint8_t GetRegisterValue(const Registers);
        uint8_t ConvertUint8ToBCD(const uint8_t);
        void BurstTransfer(uint8_t *, uint8_t);
        void BurstTransfer(uint8_t *, uint8_t *, uint8_t);
        uint8_t ConvertBCDToUint8(const uint8_t);
};

#endif

Perversely, this discussion will start with the private data items and methods as these underpin the way in which the class provides the higher-level functionality.

Constants

The three constants define the size of the various register and RAM space within the chip.

REGISTER_SIZE

Defines the total number of bytes in the register bank excluding the reserved and SRAM registers.

DATE_TIME_REGISTERS_SIZE

Number of bytes holding the current date and time information.

MEMORY_SIZE

Number of bytes of SRAM.

Private Member Variables

The private member variables hold the state information for the class.

As noted in the datasheet for the DS3234, communication with the DS3234 is over the SPI bus. The first version of this class uses a software SPI class BitBang to provide this functionality.

m_Clock, m_MOSI, m_MISO, m_ChipSelect

The software SPI class allows the user to change the pins that provide the MOSI, MISO and Clock input/outputs. These four member variables record the pins used to provide the soft SPI bus.

m_Port

Instance of the class providing the SPI service.

m_Registers

Internal copy of the registers (up to the reserved registers).

m_SRAM

Internal copy of the SRAM.

Methods

The private methods provide the low level functionality supporting the public methods. These methods provide functions such as initialisation, communication and data conversion.

DS3234RealTimeClock()

The default constructor is made private to ensure that the developer cannot call this accidentally, forcing the use of the constructors that define the properties of the soft SPI bus.

void Initialise(const uint8_t chipSelect, const uint8_t mosi, const uint8_t miso, const uint8_t clock)

The four parameters inform the class which pins are used for the MOSI, MISO, Clock and Chip Select functions.

uint8_t ConvertBCDToUint8(const uint8_t value)

The values in the registers recording the date / time hold BCD values. This method provides a way to convert the DS3234 BCD values into a more natual (for the developer) uint8_t value.

uint8_t ConvertUint8ToBCD(const uint8_t value)

As noted above (see ConvertBCDToUint8), the DS3234 uses BCD to hold time vales. This methods provdes a method to convert uint8_t values to BCD before they are written to the registers.

uint8_t GetRegisterValue(const Registers reg)

Read a value from the specified register.

void SetRegisterValue(const Registers reg, const uint8_t value)

Set the specified register reg to value.

void BurstTransfer(uint8_t *dataToChip, uint8_t size) and void BurstTransfer(uint8_t *dataToChip, uint8_t *dataFromChip, uint8_t size)

As noted in the hardware discussion, the DS3234 will automatically increment the “address register” if multiple bytes of data are written to the SPI bus. These two methods take advantage of this functionality.

The first variant of the overloaded method, BurstTransfer(uint8_t *dataToChip, uint8_t size), transfers a number of bytes defined by size to the SPI bus. No data is read from the bus.

The second method, BurstTransfer(uint8_t *dataToChip, uint8_t *dataFromChip, uint8_t size), both writes and reads size bytes to/from the SPI bus.

In both cases, the first byte of the dataToChip buffer is set to the address of the first register to be read/written.

DS3234 Software – Public Interface

The public interface is the useful part of this class as it provides the functionality that is usable by the application developer.

Enumerated Types

The first group of items in the public interface are a series of enumerated types. These allow a meaningful name to be given to the various registers, bits within the registers and method parameters.

Alarm

There are two alarms on the DS3234, Alarm1 and Alarm2. These have slightly different capabilities as defined in the next enum.

enum Alarm { Alarm1, Alarm2, Both, Unknown };

This enum is also used to represent the type of alarm that has been generated.

AlarmType

The AlarmType represents the type of alarm being set. These have been put into two groups, one for each of the alarms:

enum AlarmType { OncePerSecond, WhenSecondsMatch, WhenMinutesSecondsMatch,  // Alarm 1 options.
                 WhenHoursMinutesSecondsMatch, WhenDateHoursMinutesSecondsMatch, WhenDayHoursMinutesSecondsMatch,
                 OncePerMinute, WhenMinutesMatch, WhenHoursMinutesMatch,    // Alarm 2 options.
                 WhenDateHoursMinutesMatch, WhenDayHoursMinutesMatch };

The first two lines represent the type of alarms that can be set on Alarm1, the second two lines can be used with Alarm2.

ControlRegisterBits

The values in this enum are the flags in the control register:

enum ControlRegisterBits { A1IE = 0x01, A2IE = 0x02, INTCON = 0x04, RS1 = 0x08, RS2 = 0x10, Conv = 0x20, BBSQW = 0x40, NotEOSC = 0x80 };

Two or more options can be set by or-ing the flags.

StatusRegisterBits

The status register as actually a combination of a number of bits that indicate the status of the DS3234 as well as some that control the action of the DS3234.

enum StatusRegisterBits { A1F = 0x02, A2F = 0x02, BSY = 0x04, EN32Khz = 0x08, Crate0 = 0x10, Crate1 = 0x20, BB32kHz = 0x40, OSF = 0x80 };

RateSelect

It is possible to generate a square wave at one of four frequencies on the interrupt pin. These flags determine the frequency of the generated signal:

enum RateSelect { OneHz = 0, OnekHz = 1, FourkHz = 2, EightkHz = 3 };

Registers

There are a total of 25 register addresses on the DS3234, containing information about the time, alarms and various other functions of the DS3234. This enumeration contains the addresses of several of the registers excluding the time register addresses:

enum Registers { ReadAlarm1 = 0x07, WriteAlarm1 = 0x87,
                 ReadAlarm2 = 0x0b, WriteAlarm2 = 0x8b,
                 ReadControl = 0x0e, WriteControl = 0x8e,
                 ReadControlStatus = 0x0f, WriteControlStatus = 0x8f,
                 ReadAgingOffset = 0x10, WriteAgingOffset = 0x90, 
                 ReadSRAMAddress = 0x18, WriteSRAMAddress = 0x98,
                 ReadSRAMData = 0x19, WriteSRAMData = 0x99 };

The current time register addresses have been excluded as there are methods available to set and get the time using the ts structure.

DayOfWeek

The day of the week is represented by a number inthe range 1 to 7. The actual day represented is arbitary and Sunday has been selected for the number 1:

enum DayOfWeek { Sunday = 1, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };

Constructor and Destructor

As already noted, the default constructor has been made private but two public constructors have been created. This ensures that the developer is forced to define one or more of the parameters for the SPI bus.

DS3234RealTimeClock(const uint8_t chipSelect)

The only parameter to this constructor is the pin to be used for chip select.

If this constructor is used the default pins are used for MOSI, MISO and SCLK.

DS3234RealTimeClock(const uint8_t chipSelect, const uint8_t mosi, const uint8_t miso, const uint8_t clock)

Using this method, the developer can override all of the pins used for the SPI bus.

~DS3234RealTimeClock()

Destructor to release all of the resources held by the instance of the class.

Methods

All of the high level functionality of the class is exposed by the public methods.

uint8_t GetAgingOffset() & SetAgingOffset(uint8_t agingOffset)

Get or set the value for the aging offset register. This is the value used to apply to the oscillator to compensate for variations in temperature.

float GetTemperature()

Get the current temperature.

uint8_t GetControlStatusRegister()

Get the current contents of the control register.

void SetControlRegister(const uint8_t controlRegister)

Set the control register to the specified value. The ControlRegisterBits can be or-ed together to build up the required value.

ts *GetDateTime()

Get the current date and time.

void SetDateTime(ts *dateTime)

Set the current date and time to the specified value.

String DateTimeString(ts *theTime)

Convert theDate into a string using the format dd-mon-yyyy hh-min-ss.

void SetAlarm(Alarm alarm, ts *time, AlarmType type)

Set the alarm time to time and the type of the alarm type to type.

void EnableDisableAlarm(Alarm alarm, bool enableOrDisable)

Turn the specified alarm on (if enableOrDisable is true) or off (if enableOrDisable is false).

void ClearInterrupt(Alarm alarm)

Clear the interrupt flag for the specified alarm. If both interrupt have been triggered then the application will need to clear both alarms in order to reset the interrupt line on the DS3234.

Alarm WhichAlarm()

Determine which alarm has generated the interrupt.

void InterruptHandler()

This method has been provided to allow the application to call a generic interrupt handler. This method is not currently implemented.

SRAM Methods

There are 256 bytes of battery backed up SRAM in the DS3234 and the following two methods expose this memory to the developer.

void WriteToSRAM(uint8_t address, uint8_t data)

Write a byte of data into the specified address in the battery backed up SRAM.

uint8_t ReadFromSRAM(uint8_t)

Read the byte of data at the specified address in the battery backed up SRAM.

Diagnostic Methods

The following methods are used for diagnostic purposes.

void ReadAllRegisters()

Read the contents of the entire register set within the DS3234 and place them in the internal private variable m_Registers variable.

void DumpRegisters(const uint8_t *)

Dump the regsiters to the serial port. The ReadAllRegisters should be called before calling this method.

void ReadSRAM()

Read the content of the SRAM and store this in the private internal m_SRAM variable.

void DumpSRAM()

Display the contents of the battery backed up SRAM to the serial port.

Example application

Several example applications were developed throughout the development of the library. The application below illustrates several of the key features of the library.

First thing that is needed is an instance of the DS3234 class:

DS3234RealTimeClock *rtc = new DS3234RealTimeClock(6);

Next up, we need to setup the DS3234 and attach an interrupt handler.

void setup()
{
    ts *dateTime;

    Serial.begin(9600);
    Serial.println("Setup in progress.");
    rtc->SetControlRegister(DS3234RealTimeClock::INTCON);
    rtc->ClearInterrupt(DS3234RealTimeClock::Alarm1);
    rtc->ClearInterrupt(DS3234RealTimeClock::Alarm2);

    //dateTime = new (ts);
    //dateTime->day = 27;
    //dateTime->wday = 5;
    //dateTime->month = 5;
    //dateTime->year = 2016;
    //dateTime->minutes = 05;
    //dateTime->hour = 13;
    //dateTime->seconds = 0;
    //rtc->SetDateTime(dateTime);

    dateTime = rtc->GetDateTime();
    dateTime->minutes += 2;
    if (dateTime->minutes >= 60)
    {
        dateTime->minutes -= 60;
    }
    dateTime->seconds = 0;
    Serial.print("Setting alarm for ");
    Serial.println(rtc->DateTimeString(dateTime));
    rtc->SetAlarm(DS3234RealTimeClock::Alarm1, dateTime, DS3234RealTimeClock::WhenMinutesSecondsMatch);
    rtc->EnableDisableAlarm(DS3234RealTimeClock::Alarm1, true);
    pinMode(5, INPUT_PULLUP);
    attachInterrupt(5, RTCInterrupt, FALLING);
    Serial.println("Setup complete.");
}

setup‘s first job is to clear any interrupts and set the DS3234 interrupt pin to generate an interrupt.

The next block of code which is commented out, allows the application to set the current date and time. This is only required when the application is first run or if the application is run with a DS3234 without a battery installed. If a battery is not installed then it may be desirable to get the current date and time for a time server if one is available.

The next step is to get the current date and time from the module and add two minute to the time, set the seconds for the alarm to 0 and then set an alarm for two minutes time. The first time this is run may generate an alarm somewhere between 1 and 2 minutes time. All subsequent alarms should occur at two minute intervals.

Setting the seconds for the alarm to 0 makes the first alarm appear at a seemingly random time. Consider the case when the application starts at 13:46:32. The application will set the alarm time to 13:48:00, i.e in 1 minute and 28 time and not 2 minutes. The following alarm will occur 2 minutes after the current alarm.

Finally, setup will set the interrupt behaviour on pin 5 of the microcontroller to trigger on the falling edge.

All of the work is performed in the setup method and the interrupt handler. This means that the main program loop does not contain any code:

void loop()
{
}

All of the work for this application is performed in the interrupt handler:

void RTCInterrupt()
{
    ts *dateTime;
    uint8_t count = 0;
    uint8_t minutes;

    rtc->ClearInterrupt(DS3234RealTimeClock::Alarm1);
    count = rtc->ReadFromSRAM(0x01);
    Serial.printf("Alarm %03d: ", ++count);
    dateTime = rtc->GetDateTime();
    Serial.println(rtc->DateTimeString(dateTime));
    rtc->WriteToSRAM(0x01, count);
    //
    //  Now reset the alarm.
    //
    minutes = dateTime->minutes;
    minutes += 2;
    if (minutes >= 60)
    {
        minutes = 0;
    }
    dateTime->minutes = minutes;
    Serial.print("Setting alarm for ");
    Serial.println(rtc->DateTimeString(dateTime));
    rtc->SetAlarm(DS3234RealTimeClock::Alarm1, dateTime, DS3234RealTimeClock::WhenMinutesSecondsMatch);
    delete(dateTime);
}

RTCinterupt is invoked then the interrupt pin of the DS3234 falls from high to low. This will happen when the specified alarm event occurs, in this case when the minutes and seconds of the current date / time match the value in Alarm1.

First thing to do is to clear the current interrupt taking the interrupt line from low back to high.

An interrupt counter is being stored in the battery backed up memory. This is read, incremented, displayed and then written back into the SRAM.

Finally, the alarm is reset to occur in two minutes.

Conclusion

DS3234 modules provide a method of recording time when the Internet is not available. The intention is to provide a way of taking readings and timestamping the readings when Internet connectivity is not available. Data can be uploaded when connectivity is restored and the time information will be retained.

The SRAM could be used to store readings along with a timestamp however, with only 256 bytes available this would not allow for a lot of readings and so another method will be investigated.

The DS3234 class can be downloaded from GitHub.

Enhancing the Pluviometer

May 22nd, 2016 • Electronics, Internet of Things, Software DevelopmentComments Off on Enhancing the Pluviometer

A previous post looked at measuring rainfall using a pluviometer that generated a pulse for every 0.2794mm of rainfall. The conclusion of that post was that the algorithm used to read the counter could generate a significant error in the right circumstances.

So let’s have a look at what we can do about the error.

Parallelism

Time for an admission, the algorithm used to collect the sensor reading was not as efficient as it could have been. The code read one bit at a time from the MCP23017 chip and built up the 8-bit sensor reading gradually. This reading took approximately 2800us to complete.

This is more than enough time for one or more of the bits to change in the time taken to take the sensor reading.

Fix One – Read 8 Bits in One Go

A more detailed examination of the library for the MCP23-17 reveals methods for reading not only one bit but 8 and 16 bits in a single operation (from a callers pint of view). The point about a callers point of view will become important later.

For a single 8-bit read the application can obtain all of the bits in a single, near instantaneous pass. Reading the counter in this manner takes approximately 480us. Most of this time will be spent setting up the call to the API and talking to the MCP23017 at 100kHz.

So for a single 8-bit read we have reduced the possibility or error caused by the data changing during the time the reading is being made.

Fix Two – Stop the Clock – FAIL

Within the API for the MCP23017 is an additional method for reading both of the registers “at the same time”. This is quoted because the library reads the first 8 bits in one request and then makes a second request for the next 8 bits. The request mechanism is working at 100 kHz and so there is going to be a finite delay between reading the two 8 bit registers. Plenty of time for the values to change.

One solution is to borrow on an old idea from CPU design, namely using a clock to synchronise operations.

Any operation in a CPU is a combination of signals passing from one gate to another, to yet another and so on. Each gate takes a finite amount of time to process any signals and generate a result. This time is know as the propagation delay.

In the case of the pluviometer, the propagation delay is the amount of time needed to read the counter (the registers in the MCP23017).

In a CPU the clock synchronises the signals and output by effectively freezing the state of the system. So, the input to a system might be changing but the use of the clock freezes the output of the CPU until the next clock pulse.

The same idea can be used with the pluviometer but instead of freezing the output we freeze the input while a reading is being made.

The only problem with the implementation being considered was that the switch caused multiple signals to be generated even with the debounce circuit.

Time for a rethink.

Fix Three (or Two Revisited) – Multiple Readings

Stepping away from a problem can often yield an alternative solution as the mind works on the problem subconsciously. The problem is that we could have a reading taken whilst the input signal is changing the value being read. So if a reading is taken twice and the values are the same then we can be confident that the input has not changed provided that the time between the samples is longer then the time taken for the input to change.

A simple solution to the problem.

Software Changes

The software change is minimal, read the input twice until a consistent sample is read, then reset the counter.

A retry counter is also used in order to prevent the reading method from blocking the application.

uint8_t first, second;
byte retryCount = 0;

do
{
    first = _outputExpander.readGPIO(1);
    second = _outputExpander.readGPIO(1);
    retryCount++;
}
while ((first != second) && (retryCount < 3));
if (first == second)
{
    _pluviometerPulseCount = first;
    _pluviometerPulseCountToday += _pluviometerPulseCount;
    ResetPluviometerPulseCounter();
}
else
{
    _pluviometerPulseCount = 0;
}

One important point to note is that if the retry count exceeds three, then the method exist but sets the current count to zero without resetting the pluviometer counter (hardware). This allows the rainfall measurement to continue and allow for the reading not to be distorted.

Conclusion

A couple of minor software changes have improved the reliability of the pluviometer samples. There is still the chance of missing a single sample (0.2794mm) for every time the counter is read.

A further enhancement would be to increase the time between samples. This could be done by taking a measurement every hour (or more) or to accumulate the readings over a long time (say a day) and reset the counter once a day.

Wind Direction and Ground Temperature

May 14th, 2016 • Electronics, ESP8266, Software DevelopmentComments Off on Wind Direction and Ground Temperature

The last days have seen the final two sensors connected to the Oak, the wind direction and the ground temperature sensors.

Ground Temperature Sensor

This sensor is a DS18B20 sensor that has been encapsulated in a rubber like coating to make it waterproof. The sensor has a 1.8m (6 ft) waterproofed cable attached.

DS18B20 Waterproof Temperature Sensor

DS18B20 Waterproof Temperature Sensor

The sensor uses a one wire protocol to communicate with the microcontroller, so only three connections required, power, data and ground. Strictly speaking, only two connections are required, data and ground as the sensor can use power from the data line to provide enough power for the sensor.

Adafruit also provide a library for communicating with the DS18B20 sensor along with some example code. This makes working with the sensor a relatively simple job. Most of the code required to set up and read the sensor was taken from the example code provided and as such will not be discussed is any detail here.

Wind Direction Sensor

The wind direction sensor is an analogue sensor and the last of the sensors on the Weather Station sensor kit.

The sensor consists of a series of reed switches in a star shape. The wind vane uses magnets to close the reed switches and the resulting connections connect a resistors to ground through one of the wires. The second wire should be connected to a a resistor (the example in the data sheet is 10K) with the other end connected to Vcc.

The result is a potential divider circuit. The output from the circuit can be connected to an Analog to Digital Converter (ADC). The datasheet for the sensor is supplied with a list of the resistance values along with expected outputs for a 5V supply. All of the circuitry used so far has been using a 3.3V supply as the Oak is a 3.3V component. A little bit of spreadsheet wizardry converts the values to a 3.3V supply:

Degrees (from North) Resistance Reading (5V) Reading (3.3V)
0.0 33000 3.837 2.533
22.5 6570 1.982 1.308
45.0 8200 2.253 1.487
67.5 891 0.409 0.270
90.0 1000 0.455 0.300
112.5 688 0.322 0.212
135.0 2200 0.902 0.595
157.5 1410 0.618 0.408
180.0 3900 1.403 0.926
202.5 3140 1.195 0.789
225.0 16000 3.077 2.031
247.5 14120 2.927 1.932
270.0 120000 4.615 3.046
292.5 42120 4.041 2.667
315.0 64900 4.332 2.859
337.5 21880 3.432 2.265

This now brings us on to the problem of the number of ADC channels on the Oak, there is only one and we have two analogue devices, the ultraviolet light and the wind direction sensors. One solution is to add a multichannel ADC to the system.

The ADS1115 is a four channel ADC with a an I2C bus. As with the DS18B20, there is a library available in the Arduino library collection, again written by Adafruit.

In the initial experiments with this library using a potentiometer I experienced problems with the conversions appearing to be duplicated / incorrect. It appeared that the conversion was appearing on the next channel (i.e. conversion for A0 was given on A1) or on both channels (i.e. A2 and A3 having the same reading). Perhaps there is not enough time being left for the ADC to complete the sample and conversion. A brief investigation into the library code lead to a pause parameter being used to allow the ADC to perform the conversion. This was 8ms, changing this to 10ms fixed the issue.

Next step is to calibrate the wind direction sensor and confirm the sensor readings with the theoretical values from the spreadsheet.

A painstaking few hours with the sensor, the ADC and the spreadsheet followed. The net result was the following:

Degrees (from North) Resistance Theoretical Reading (V) Actual Reading (V) Actual Reading
0.0 33000 2.533 2.4913 13287
22.5 6570 1.308 1.2873 6866
45.0 8200 1.487 1.4628 7801
67.5 891 0.270 0.2647 1412
90.0 1000 0.300 0.2938 1567
112.5 688 0.212 0.2084 1112
135.0 2200 0.595 0.5857 2134
157.5 1410 0.408 0.4004 2136
180.0 3900 0.926 0.9106 4587
202.5 3140 0.789 0.7745 4131
225.0 16000 2.031 1.9948 10639
247.5 14120 1.932 1.9953 10642
270.0 120000 3.046 2.9958 15978
292.5 42120 2.667 2.6233 13991
315.0 64900 2.859 2.8132 15004
337.5 21880 2.265 2.2282 11884

The final column in the table above is the actual 16-bit reading from the ADC. The intention is to use these values when working out the wind direction, it is quicker (from a processor point of view) to work with integers rather than floating point numbers. Comparisons are also more accurate.

From a code perspective, the best way to deal with the above is to use a lookup table. The table can be ordered on the actual reading, or a mid-point reading.

If the readings are ordered in ascending order then we can calculate the values that are midway between two readings. The idea is to give a spread of possible values for a particular reading. There will be slight variations in the ADC reading around the theoretical value for any particular angle. Rather than define the actual spread, or a tolerance, say 100 points either way, a mid-point value should give a very wide range and should ensure that we have the right result for the particular ADC reading.

So let’s look at some code. There are a couple of basic things we need to set up, an enumeration to represent the wind direction and a data structure for the look up table:

enum WindDirection
{
    North, NorthNorthEast, NorthEast, EastNorthEast, East, EastSouthEast, SouthEast, SouthSouthEast,
    South, SouthSouthWest, SouthWest, WestSouthWest, West, WestNorthWest, NorthWest, NorthNorthWest
};

//
//  Entry in the wind direction lookup table.
//
struct WindDirectionLookup
{
    uint16_t midPoint;
    uint16_t reading;
    float angle;
    WindDirection direction;
    char *directionAsText;
};

WindDirectionLookup _windDirectionLookupTable[16];
uint8_t _windDirectionLookupEntry;

The last two entries direction and directionAsText are really there to give us a meaningful description within the code and also a textural description should we need to output the wind direction to a display.

Now that the table is declared we should really populate it:

//
//  Populate the wind direction lookup table.  Each entry has the following form:
//
//  midPoint, reading, angle, direction, direction as text
//
//  Note that this table is order not in angles/wind direction but in terms of the
//  mid points of the ADC readings found empirically.  This allows for easier
//  searching of the table.
//    
_windDirectionLookupTable[0] = { 0, 1112, 112.5, EastSouthEast, (char *) "East-South-East" };
_windDirectionLookupTable[1] = { 1262, 1412, 67.5, EastNorthEast, (char *) "East-North-East" };
_windDirectionLookupTable[2] = { 1489, 1567, 90, East, (char *) "East" };
_windDirectionLookupTable[3] = { 1851, 2136, 157.5, SouthSouthEast, (char *) "South-South-East" };
_windDirectionLookupTable[4] = { 2630, 3124, 135, SouthEast, (char *) "South-East" };
_windDirectionLookupTable[5] = { 3627, 4131, 202.5, SouthSouthWest, (char *) "South-South-West" };
_windDirectionLookupTable[6] = { 4359, 4587, 180, South, (char *) "South" };
_windDirectionLookupTable[7] = { 5726, 6866, 22.5, NorthNorthEast, (char *) "North-North-East" };
_windDirectionLookupTable[8] = { 7333, 7801, 45, NorthEast, (char *) "North-East" };
_windDirectionLookupTable[9] = { 9220, 10639, 225, SouthWest, (char *) "South-West" };
_windDirectionLookupTable[10] = { 10640, 10642, 247.5, WestSouthWest, (char *) "West-South-West" };
_windDirectionLookupTable[11] = { 11263, 11884, 337.5, NorthNorthWest, (char *) "North-North-West" };
_windDirectionLookupTable[12] = { 12585, 13287, 0, North, (char *) "North" };
_windDirectionLookupTable[13] = { 13639, 13991, 292.5, WestNorthWest, (char *) "West-North-West" };
_windDirectionLookupTable[14] = { 14497, 15004, 315, NorthWest, (char *) "North-West" };
_windDirectionLookupTable[15] = { 15491, 15978, 270, West, (char *) "West" };

Reading the sensor is as easy as getting the ADC rading and then walking through the look up table:

//
//  Read the wind direction sensor and calculate the direction the vane is pointing.
//
WeatherSensors::WindDirection WeatherSensors::ReadWindDirection()
{
    uint16_t windDirection = _adc.readADC_SingleEnded(_windDriectionAnalogChannel);

    _windDirectionLookupEntry = 15;
    for (int index = 0; index < 15; index++)
    {
        if ((windDirection > _windDirectionLookupTable[index].midPoint) && (windDirection <= _windDirectionLookupTable[index + 1].midPoint))
        {
            _windDirectionLookupEntry = index;
            break;
        }
    }
    message = "Wind is blowing from ";
    message += _windDirectionLookupTable[_windDirectionLookupEntry].directionAsText;
    Debugger::DebugMessage(message);
}

//
//  Get the last wind direction reading as a textural description.
//
char *WeatherSensors::GetWindDirectionAsString()
{
    return(_windDirectionLookupTable[_windDirectionLookupEntry].directionAsText);
}

The principle is to walk through the look up table and compare the reading from the ADC with the midpoint reading. The midpoint for any element in the table is the value halfway between this value and the previous value.

So if a reading x is greater then xm and greater than xm+1 (where xm is the midpoint of the current element in the table xm+1 is the midpoint of the next element in the table) then the program should move on to the next element.

If in doubt walk thorough an example. Take a reading x = 1567, the exact reading for East and walk through the table above. The answer you should reach is East. Now take a reading x = 1566 or x = 1568 and the answer should be the same, namely East. Finally, be a little more extreme, say x = 1530, the answer should still be the same.

Conclusion

The final two sensors have been connected to the Oak (on breadboard) and indoor readings are available through the serial port.

Next steps:

  1. Fix the rain fall meter issue wher it is possible to produce an incorrect reading when a pulse is generated by the rainfall guage at the same time as the gus=age is being read.
  2. Add a real time clock (RTC) to the system for times when the internet is not available.
  3. Send data to the cloud
  4. Transfer the design to protoboard and start to work on PCB designs

Still plenty to do on the design / coding front. And at some point this needs to be made hardy enough for the great outdoors, and become self powered.

Measuring Rain Fall

May 9th, 2016 • Electronics, ESP8266, Software Development1 Comment »

A new day and a new sensor to look at, the pluviometer.

The pluviometer is a mechanical sensor that looks a little like a see-saw. A small plastic bucket is placed on each side of the fulcrum. The buckets are designed so that when a critical mass of water is in one bucket it tips the see-saw and the bucket on the other side starts filling. The process continues as each bucket is filled and emptied. A picture paints a thousand words, so here is a view of the inside of the pluviometer:

Inside the Pluviometer

Inside the Pluviometer

A small magnet is placed on the see-saw and a reed switch is behind the fulcrum. The tipping motion triggers the reed switch each time the see-saw tips, this in turn generates a single pulse.

Reading the switch becomes a seeming simple repetition of the wind speed problem, namely debouncing a switch and attaching an interrupt.

One long term goal has to take this solution off grid. This will make power consumption a critical factor in the design. Attaching an interrupt does not necessarily become an attractive option as the Oak would be running continuously and with the WiFi running this would consume a fair amount of power.

According to the specification for the pluviometer, a pulse will be generated for every 0.2794mm of rainfall.

Offline Counting

One possible solution to the power problem would be to put the Oak to sleep and wake it up every say 5-15 minutes to take measurements and upload them to the cloud. Doing this would reduce power but would also mean that no measurements would be take during the sleep period if interrupts were used.

A cheap solution in terms of cost is to use the 74HC4040 counter. This could be put into a circuit and kept active while the Oak is sleeping. The output from the debounced switch would then be used as a clock signal for the 74HC4040. This would allow the pulses from the pluviometer to be counted while the Oak is sleeping.

The downside is that eight pins are needed to read the output from the counter. With only a small number of pins, many of which have already been used, this will require some way of adding extra pins to the system. Fortunately there are a number of digital IO expanders on the market. A common series of chips is the MCP23x17 chips. These add an additional 16 inputs/output pins with communication to the chip vis I2C (X=0) or SPI (X=S). The I2S variation will fit the bill nicely.

One final connection that is required for the counter is a reset connection. Left to itself, the rain gauge counter would continue to count pulses until the counter overflowed and started from zero once more. From a design perspective there are a few options:

  1. Reset the counter each time it has been read
  2. Reset the counter once a day
  3. Let the counter overflow and detect the reset to zero

One consideration will be the amount of rainfall the system can detect before it resets to zero.

Maximum number of counts = 212

Which gives 4096 counts. However, for simplicity we will only consider the lower 8 bits, i.e. 0-255 counts. The system can always be expanded later should this be necessary (or if the system has the capacity).

Maximum rainfall = 255 * 0.2794 mm

Giving a maximum rainfall count of 71.247 mm per counting period. It is envisaged that the counting period would be somewhere in the region of 5-15 minutes. This would make an hourly average of 285mm assuming a steady rainfall and a 15-minute interval between counts.

This should be well within expectations for UK weather.

As an aside, the full 12-bits would allow for a rainfall of 1144mm per counting interval, or 4.5 metres of rain per hour.

Schematic

With a little supporting hardware to be added, the schematic looks as follows:

Weather Station Partial Schematic

Weather Station Partial Schematic

Note that the BME280 and TSL2561 are also on the schematic.

The rain gauge counter bit values have been labeled RXQx, RG = Rain Gauge and Q is the standard notation for a bit in a logic design.

Software

The MCP23017 (I2C version of the IO expander) is sold by Adafruit. They have a standard Ardunio library for this component and so in the interest of code re-use, expediency and idleness this will be used.

So the first thing to do is to create an instance of the MCP23017 IO expander:

Adafruit_MCP23017 _outputExpander;      // MCP23017, I2C controlled 16-port output.

Next up, the output expander needs to be setup. Looking at the schematic, the output from the counter is mapped to port B on the MCP23017. This maps to IO pins 8 to 15 inclusive. These bits should be set to inputs. One final piece of configuration is the reset pin, this needs to be set to output:

//
//  Set up the pluviometer, zero the counts and then attached and initialise the
//  counter to the output expander.
//
void WeatherSensors::SetupRainfallSensor()
{
    _pluviometerPulseCount = 0;
    _pluviometerPulseCountToday = 0;
    //
    //  Attach the rainfall counter to the output expander.
    //
    int index;
    for (index = 8; index < 16; index++)
    {
        _outputExpander.pinMode(index, INPUT);
    }
    //
    //  Attach the counter reset pin to the output expander and reset the counter.
    //
    _outputExpander.pinMode(PIN_RAINFALL_RESET, OUTPUT);
    ResetPluviometerPulseCounter();
}

As well as setting up the pulse counter, the above code resets internal counters for rainfall today and also resets the pulse count.

Reading the pulse counter is a simple matter to reading the state of each bit starting at 0:

//
//  Read the counter from the Rainfall sensor then reset the count.
//
void WeatherSensors::ReadRainfallSensor()
{
    byte mask = 1;

    _pluviometerPulseCount = 0;
    for (int index = 8; index < 15; index++)
    {
        if (_outputExpander.digitalRead(index) == 1)
        {
            _pluviometerPulseCount |= mask;
        }
        mask <<= 1;
    }
    _pluviometerPulseCountToday += _pluviometerPulseCount;
    ResetPluviometerPulseCounter();
}

Accuracy

The eagle-eyed amongst you will have spotted a small flaw in the above logic, namely that the pulse counter could increment whilst the system is reading the values. It turns out that this matters a great deal in some cases. Consider the following:

The pulse counter has counter 15 pulses (0b00001111). The software is processing these bit values and has processed bit 2, counter value = 7. A pulse is registered by the counter and the pulse count becomes 16 (0b00010000). The system processes bit 3 and finds it is 0 and then moves on to bit 4 and finds this is 1, i.e. 16. The counter then becomes 16 + 7 = 23 instead of 15. A little bit difficult to see, so lets put this in tabular form:

Bit Value Decimal Value Cumulative Sum Counter
0 1 1 1 00001111
1 1 2 3 00001111
2 1 4 7 00001111
Counter increments here 00010000
3 0 0 7 00010000
4 1 16 23 00010000
5 0 0 23 00010000
6 0 0 23 00010000
7 0 0 23 00010000

The counter column contains the value in the 74HC4040 counter chip (lower 8 bits only).

The problem occurs because the reading of the pulses is not instantaneous, the software takes longer to process the data than the counter takes to register a clock pulse and update the counter.

There are two possible solutions to this, both left for later investigation:

  1. Add an AND gate between the incoming rain gauge pulse and the 74HC4040 clock pin. This can be turned off when taking a reading.
  2. Check the documentation for the MCP23017 to see if the values can be latched and then read. Latching the values means that they cannot be changed; once latched and the values being read would be consistent.

Both offer a solution to the problem and will be left as a later refinement.

Conclusion

The pluviometer is a seemingly simple switch but it needs to run constantly. This presents a challenge when considering the requirement to save power where possible. This article presented a possible solution using a small number of components bringing powered on permanently.

The solution does introduce a possible error in the reading, although this should be reasonably small. A full analysis will be presented in a future article.

Weather Station Proof of Concept Software

April 3rd, 2016 • Electronics, ESP8266, Internet of Things, Software Development1 Comment »

The weather station project is still at the proof of concept stage but the last few articles have run through the concepts for connecting several sensors to the Digistump Oak microcontroller. In this article we will look at the basic (and I stress basic) software required to connect the sensors discussed so far with the microcontroller and start to collect data.

Hardware – So Far

In the previous articles the following sensors have been discussed:

These sensors are currently connected to a piece of breadboard along with the Oak.

Now we have the sensors connected we need to add some software goodness.

Software Requirements

The first and most obvious requirement is to be able to collect data from the above sensors. In addition the proof of concept software should also permit the following:

  1. Test logging data to the cloud
  2. Serial debugging
  3. Setting the system time from the Internet

Data logging to the cloud will initially be to the Sparkfun Data Service as this is a simple enough service to use. One of the first things to do is to create an account / data stream in order to permit this.

Development Environment

The Arduino development environment can be used to program the Oak and it has the advantage that it is available across multiple environments. Digistump recommend using version 1.6.5 of the Arduino environment as there are known issues with more current versions.

Libraries

In previous articles it was noted that Sparkfun and Adafruit provide libraries for two of the I2C sensor boards being used (BME280 and TSL2561). An additional library is also required to support the third objective, setting the time from the Internet.

The additional libraries are installed from the Sketch -> Include Library -> Manage Libraries.. dialog. Open this dialog and install the following libraries:

  • Adafruit Unified Sensor (1.0.2)
  • Adafruit BME280 (1.0.3)
  • Sparkfun TSL2561 (1.1.0)
  • NtpClientLib by German Martin (1.3.0)

The version numbers are the ones available at the time of writing.

Software Walk Through

At this point the hardware should be in place and all of the necessary libraries installed. Let the coding begin.

This first thing we need is the includes for the libraries that are going to be used:

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <Time.h>
#include <NtpClientLib.h>
#include <SparkFunTSL2561.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <Wire.h>
#include <SPI.h>

The SPI library is not going to be used in this example but will be required in future articles.

Next up we need some definitions and global variables to support the sensors:

//
//  Definitions used in the code for pins etc.
//
#define VERSION           "0.04"
//
#define PIN_SLEEP_OR_RUN  7
#define PIN_ONBOARD_LED   1
#define PIN_ANEMOMETER    5
#define PIN_PLUVIOMETER   8
//
#define SLEEP_PERIOD      60

//
//  Light sensor (luminosity).
//
SFE_TSL2561 light;

//
//  Create a Temperature, humidity and pressure sensor.
//
Adafruit_BME280 bme;
float temperature;
float pressure;
float humidity;

//
//  TLS2561 related globals.
//
boolean gain;       //  Gain setting, 0 = X1, 1 = X16;
unsigned int ms;    //  Integration ("shutter") time in milliseconds
double lux;         //  Luminosity in lux.
boolean good;       //  True if neither sensor is saturated

//
//  Ultraviolet analog reading.
//
int ultraviolet;
#define UV_GRADIENT           0.12
#define MAXIMUM_ANALOG_VALUE  1023
#define REFERENCE_VOLTAGE     3.3
#define UV_OFFSET             1.025

//
//  Buffer for messages.
//
char buffer[256];
char number[20];

//
//  NTP class to provide system time.
//
ntpClient *ntp;

//
//  Wind Speed sensor, each pulse per second represents 1.492 miles per hour.
//
volatile int windSpeedPulseCount;
#define WINDSPEED_DURATION      5
#define WINDSPEED_PER_PULSE     1.492

//
//  We are logging to Phant and we need somewhere to store the client and keys.
//
#define PHANT_DOMAIN        "data.sparkfun.com"
#define PHANT_PAGE          "/input/---- Your stream ID goes here ----"
const int phantPort = 80;
#define PHANT_PRIVATE_KEY   "---- Your Private Key goes here ----"

Before we progress much further it should be acknowledged that some of the code for the BMS280 and the TSL2561 is modified from the Adafruit and Sparkfun example applications.

A key point to note from the above code is the definition of the windSpeedPulseCount variable. Note the use of the volatile keyword. This tells the compiler not to optimise the use of this variable.

Next up is some support code. Two methods are initially required, ftoa adds the limited ability to convert a floating point number to a char * for debugging. The second method outputs a debugging message. This has been abstracted to allow for possible network debug messages later in the project. At the moment the serial port will be used.

//
//  Output a diagnostic message if debugging is turned on.
//
void DebugMessage(String message)
{
    Serial.println(message);
}

//
//  Convert a float to a string for debugging.
//
char *ftoa(char *a, double f, int precision)
{
    long p[] = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
 
    char *ret = a;
    long integer = (long) f;
    itoa(integer, a, 10);
    while (*a != '\0')
    {
        a++;
    }
    if (precision != 0)
    {
      *a++ = '.';
      long decimal = abs((long) ((f - integer) * p[precision]));
      itoa(decimal, a, 10);
    }
    return ret;
}

TSL2561 – Luminosity Sensor

The next group of methods deal with the luminosity sensor, the TSL2561. these methods are slightly modified versions of the example code from Sparkfun. It is envisaged that future versions of these methods will deal with saturation and low light levels by dynamically changing the way the sensor works. For the proof of concept the basic code should suffice:

//
//  Convert the error from the TSL2561 into an error that a human can understand.
//
void PrintLuminosityError(byte error)
{
    switch (error)
    {
        case 0:
            DebugMessage("TSL2561 Error: success");
            break;
        case 1:
            DebugMessage("TSL2561 Error: data too long for transmit buffer");
            break;
        case 2:
            DebugMessage("TSL2561 Error: received NACK on address (disconnected?)");
            break;
        case 3:
            DebugMessage("TSL2561 Error: received NACK on data");
            break;
        case 4:
            DebugMessage("TSL2561 Error: other error");
            break;
        default:
            DebugMessage("TSL2561 Error: unknown error");
    }
}

//
//  Set up the luminsoity sensor.
//
void SetupLuminositySensor()
{
    light.begin();

    // Get factory ID from sensor:
    // (Just for fun, you don't need to do this to operate the sensor)
    unsigned char id;
  
    if (light.getID(id))
    {
        sprintf(buffer, "Retrieved TSL2561 device ID: 0x%x", id);
        DebugMessage(buffer);
    }
    else
    {
        byte error = light.getError();
        PrintLuminosityError(error);
    }
 
    // The light sensor has a default integration time of 402ms,
    // and a default gain of low (1X).
  
    // If you would like to change either of these, you can
    // do so using the setTiming() command.
    
    // If gain = false (0), device is set to low gain (1X)
    // If gain = high (1), device is set to high gain (16X)  
    gain = 0;
 
    // If time = 0, integration will be 13.7ms
    // If time = 1, integration will be 101ms
    // If time = 2, integration will be 402ms
    // If time = 3, use manual start / stop to perform your own integration
    unsigned char time = 2;
  
    // setTiming() will set the third parameter (ms) to the
    // requested integration time in ms (this will be useful later):
    light.setTiming(gain, time, ms);
  
    // To start taking measurements, power up the sensor:
    
    DebugMessage((char *) "Powering up the luminosity sensor.");
    light.setPowerUp();
}

//
//  Read the luminosity from the TSL2561 luminosity sensor.
//
void ReadLuminositySensor()
{
    unsigned int data0, data1;
    
    if (light.getData(data0, data1))
    {
        sprintf(buffer, "TSL2561 data: 0x%04x, 0x%04x", data0, data1);
        DebugMessage(buffer);
        //
        //  To calculate lux, pass all your settings and readings to the getLux() function.
        //
        //  The getLux() function will return 1 if the calculation was successful, or 0 if one or both of the sensors was
        //  saturated (too much light). If this happens, you can reduce the integration time and/or gain.
        //  For more information see the hookup guide at: 
        //  https://learn.sparkfun.com/tutorials/getting-started-with-the-tsl2561-luminosity-sensor
        //

        //
        // Perform lux calculation.
        //
        double localLux;
        good = light.getLux(gain ,ms, data0, data1, localLux);
        if (good)
        {
            lux = localLux;
        }
    }
    else
    {
        byte error = light.getError();
        PrintLuminosityError(error);
    }
}

//
//  Log the luminosity data to the debug stream.
//
void LogLuminosityData()
{
    sprintf(buffer, "Lux: %s", ftoa(number, lux, 2));
    DebugMessage(buffer);
}

BME280 – Air Temperature, Pressure and Humidity Sensor

Dealing with this sensor is simpler than the luminosity sensor as can be seen from the code below:

//
//  Setup the Adafruit BME280 Temperature, pressure and humidity sensor.
//
void SetupTemperaturePressureSensor()
{
    if (!bme.begin())
    {
        DebugMessage("Could not find a valid BME280 sensor, check wiring!");
    }
    else
    {
        DebugMessage("BME280 sensor located on I2C bus.");
    }
}

//
//  Log the data from the temperature, pressure and humidity sensor.
//
void LogTemperaturePressureData()
{
    sprintf(buffer, "Temperature: %s C", ftoa(number, temperature, 2));
    DebugMessage(buffer);
    sprintf(buffer, "Humidity: %s %%", ftoa(number, humidity, 2));
    DebugMessage(buffer);
    sprintf(buffer, "Pressure: %s hPa", ftoa(number, pressure / 100, 0));
    DebugMessage(buffer);
}

//
//  Read the data from the Temperature, pressure and humidity sensor.
//
void ReadTemperaturePressureSensor()
{
    temperature = bme.readTemperature();
    pressure = bme.readPressure();
    humidity = bme.readHumidity();
}

This group of methods follows a similar line to the TSL2561 sensor methods, namely, setup, read and log methods.

ML8511 – Ultraviolet Light Sensor

This sensor is the simplest so far as it provides a simple analog value representing the intensity of the ultraviolet light falling on the sensor.

//
//  Read the ultraviolet light sensor.
//
void ReadUltravioletSensor()
{
    ultraviolet = analogRead(A0);
}

//
//  Log the reading from the ultraviolet light sensor.
//
void LogUltravioletData()
{
    char num[20];
    
    sprintf(buffer, "Ultraviolet light: %s", itoa(ultraviolet, num, 10));
    DebugMessage(buffer);
}

Note that this sensor does allow for an enable line. This can be used to disable the sensor and put it into a low power mode if necessary. This will not be used in the proof of concept will be considered when the project moves to the point where power supplies / solar power is considered.

Wind Speed Sensor Interrupt Service Routine (ISR)

This method looks trivial and it is:

//
//  ISR to count the number of pulses from the anemometer (wind speed sensor).
//
void IncreaseWindSpeedPulseCount()
{
    windSpeedPulseCount++;
}

The non-trivial thing to remember about this code is that the method changes a global variable. The volatile keyword used in the variable definition is necessary to stop the compiler from optimising the global variable as this can have side effects.

Data Logging to Sparkfun’s Data Service

Assuming that we have a WiFi connection then we can log the data collected to the Sparkfun data service (this is based upon Phant).

//
//  Post the data to the Sparkfun web site.
//
void PostDataToPhant()
{
    String url = PHANT_PAGE "?private_key=" PHANT_PRIVATE_KEY "&airpressure=";
    url += ftoa(number, pressure / 100, 0);
    url += "&groundmoisture=0";
    url += "&groundtemperature=0";
    url += "&temperature=";
    url += ftoa(number, temperature, 2);
    url += "&humidity=";
    url += ftoa(number, humidity, 2);
    url += "&luminosity=";
    url += ftoa(number, lux, 2);
    url += "&rainfall=0";
    double uvStrength = (((double) ultraviolet) / MAXIMUM_ANALOG_VALUE) * REFERENCE_VOLTAGE;
    if (uvStrength < UV_OFFSET)
    {
        uvStrength = 0;
    }
    else
    {
        uvStrength = (uvStrength - UV_OFFSET) / UV_GRADIENT;
    }
    url += "&ultravioletlight=";
    url += ftoa(number, uvStrength, 2);
    url += "&winddirection=0";
    double windSpeed = windSpeedPulseCount / WINDSPEED_DURATION;
    windSpeed *= WINDSPEED_PER_PULSE;
    url += "&windspeed=";
    url += ftoa(number, windSpeed, 2);
    //
    //  Send the data to Phant (Sparkfun's data logging service).
    //
    HTTPClient http;
    http.begin(PHANT_DOMAIN, phantPort, url);
    int httpCode = http.GET();
    sprintf(buffer, "Status code: %d", httpCode);
    DebugMessage(buffer);
    String response = http.getString();
    sprintf(buffer, "Phant response code: %c", response[3]);
    DebugMessage(buffer);
    if (response[3] != '1')
    {
        //
        //  Need to put some error handling here.
        //  
    }
    http.end();
}

The stream has been set up to collect more data than is currently collected, for instance, ground temperature. Any parameter not measured at the moment is set to 0.

Some other things to consider following the proof of concept:

  1. Error handling for network issues
  2. Possible offline collection of data
  3. Using a local version of Phant

Something to bear in mind after the project moved from proof of concept.

Data Collection

The majority of the sensor have a read method to collect the data from the sensor. The only exception at the moment is the wind speed sensor. The data collection is performed inside the main method for collecting and logging the sensor readings:

//
//  Raad the sensors and publish the data.
//
void ReadAndPublishSensorData()
{
    digitalWrite(1, HIGH);
    DebugMessage("\r\nCurrent time: " + ntp->getTimeString());
    ReadLuminositySensor();
    ReadTemperaturePressureSensor();
    LogLuminosityData();
    LogTemperaturePressureData();
    ReadUltravioletSensor();
    LogUltravioletData();
    //
    //  Read the current wind speed.
    //
    DebugMessage("Reading wind speed.");
    windSpeedPulseCount = 0;
    attachInterrupt(PIN_ANEMOMETER, IncreaseWindSpeedPulseCount, RISING);
    delay(WINDSPEED_DURATION * 1000);
    detachInterrupt(PIN_ANEMOMETER);
    //
    PostDataToPhant();
    digitalWrite(1, LOW);
}

Reading the wind speed is performed through the ISR described above. The algorithm is simple:

  1. Clear the count of the number of revolutions (pulses) from the sensor
  2. Attach an interrupt to the sensor (the interrupts increments the count every revolution of the sensor)
  3. Wait for a know number of seconds (in this case 5)
  4. Detach the interrupt to stop the count

By using this method we can provide an average over a number of seconds and the wind speed can be calculated as:

Wind Speed = (Revolution count / number of seconds) * 1.492

This is the calculation performed in the PostDataToPhant method.

Setup and Loop

The final things needed by an application developed in the Arduino environment are the setup and loop methods. So let’s start looking at the setup method.

//
//  Setup the application.
//
void setup()
{
    Serial.begin(9600);
    Serial.println("\r\n\r\n-----------------------------\r\nWeather Station Starting (version " VERSION ", built: " __TIME__ " on " __DATE__ ")");
    Serial.println();
    //
    //  Connect to the WiFi.
    //
    Serial.print("\r\nConnecting to default network");

At the start of setup we need to rely upon the Serial object being available and so there are no calls to the DebugMessage method as this may be modified later to use networking for debugging. The next step is to try and connect to the network:

    WiFi.begin();
    while (WiFi.status() != WL_CONNECTED) 
    {
      delay(500);
      Serial.print(".");
    }
    Serial.println("");
    Serial.print("WiFi connected, IP address: ");
    Serial.println(WiFi.localIP());

At this point the application will be either looping indefinitely until the network becomes available or we will have an IP address output to the serial port. In a more complete application this will start the logging process and periodically try to connect to the network when the network is unavailable. This is only a proof of concept after all.

There is currently no Real Time Clock (RTC) attached to the system and so we need to check the network time at startup.

    //
    //  Get the current date and time from a time server.
    //
    DebugMessage("Setting time.");
    ntp = ntpClient::getInstance("time.nist.gov", 0);
    ntp->setInterval(1, 1800);
    delay(1000);
    ntp->begin();
    while (year(ntp->getTime()) == 1970)
    {
        delay(50);
    }

This block of code loops until the network time is set correctly. At start up the year will be set to a default value of 1970, this is why the code loops until the year is something other than 1970.

Next up, setup the sensors and take our first reading:

    //
    //  Set up the sensors and digital pins.
    //
    SetupLuminositySensor();
    SetupTemperaturePressureSensor();
    pinMode(PIN_ONBOARD_LED, OUTPUT);
    pinMode(PIN_ANEMOMETER, INPUT);
    //
    //  Read the initial data set and publish the results.
    //
    ReadAndPublishSensorData();
}

Everything is setup, only thing left is to continue to collect and publish the data, enter loop:

//
//  Main program loop.
//
void loop()
{
    delay(SLEEP_PERIOD * 1000);
    ReadAndPublishSensorData();
}

Example Output

Running the above code results in the following output in the serial monitor (note that some data (IP addresses has been modified):

   
-----------------------------
Weather Station Starting (version 0.04, built: 06:46:39 on Apr  3 2016)


Connecting to default network...........
WiFi connected, IP address: 192.168.xxx.yyy
Setting time.
Retrieved TSL2561 device ID: 0x50
Powering up the luminosity sensor.
BME280 sensor located on I2C bus.

Current time: 06:48:45 03/04/2016
TSL2561 data: 0x0373, 0x004f
Lux: 399.65
Temperature: 18.89 C
Humidity: 52.86 %
Pressure: 1006 hPa
Ultraviolet light: 307
Reading wind speed.
Status code: 200
Phant response code: 1

The current public data stream for this service should be viewable here.

Checking this stream you can see the resulting data that was sent to Sparkfun for the above serial output:

Phant Data From Weather Station Proof of Concept

Phant Data From Weather Station Proof of Concept

One thing to remember when comparing the above data is that the Sparkfun data stream logs data using UTC and the application above was running during British Summer Time.

Conclusion

The code above is a proof of concept, it is not error proof nor does it take into account power usage or optimisation, it is merely meant to prove that the sensors can be read and the data output to a cloud service.

Next steps for the proof of concept:

  1. How to handle two sensors requiring an analog conversion
  2. The project is running out of digital pins
  3. Real time clocks
  4. Offline data logging

All of this before even looking at the location and powering the project in the wild (OK, my garden).

Back right after this break…

Wind Speed and Ultraviolet Light Sensors

April 2nd, 2016 • Electronics, ESP8266, Internet of Things1 Comment »

Last time we looked at the I2C Sensors that are part of the weather station. At the end of the article it was noted that these were working and able to log data to the Particle ecosystem.

Now it is time for the analog sensors:

  1. Wind speed
  2. Wind direction
  3. Rainfall
  4. Ultraviolet light

The Ultraviolet light and wind direction sensor will present us with a problem as they are both analog sensors and the Oak only has one Analog-to-Digital Convertor (ADC).

The rainfall gauge and the wind speed sensor both use a similar technology to generate a signal, namely a magnet that will trigger a reed switch. These two sensors also present an issue; while they are simple enough there are a finite number of pins on the Oak and they are being consumed at a fair rate.

ML8511 – Ultraviolet Light Sensor

The ML8511 measures ultraviolet light at a wavelength of 365nM. This is at the top end of the UVB band, the band which is harmful to living tissue. The sensor generates a voltage that is linear and proportional to the intensity of the UV light. The intensity of the light measured is on the scale 0 to 15 mW/cm2. The following chart is taken from the data sheet:

ML8511 UV Plot

ML8511 UV Plot

The chart above is generated when the supply voltage is 3.0V but the system under development will be using a 3.3V supply. Some investigation is required to determine the output voltage when there is no UV present. After running the sensor under constant temperature conditions with no UV light present the ADC was generating a reading in the 316 – 320 range. This gives out output voltage for the sensor in the range 1.02V – 1.03V.

If we assume that the output characteristics of the sensor remain linear at 3.3V and the gradient remains the same then we can generate a formula for calculating the intensity of the UV light based upon the sensor output.

All linear graphs can be represented by the following formula:

y = mx + c

Where:

  • y is the y coordinate (in our case the voltage output of the sensor)
  • x is the x coordinate (in our case the intensity of the UV light)
  • m is the gradient of the line
  • c is a constant offset of the y coordinate

If we set x to 0 then the equation becomes y = c This represents the output of the sensor when there is no UV light present. As we have seen from the above experiment, this is in the range 1.02V – 1.03V. So as an approximation we will use the value c = 1.025V.

The gradient of any line is represented by the following formula:

m = deltaY / deltaX

When given two points on a line (x1, y1) and (x2, y2), then the gradient becomes:

m = (y2 – y1) / (x2 – x1)

Luckily the data sheet gives us two points on the line at 25C, namely the output voltage in shade and the output voltage at 10mW/cm2. So assuming shade represents no ultraviolet light then the gradient of the line becomes:

m = (2.2 – 1.0) / (10 – 0)

So m is 0.12. Plugging m and c back into the original equation gives:

y = 0.12x + 1.025

Solving for x gives:

x = (y – 1.025) / 0.12

Or to put it in context:

UV Intensity = (Sensor output in volts – 1.025) / 0.12 mW / cm2

There are some assumptions in the above work and the data sheet shows that the output voltage can vary but we have a method for calculating the intensity of UV light; accurate enough for a home project anyway.

Wind Speed

Wind speed is measured by an anemometer. The anemometer in the Weather Sensor kit is a cup anemometer:

Cup Anemometer

Cup Anemometer

This has a magnet on the spindle connected to the cups. The magnet closes (and opens) a reed switch each time it passes the switch. So one pulse is generated per full revolution of the spindle. Each full revolution of the spindle (per second) represents a wind speed of 1.492 miles per hour (mph) or 2.4 km/h.

For the experienced, this sounds simple but we all know there could be a nasty shock in store for us, namely switch bounce. An easy way to find out, hook the output up to an oscilloscope.

The circuit is simple enough, connect one switch contact to 3.3V, one to a resistor and the other end of the resistor to ground. Connect the scope to the resistor / switch junction.

The scope was set up to have a trigger voltage of about 1.5V and to trigger on the falling edge of a signal. The cups on the anemometer were then position so that the reed switch was closed (a high output on the scope) and the scope setup in single shot mode (to capture and hold the trace when triggered). Spinning the anemometer gave the following output:

Anemometer Switch Bounce

Anemometer Switch Bounce

As you can see, the switch does bounce. Switch debouncing is a well known and documented problem, in fact I have written about it here so we will not go too deep into the problem in this article. The solution that will be used is a simple RC circuit:

MSP430 Launchpad Debounce Circuit

MSP430 Launchpad Debounce Circuit

The principle is that the RC circuit resists change and so filters out the glitch in the above trace. So we need a filter that is resistive enough to filter out the glitch but fast enough to respond in the minimum time between pulses.

The weather station is going to be located in the mainland UK, about 30 miles from the coast. In this location the wind speed is unlikely to rise above 60-80 mph unless in extreme conditions (tornadoes are known to occur in the UK). So assuming the maximum wind speed in 149.2 mph (this number is based upon the fact that 1.492 mph gives one revolution per second) then we have a maximum number of rotations of 100 per second. This gives a revolution time of 10 milliseconds.

So we have a 10 millisecond window for the pulse from the sensor. The pulse will be low for the majority of the time as the switch can only close when the magnet is above the switch, so for most of the 10ms we will have a low pulse. You can see this in the following trace:

Anemometer Duty Cycle

Anemometer Duty Cycle

The signal appears to be high for 30% of the time. An accurate measurement could be made using the scope but it does not appear to be necessary. In our case, 10 milliseconds per rotation, the signal would be low for approximately 70% of the time, i.e. 7 milliseconds.

Going back to the first trace should the switch bounce it is observed that the switch bounce lasts for approximately 50-60 microseconds. Several observations of both the rising and falling edges showed this to be reasonable consistent. This final piece of information helps to define the parameters for this problem:

  • The frequency of the pulses should be 100Hz maximum (i.e. 10 milliseconds between pulses)
  • Duty cycle is 30% (high for 3 milliseconds, low for 7 milliseconds)
  • Switch bounce can last 50-60 microseconds (assume 100 microseconds as a worst case)
  • Trigger voltage is 2.3V with a supply voltage of 3.3V

There are plenty of online calculators for this type of circuit, Layada has one here that covers this type of scenario. We know the supply voltage (3.3V) and the trigger voltage (2.3V) so the only thing to do is look at the components available and calculate the delay time. After a few tries a 10K resistor and a 100nF capacitor were found to give a delay of 1.1939 milliseconds. This covers our case with plenty of margin for error.

Putting together the circuit above where the anemometer is the switch and triggering on the rising edge gives the following output on the oscilloscope:

Anemometer Gradual Rise

Anemometer Gradual Rise

The rise time looks to be acceptable, smooth and slow enough to iron out the glitches but fast enough to allow a 10 millisecond duration with a 30% duty cycle.

Conclusion

At the start of the article it was noted that four analog sensors are present in the kit. This article has concentrated on just two, the UV and Wind Speed sensors. The Rainfall and Wind Direction sensors will be covered in future articles.

The two sensors here will require one digital pin and one analog pin. As there is only one analog pin on the Oak then some creative thinking will be required in order to connect the Wind Direction sensor.

Assuming a maximum wind velocity of 149.2 mph then we will have 100 rotations of the anemometer per second. This should be something that could be measured by the Oak using an interrupt on one of the digital pins

At this point the hardware for six measurements can be put together on breadboard and some prototype software put together. This is the subject of the next article, Proof of Concept Software

Reading I2C Sensor Data with the Oak

April 1st, 2016 • Electronics, ESP8266, Internet of Things, Software Development2 Comments »

The weather station project will be bringing together a number of sensors, light, ultraviolet light, air pressure, humidity, wind speed, wind direction and rain fall. This collection of sensors falls into three groups:

  • Electronic sensors on an I2C bus
  • Mechanical sensors using switches
  • Analog sensors

The current plan is for the weather station to use the Oak as the microcontroller running the show. The data from the sensors can then be uploaded to the cloud, destination to be determined, but let’s start with Sparkfun’s data service.

The I2C sensors will require the least amount of work to get up and running so let’s start with those. The two sensors operating on the I2C bus are:

Oak and  2C Sensors on Breadboard

Oak and 2C Sensors on Breadboard

One of the great things about working with these two sensors is the fact that there are prebuilt drivers and example code for both breakout boards available from Github. What could be simpler, well head over to the Sketch – Include Library – Board Manager… menu in the Arduino IDE and you can download the library and have the IDE install it for you.

TSL2561 – Luminosity Sensor

This sensor allows the radiance of the light to be calculated in a way that approximates the response of the human eye. It does this by combining the input from two photodiodes, one infra-red only and one visible light and infra-red light combined. The output from the two sensors can be used to luminous emittance in lux (lumens per square metre).

The following table gives an idea of the lux values for typical scenarios:

Lux Typical Environment
0.0001 Moonless, overcast night sky
0.002 Moonless clear night sky
0.27–1.0 Full moon on a clear night
80 Office building hallway
320–500 Office lighting
1000 Overcast day
10000–25000 Daylight

As you can see from the table above, the lux values for a “normal” human day can vary dramatically. The sensor copes with this by allowing the use of a variable time window and sensitivity when taking a reading. Effectively the sensor accumulates the readings over the time window (integration interval) into a single 16-bit number which can then be used to calculate the lux reading.

BME280 – Air Pressure, Temperature and Humidity Sensor

This sensor is produced by Bosch and is packaged in both I2C and SPI configuration on the same board. The accuracy of the sensor appears good, pressure and temperature both to 1% and humidity to 3%.

Libraries

Both Sparkfun and Adafruit have provided libraries and example code for the boards. These were easy to add to the development environment.

One caveat, the BME280 requires the addition of the Adafruit sensor library as well as the BME280 library.

Once added it was a simple case of wiring up the sensor to 3.3V and the I2C bus and running the example code.

They both worked first time.

Some Code Modifications (for later)

The light sensor has been show to work in low light conditions but not to any degree of precision. A possible modification to the example code is to look at the sensitivity and integration window settings to see if the precision can be adjusted to make the sensor return better readings in low light.

Some of the values when calculated use the fractional part of a floating-point number, temperature and humidity spring to mind. This meant adding a method to convert a double into it’s string representation for debugging purposes. Trivially solved but an annoying omission from the implementation of sprintf.

Posting to Particle Dashboard

The Oak can also post to the Particle dashboard providing a second method of debugging your application. Statements such as Particle.publish(“Debug”, “Temperature data…”); will cause the string Temperature data… to be posted in a group/attribute Debug

So the readings from the office look like this:

Some Weather Data

Some Weather Data

Conclusion

Two simple to use sensors with good supported class libraries make these sensors quick and easy to hook up to the Oak. Merging the two examples was simple and sensor data is now appearing over the serial output from the Arduino.

Compiling the code gives some warnings about the I2C library being compiled for the ESP8266 whilst the target board is defined as an Oak. This can be ignored as the Oak is really a convenient wrapper around an ESP8266 module.

Next up, some analog sensors to measure ultraviolet light and wind and rain properties.

Setting Up the Oak – Flashing LED

March 27th, 2016 • Electronics, ESP8266, Internet of Things, Software DevelopmentComments Off on Setting Up the Oak – Flashing LED

The Oak microcontroller is new, Digistump only shipped version 1 firmware a few days ago (20th Match 2016). The hardware I have was shipped sometime in January. So the first thing to do is to upgrade the firmware and then try blinking an LED.

Upgrading the firmware

The Digistump Wiki contains a number of tutorials and troubleshooting guides. First stop the Connecting Your Oak for the First Time page. This shows how the Oak can be connected to your WiFi network and the firmware updated.

The initial over the air firmware update was problematic to say the least. Reading through the Digitsump forums it seems that I am not the only one having a problem with the first update. There are three methods for upgrading the firmware:

  • Over the Air using firmware from the internet
  • Over the air with a local server
  • Serial using pyserial or esptool

I started at the top of the list and slowly worked my way down. In the end the only method that worked for me was the serial update.

Flashing an LED

With the latest version of the firmware installed it is time to test the development environment. What could be simpler than flashing and LED. The on board LED is connected to pin 1 so lets try and use that.

Digistump offer two development environments:

At the time of writing there was a known issue with the Particle development environment which prevented an application being built and flashed successfully. This is an early release and so issues are expected.

This only leaves the Arduino environment, an IDE I really hate.

There are two methods for flashing an application to the Oak using the Arduino environment:

  • Over The Air (OTA)
  • Serial (requires Python)

Despite the earlier problems with the firmware update I decided to try the OTA method first. DigitStump have provided compehensive instructions in their WiKi on how to achieve this.

Following the example was easy and after only 15 minutes I have the LED on the Oak flashing at 1Hz. Just to prove it was not a fluke I then tried changing the frequency and reflashing the Oak.

Success!

Conclusion

My initial frustration with the firmware update was soon forgotten once I had an application successfully running on the Oak. I am hoping that the issues were caused by the fact I have an early release of the board with the original firmware installed.

Programming is easy enough and can be done over the air which is convenient.

Next up, talking to sensors.