Introduction
In my first article using the Arduino 2009 board, I described a simple temperature sensor interfaced using Visual Basic.
I have developed the board and Visual Basic code to give a fairly usable indoor weather station.
Overall Operation
The Arduino 2009 acts as a standalone weather station. It does not display the data. It can operate independently for months. It samples the sensors until the RAM is full. The RAM samples are stored on a sector of an SD card. Eventually, a year's weather samples could be stored on the SD card.
Each time the PC is connected, any unstored sectors are uploaded to files on the PC's hard drive. It can then be displayed using all the facilities of the PC's display.
The central idea is that the PCc has a file copy of each SD card sector stamped with the date time it was recorded.
Arduino Indoor Weather Station
The activity diagram for the program:
The Arduino IDE uses C++, and the actual program for this activity diagram is straightforward.
The SD card current sector is held in EEPROM so that during power off or reset, it can be reused. The Arduino has a class EEPROM
which only allows read and write of bytes. To read a long (32 bytes in Arduino) requires some work:
inline void ready()
{
unsigned long lastblock = 0; unsigned long tempblock = 0;
tempblock = EEPROM.read(0); lastblock |= tempblock;
tempblock = EEPROM.read(1); lastblock |= tempblock << 8;
tempblock = EEPROM.read(2); lastblock |= tempblock << 16;
tempblock = EEPROM.read(3); lastblock |= tempblock << 24;
Serial.println("ready"); Serial.println(lastblock); delay(10000); }
The Arduino does not have a class to read and write to SD cards, so I wrote my own. This is the .h file:
#ifndef SDCARD_h
#define SDCARD_h
#define setupSPI SPCR = 0x53; //Master mode, MSB first,
#define deselectSPI SPCR = 0x00; //deselect SPI after read write block
#define clearSPI SPSR = 0x00; // clear SPI interrupt bit
#define setupDDRB DDRB |= 0x2c; //set SS as output for cs
#define selectSDCARD PORTB &= ~0x04; //set the SS to 0 to select the sd card
#define deselectSDCARD PORTB |= 0x04; //set the SS to 1 to deselect the sd card
#include "WProgram.h"
class SDCARDclass
{
public:
unsigned char readblock(unsigned long Rstartblock);
unsigned char writeblock(unsigned long Wstartblock);
private:
unsigned char SD_reset(void);
unsigned char SD_sendCommand(unsigned char cmd, unsigned long arg);
unsigned char SPI_transmit(unsigned char data);
};extern SDCARDclass SDCARD;
#endif
So, when we need to save a sector of data, we do:
inline void lastblocksave()
{
unsigned int e = 0; e = SDCARD.writeblock(currentblock); while (e != 0) {
Serial.println("writesderror"); Serial.println(e); digitalWrite(8, HIGH); delay(10000); } currentblock +=1; EEPROM.write(0,currentblock); EEPROM.write(1,currentblock >> 8); EEPROM.write(2,currentblock >> 16); EEPROM.write(3,currentblock >> 24); ramaddress = 0; }
PC Program Startup
The PC program was written using Microsoft's Visual Basic Express IDE.
When the display program loads and before it is activated, we create a startup form which has all the routines to upload the data samples.
Private Sub ArduinoWeather_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Dim f As New showstart
f.ShowDialog()
While My.Computer.FileSystem.FileExists(cd & "\" & last_sector)
last_sector += 1
End While
If My.Computer.FileSystem.FileExists(cd & "\archive") Then
Dim st As Stream = File.Open(cd & "\archive", FileMode.Open, FileAccess.Read)
Dim br As New BinaryReader(st)
Try
Dim n As Integer = 0
Do
archived(n) = br.ReadInt64()
archivedisplay.Items.Add(archived(n))
If archived(n) > lastblock Then lastblock = archived(n)
n += 1
If n = 100 Then Exit Do
Loop
Catch ex As Exception
Finally
br.Close()
st.Close()
End Try
End If
fill_buffers()
If overflow.Text = "" Then
Try
com8 = My.Computer.Ports.OpenSerialPort("com8", 9600)
readthread = New Threading.Thread(AddressOf read)
readthread.Start()
Catch ex As Exception
comdisplay.Text = "no connection" & vbNewLine & _
"or" & vbNewLine & "no communication"
display_noconnect.Enabled = True
End Try
End If
End Sub
The activity diagram for the start form is shown here:
The whole purpose of this startup is to ensure that every sector on the SD card is recorded on the hard drive with its real sample time. I start a thread which is used to upload any required data. This following code is the core of the procedure:
Dim st As Stream = File.Open(save_sd, FileMode.Create, FileAccess.Write)
Dim bw As New BinaryWriter(st)
For i = 0 To 255
bw.Write(sd_samples(i))
Next
bw.Write(date_stamp.ToString("F"))
date_stamp = date_stamp.Add(TimeSpan.FromSeconds(850))
bw.Close()
st.Close()
lastblock = j
upload = "uploading"
Invoke(New messdelegate(AddressOf showmessage))
The user will see a form center screen:
Next, in the user form load, we determine the number of sector files. Then, the archived files are stored in their own file. We then fill the display buffers from the relevant sector files.
Finally, we start a thread which will read the new data samples that will be displayed in the current display.
At last, the user form is displayed:
PC Program User Form
The display images are generated in its own class. The data is passed to the class and then the image is added to the user form. If the display is as detailed as possible, i.e., each 10 second sample has its own pixel, the full display span will be for 4 hours. The data can be averaged to give a maximum of 4 weeks per display span.
The start time of the display can be offset to allow a view of any section of the 4 weeks data. This can be at the maximum resolution (10 sec. per sample).
This code implements the operation:
Private Sub display_all()
Try
Dim Tinterrim_buffer(241919) As Int32
Dim Hinterrim_buffer(241919) As Int32
Dim Ainterrim_buffer(241919) As Int32
Dim Cdisplay_start_time As DateTime
Select Case True
Case RadioButton1.Checked
display_span = span_define(0)
Case RadioButton2.Checked
display_span = span_define(1)
Case RadioButton3.Checked
display_span = span_define(2)
Case RadioButton4.Checked
display_span = span_define(3)
Case RadioButton5.Checked
display_span = span_define(4)
Case RadioButton6.Checked
display_span = span_define(5)
Case RadioButton7.Checked
display_span = span_define(6)
Case RadioButton8.Checked
display_span = span_define(7)
Case RadioButton9.Checked
display_span = span_define(8)
End Select
For i = 0 To 241919
If i < last_pointer + 1 Then
Tinterrim_buffer(241919 - i) = temp_buffer(last_pointer - i)
Hinterrim_buffer(241919 - i) = humid_buffer(last_pointer - i)
Ainterrim_buffer(241919 - i) = air_buffer(last_pointer - i)
Else
Tinterrim_buffer(241919 - i) = 999999
Hinterrim_buffer(241919 - i) = 999999
Ainterrim_buffer(241919 - i) = 999999
End If
Next
d.display_span_time = TimeSpan.FromMinutes(240 * display_span)
Dim number_display As Integer = _
1440 * display_span - 1
If cursor_time + number_display < 241920 Then
Cdisplay_start_time = display_start_time.AddDays(-28 * cursor_time / 241919)
Dim counter As Integer = 0
For i = 241919 - cursor_time To 0 _
Step -display_span
Dim average = 0
For j = 0 To display_span - 1
average += Tinterrim_buffer(i - j)
Next
d.temperature(1439 - counter) = average / display_span
average = 0
For j = 0 To display_span - 1
average += Hinterrim_buffer(i - j)
Next
d.humidity(1439 - counter) = average / display_span
average = 0
For j = 0 To display_span - 1
average += Ainterrim_buffer(i - j)
Next
d.airpressure(1439 - counter) = average / display_span
counter += 1
If counter = 1440 Then Exit For
Next
Else
hasbeenoffset.Text = "selected offset out of range"
cursor_time = 0
End If
d.display_start_time = Cdisplay_start_time
If zoom_TH Or zoom_AR Then
If zoom_TH Then
d.full_temp_hum()
Else
d.full_air_rain()
End If
Else
d.scale_temp_hum()
d.scale_air_rain()
End If
Catch ex As Exception
End Try
End Sub
The user form can start a file management form:
Here is the code to archive a file:
Private Sub archivefile_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button5.Click
If My.Computer.FileSystem.FileExists(cd & "\archive") Then
Dim words() As String = archived.Text.Split(vbNewLine)
Dim x = CLng((words.Length - 1))
If archiveblock > CLng(words(words.Length - 2)) + 100 Then
Dim str As Stream = File.Open(cd & "\archive", _
FileMode.Append, FileAccess.Write)
Dim bwr As New BinaryWriter(str)
bwr.Write(archiveblock)
bwr.Close()
str.Close()
Else
MsgBox("The archived block must be at least" & vbNewLine & _
"one day -that is 100 bigger than last")
End If
Dim st As Stream = File.Open(cd & "\archive", FileMode.Open, FileAccess.Read)
Dim br As New BinaryReader(st)
archived.Text = ""
Try
Do
archived.Text = archived.Text & (br.ReadInt64()) & vbNewLine
Loop
Catch ex As Exception
Finally
br.Close()
st.Close()
End Try
End If
End Sub
Circuit diagram for weather station: