Archive for the ‘Software Development’ Category

One of the current projects on the go is a level shifter for the Teensy 3.6 using the TXS0108E chip. The aim is to allow the use of as many of the Teensy’s GPIO pins as possible to allow the development of another project that is working on 5V logic levels.

This project reminded me that when putting together a microcontroller project that the system is made up of both hardware and software. Sometimes a design decision made in one element can have an adverse effect on the other.

Design Decisions

From the start it was decided that the GPIO pins would have a one to one mapping from the microcontroller to the external bus. So pin 1 on the microcontroller would map to pin 1 on the external connectors.

This would make coding easy when using the Arduino API. So connecting to the external bus and outputting a digital high signal on pin 1 would become:

pinMode(1, OUTPUT);
digitalWrite(1, HIGH);

Impact

Putting the circuit together in KiCAD resulted in the following design:

TXS0108E Schematic

As you can see, the Teensy GPIOS (TIO-1…) mapped directly to the external bus (GPIO-1…)

When translated into the rats nest there were three occurrences of the following:

PCB Layout

Routing this was going to be a nightmare.

Changing the Design

At this point the penny dropped that a small change in the software would make the routing a whole lot easier.

Instead of using the pin numbers directly, a #define could be used for the external bus pin numbers. The above snippet would become:

The Manchester Small Scale Experimental Machine (SSEM) or Manchester Baby as it became known, was the first computer capable of executing a stored program. The Baby was meant as a proving ground for the early computer technology having only 32 words of memory and a small instruction set.

As you can see, it is a large machine weighing in at over 1 ton (that’s imperial, not metric, hence the spelling).

Manchester Baby Architecture

A full breakdown on the Manchester Baby’s architecture can be found in the Wikipedia article above as well as several PDFs, all of which can be found online. The following is meant as a brief overview to present some background to the Python code that will be discussed later.

Storage Lines

Baby used 32-bit words with numbers represented in twos complement form. The words were stored in store lines with the least significant bit (LSB) first (to the left of the word). This is the reverse of most modern computer architectures where the LSB is held in the right most position.

The storage lines are equivalent to memory in modern computers. Each storage line contains a 32-bit value and the value could represent an instruction or data. When used as an instruction, the storage line is interpreted as follows:

Bits

Description

0-5

Storage line number to be operated on

6-12

Not used

13-15

Opcode

16-31

Not used

As already noted, when the storage line is interpreted as data then the storage line contains a 32-bit number stored in twos complement form with the LSB to the left and the most significant bit (MSB) to the right.

This mixing of data and program in the same memory is known as von Neumann architecture named after John von Neumann. For those who are interested, the memory architecture where program and data are stored in separate storage areas is known as a Harvard architecture.

SSEM Instruction Set

Baby used three bits for the instruction opcode, bits 13, 14 and 15 in each 32 bit word. This gave a maximum of 8 possible instructions. In fact, only seven were implemented.

Binary Code

Mnemonic

Description

000

JMP S

Jump to the address obtained from memory address S (absolute jump)

100

JRP S

Add the value in store line S to the program counter (relative jump)

010

LDN S

Load the accumulator with the negated value in store line S

110

STO S

Store the accumulator into store line S

001 or 101

SUB S

Subtract the contents of store line S from the accumulator

011

CMP

If the accumulator is negative then add 1 to the program counter (i.e. skip the next instruction)

111

STOP

Stop the program

When reading the above table it is important to remember that the LSB is to the left.

Registers

Baby had three storage areas within the CPU:

Current Instruction (CI)

Present Instruction (PI)

Accumulator

The current instruction is the modern day equivalent of the program counter. This contains the address of the instruction currently executing. CI is incremented before the instruction is fetched from memory. At startup, CI is loaded with the value 0. This means that the first instruction fetched from memory for execution is the instruction in storage line 1.

The present instruction (PI) is the actual instruction that is currently being executed.

As with modern architecture, the accumulator is used as a working store containing the results of any calculations.

Python Emulator

A small Python application has been developed in order to verify my understanding of the architecture of the Manchester Baby. The application is a console application with the following brief:

Read a program from a file and setup the store lines

Display the store lines and registers before the program starts

Execute the program displaying the instructions as they are executed

Display the store lines and registers at the end of the program run

The application was broken down into four distinct parts:

Register.py – implement a class containing a value in a register or storage line

StoreLines.py – Number of store lines containing the application and data

CPU.py – Implement the logic of the CPU

ManchesterBaby.py – Main program logic, reading a program from file and executing the program

The source code for the above along with several samples and test programs can be fount on Github.

Register.py

A register is defined as a 32-bit value. The Register class stores the value and has some logic for checking that the value passed does not exceed the value permitted for a 32-bit value. Note that no other checks or interpretation of the value is made.

StoreLines.py

StoreLines holds a number of Registers, the default when created is 32 registers as this maps on to the number of store lines in the original Manchester Baby. It is possible to have a larger number of store lines.

CPU.py

The CPU class executes the application held in the store lines. It is also responsible for displaying the state of the CPU and the instructions being executed.

ManchesterBaby.py

The main program file contains an assembler (a very primitive one with little error checking) for the Baby’s instruction set. It allows a file to be read and the store lines setup and finally instructs the CPU to execute the program.

SSEM Assembler Files

The assembler provided in the ManchesterBaby.py file is primitive and provides little error checking. It is still useful for loading applications into the store lines ready for execution. The file format is best explained by examining a sample file:

Two minus signs indicate an inline comment. Everything following is ignored.

An instruction line has the following form:

Store line number: Instruction Operand

The store line number is the location in the Store that will be used hold the instruction or data.

Instruction is the mnemonic for the instruction. The some of the instructions have synonyms:

Mnemonic

Synonyms

JRP

JPR, JMR

CMP

SKN

STOP

HLT, STP

As well as instructions a number may also be given using the NUM mnemonic.

All of the mnemonics requiring a store number (all except STOP and CMP) read the Operand field as the store line number. The NUM mnemonic stores the Operand in the store line as is.

Testing

Testing the application was going to be tricky without a reference. Part of the reason for developing the Python implementation was to check my understanding of the operation of the SSEM. Luckily, David Sharp has developed an emulator written in Java. I can use this to check the results of the Python code.

The original test program run on the Manchester Baby was a calculation of factors. This was used as it would stress the machine. This same application can be found in several online samples and will be used as the test application. The program is as follows:

Running on the Java Emulator

Executing the above code in David Sharps emulator gives the following output on the storage tube:

Result of the HCF application shown on the storage lines of the Java emulator.

and disassembler view:

Result of the program execution disassembled in the Java emulator.

And running on the Python version of the emulator results in the following output in the console:

Final output from the Python emulator.

Examination of the output shows that the applications have resulted in the same output. Note that the slight variation in the final output of the Python code is due to the way in which numbers are extracted from the registers. Examination of the bit patterns in the store lines shows that the Java and Python emulators have resulted in the same values.

Conclusion

The Baby presented an ideal way to start to examine computer architecture due to its prmitive nature. The small storage space and simple instruction set allowed emulation in only a few lines of code.

The weather station project has been struggling for part of September with a random exception in my code. The source of the exception was finally identified although the exact cause is still a mystery. As a temporary measure, the component and the associated source code were removed from the project, this allowed the project to continue.

Source of the Exception

In the previous post, Define a Minimum Viable Product and Ship it, I discussed a replacement for the clock module. This module was changed from the DS3234 to the DS3231 freeing up four additional pins (the DS3231 is an I2C peripheral and the DS3234 uses SPI). This change seems to have introduced some instability into the system.

Several days debugging followed after which I was still unable to isolate the problem. The interim solution is to remove the module and use the Ticker class to trigger an interrupt. Not ideal but this will allow the rest of the software to be soak tested.

The additional Ticker object fires once per minute to trigger the reading of the sensors. In addition, the interrupt will check the current time and if the hour and minute are both zero then the application will reset the pluviometer counter.

Soak Testing

As mentioned above, one Ticker object is generating an interrupt once per minute. The frequency of the interrupt is higher than will be used in the final project. One minute is more than enough time to perform the sensor readings and log this to the internet but also frequent enough to simulate a prolonged period in the field.

The prototype board was also located close to the final home for the sensors. This hit a problem as the range of the WiFi network was not sufficient for the ESP8266. This problem has prevented the prototype to be tested in the field at the moment.

Conclusion

The prototype board is currently sited on a window ledge in the house. taking temperature, humidity and luminosity readings once per minute. This data is logged to the Sparkfun Phant Data Service. The system has been running continuously for the last 7 days (approximately 10,000 readings).

The software seems stable at the moment, the next problem, extend the range of the WiFi network.

The last few weeks have seen me fighting a communication problem between the Oak and the STM8S. As a result the development has slowed down a little to the point that the project was becoming frustrating. It was about two weeks ago that I was listening to a podcast and the presenter made a very pertinent comment. Some software start-ups fail because they fail to define the minimum viable product. This gives them nothing to aim for and most importantly no firm idea of what they have to deliver.

This felt very relevant as the weather station was stalling. With this in mind I decided to break the project up into a number of revisions.

Revision One – Basic Sensing

Although this revision is titled Basic Sensing, we will aim to get as many of the sensors working with the Oak, and only the Oak, as possible.

The Oak has an ESP8266 (ESP12) at it heart and this leaves a restricted number of IO pins available to communicate with the outside world. With this in mind it should still be possible to work with the following sensors:

Wind speed and direction

Pluviometer

BME280 (air temperature, humidity and temperature)

DS18B20 (ground temperature

TSL2561 (luminosity)

An additional pin is also required to allow the Real Time Clock (RTC) module to generate and interrupt to let the Oak know it is time to take a reading.

One of the aims of the final system is to be able to run using solar power and batteries, however, for the initial version of the system this is not necessary. The main controller board can be placed inside to run on mains power with the rain and wind sensors outside. Some of the sensors would be inside so this would not give a good view of the weather but will allow testing of the software.

Clock Module Change

I came across a variant of the DS3234 RTC on one of my usual suppliers web page. The DS3231 module is similar to the DS3234 with four differences:

Price – I can buy four of the DS3231 modules for the same price as one DS3234

Interface – the DS3231 uses I2C rather than SPI

No on chip memory for the DS3231

Additional EEPROM on board

All of the main features that make the DS3234 desirable are also present in the DS3231. The major piece of work is the necessity to overhaul the DS3234 library created previously.

Schematic

The main changes to the hardware design involve moving the connections to the various sensors from the STM8S to the Oak. This has been made possible by the change in the clock module from the DS3234 (SPI) to the DS3231 (I2C). This freed up the four pins that had been dedicated to the SPI interface.

The updated schematic is as follows:

Weather Station Rev1 Schematic

Translating this to hardware on a protoboard:

Weather Station Rev1 Protoboard

There are a couple of items on the hardware implementation that are not on the schematic:

Additional connectors / jumpers

Connector for OpenLog board

These items may or may not make it onto the final board and have been added to aid debugging.

Software

The software for this project has been placed on Github and is broadly speaking the same as previous versions in terms of design. The following are the most significant changes:

STM8S code has been left in the project for the moment but this is not used in this initial working version.

Interrupts for the pluviometer and the wind speed are now in the Oak code

Logging to Phant has been implemented (public stream can be found

DS3234 code has been abstracted and used to make a DS323x generic timer class and a DS3231 specific class

There are still some modifications required:

Remove or convert to MQTT rather than Phant

Look at exception handling

Deal with network connection issues

Clear the rainfall today counter when moving from one day to the next

One of the previous versions of this application used Adafruit’s MQTT library to connect to Adafruit.IO. Recent changes to the site and to the library means that any attempt to connect the Oak to Adafruit results in an exception being thrown. A task for later is to correct this problem if possible.

An Internet connection may not always be possible so it would be desirable to collect data while offline and then upload this later. This may be facilitated by the EEPROM on the DS3231 breakout board.

The software is currently throwing an exception every 10-30 readings. This results in the Oak resetting itself so it does recover although there is a small gap in the data gathering. The source code has been reviewed an there are no obvious memory leaks or null references. This problem is being deferred as there is a workable solution in that the Oak does reset itself. Not satisfactory in the long term but liveable for now.

Conclusion

The definition of a minimum product has allowed the project to proceed to the point where something can be deployed and tested. There are still a number of items to be completed before the original aims can be realised including the resolution of some issues in the minimum product.

One thing I have learned along the way, don’t try and use pin 10 as an interrupt pin on the Oak as this is not implemented.

The next steps, complete the initial code and deploy the external sensors.

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:

Reducing power by putting the system to sleep

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:

Built in alarm(s)

Battery back up

Built in oscillator

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.

void SetRegisterValue(const Registers reg, const uint8_t value)

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.

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:

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.

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:

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.

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.

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.

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

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:

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.

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:

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.

Add a real time clock (RTC) to the system for times when the internet is not available.

Send data to the cloud

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.

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

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:

Reset the counter each time it has been read

Reset the counter once a day

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

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:

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:

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:

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:

Add an AND gate between the incoming rain gauge pulse and the 74HC4040 clock pin. This can be turned off when taking a reading.

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.

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

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:

Test logging data to the cloud

Serial debugging

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.

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:

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.

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:

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)

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).

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:

Error handling for network issues

Possible offline collection of data

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:

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:

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.

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

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:

How to handle two sensors requiring an analog conversion

The project is running out of digital pins

Real time clocks

Offline data logging

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

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:

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

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.