Introduction
I have an Arduino 2009 board setup with a simple temperature monitor: http://arduino.cc/en/Main/ArduinoBoardDuemilanove. I wanted to use Visual Basic to communicate with the board and build a graphics display page. Arduino provides interface code for various other languages. There is also a very professional interface to Visual Basic available using Fermata written by Andrew Craigie: http://www.acraigie.com/programming/firmatavb/default.html. This is written so that a component can be added to the toolbox. This component can then be simply dragged onto any form to make it available to code within your project. Using this method hides the actual interface code from the Visual Basic programmer. There is an extra layer of code below your program.
The Firmata library implements the Firmata protocol for communicating with software on the host computer. As such, the library must be included in the Arduino board firmware, using up scarce program storage: http://arduino.cc/en/Reference/Firmata. As I did not need most of the methods available with this application and wanted to make the program as simple as possible, I decided to write my own code for the interface.
All the functions are in classes as this brings discipline to the coding.
Program requirements
The Arduino board samples the temperature every 10 seconds. After 6 such samples (1 minute), the board checks the serial input. If it is sent "SSSSSS", it will send the sample value on the serial output. If not, the sample will be stored on EEPROM. So, if the user's computer is not sending "SSSSSS", the samples will be stored on the EEPROM until it is full (1024 bytes).
If the serial input is "U", all (if any) of the stored EEPROM samples will be sent on the serial output. So the user's computer will at the start send a "U" to check for and then upload stored samples. After that, it will look for a sample using "SSSSSS" every 1 minute.
On closing, the user computer will save the samples in a user folder. These previously stored samples will be available to the user to view at any time the program is running.
Serial connection
The Arduino uses a FTDI USB to serial port chip. This enables the board to appear as a serial port via a USB connection. When first connected, I direct Windows to the FTDI driver. After loading the driver, my computer allocated com8 for the Arduino. Communication then is just a matter of sending and receiving strings on com8.
Upload_stored class
When the user form loads, I upload any stored samples available. As this runs before the form is activated, I let this code run on the form's thread.
The serial input code is enclosed in nested Try Catch
blocks. The outer block is for if com8 is being used by another program or the Arduino is not connected. The next block catches if a response is not available within 20 seconds. The inner block catches any loss of connection every 100 msec.
Try
Dim com8 As IO.Ports.SerialPort = My.Computer.Ports.OpenSerialPort("com8", 9600)
com8.ReadTimeout = 20000
com8.Write("U")
Try
Dim addr_pointer As Integer = CInt(com8.ReadLine)
If addr_pointer > 0 Then
For i = 0 To (addr_pointer / 2 - 1)
com8.ReadTimeout = 100
Try
display_data(i) = _
software_dcoffset - CInt(com8.ReadLine) * software_scalefactor
rx_number += 1
Catch ex As TimeoutException
MsgBox("we have lost the circuit")
End Try
Next
End If
com8.Write("SSSSSS")
User_interface.Timer1.Enabled = True
com8.Dispose()
Catch ex As TimeoutException
MsgBox("we dont have a response")
End Try
Catch ex As Exception
MsgBox("we cant open port to download")
End Try
Read_current class
The serial code is very similar to upload_stored
, but as we now need to access the form during serial communication, this code is run on its own thread. This complicates the program as Windows controls are not thread safe. This means that we have to bend over backwards to use them from a thread we start. This involves using a delegate subroutine that will run on a thread that owns the underlying handle of the form. This is achieved using the Invoke
(delegate) method: http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx.
_frm.Invoke(New messdelegate(AddressOf showmessage))
Delegate Sub messdelegate()
Private Sub showmessage()
Upload_stored.rx_number = Upload_stored.rx_number + 1
If Upload_stored.rx_number = 1439 Then Upload_stored.rx_number = 0
If Display_files.show_current Then
Dim d As New Display
d.display_now = Upload_stored.display_data
d.dt = Upload_stored.displaytime
d.show_now()
_frm.Label2.Text = "Current temperature = " & _
(Upload_stored.display_data(Upload_stored.rx_number - 1) * _
Upload_stored.software_nominal).ToString("n1")
End If
_frm.Timer1.Enabled = True
End Sub
In a class, we can reference a label as form.label2
, but we cannot do the same with form.invoke
.It will compile OK, but give a runtime error of no handle available. We have to create an instance of the form in the class and then reference that in the code.
Public Sub New(ByVal f As User_interface)
_frm = f
Dim readthread As Threading.Thread = New Threading.Thread(AddressOf read)
readthread.Start()
End Sub
Private _frm As User_interface
We instantiate the class by:
Dim c As New Read_current(Me)
Display class
This uses simple graphics on a bitmap to display the temperature samples.
Private part As New Bitmap(1570, 1070, Drawing.Imaging.PixelFormat.Format24bppRgb)
Public display_now(1439) As Int16
For i = 0 To 1438
If display_now(i + 1) = 0 Then Exit For
g.DrawLine(Rpen, 62 + i, 1040 - display_now(i), 63 + i, 1040 - display_now(i + 1))
Next
User_interface.PictureBox1.Image = part
Display_files class
This displays 24 (or less) hour samples that have been saved by the program. The date time for the samples are used as for the file names so that the time and date can be displayed on the graphics.
Public Shared user_folder As String = _
My.Computer.FileSystem.SpecialDirectories.MyMusic & "\met_samples\"
Dim st As Stream = File.Open(files(displayed_time), _
FileMode.Open, FileAccess.Read)
Dim sample_n = My.Computer.FileSystem.GetFileInfo(files(displayed_time)).Length / 2 - 1
Using br As New BinaryReader(st)
For i = 0 To sample_n
display_now(i) = br.ReadInt16
Next
End Using
Dim sts As DateTime = rt.Replace(".", ":")
Dim dt = (sts.Subtract(starttime)).ToString("t")
Dim yt = (sts.Subtract(starttime)).ToString("d MMM yyyy")
Dim d As New Display
d.display_now = display_now
Arduino firmware
The C++ code is shown here with comments. The Arduino sketch is include in the zip file:
#include <EEPROM.h>
#include "WProgram.h"
void setup();
void loop();
void adc();
int inByte =0; unsigned int minutesample = 0; byte numbersample = 0; unsigned long previousMillis = 0; int addr = 0; void setup()
{
Serial.begin(9600); pinMode(12, OUTPUT); pinMode(11, OUTPUT); analogReference(EXTERNAL); }
void loop()
{
if (millis() - previousMillis > 10000) {
previousMillis = millis();
digitalWrite(12, HIGH);
digitalWrite(11, HIGH);
if (Serial.available() > 0)
{
inByte = Serial.read();
if (inByte == 'U')
{
Serial.println(addr);
if (addr > 0)
{
int i = 0; for (i; i < addr; i +=2)
{
minutesample = ((EEPROM.read(i + 1)) * 256) +
EEPROM.read(i);
Serial.println(minutesample);
} addr = 0; minutesample = 0; numbersample = 0; } } if (inByte == 'S')
{
adc();
if (numbersample == 6 )
{
Serial.println(minutesample/6); minutesample = 0; numbersample = 0; } } } else
{
if (addr < 1024)
{
adc();
if (numbersample == 6 )
{
minutesample /= 6; EEPROM.write(addr, (minutesample%256)); EEPROM.write(addr + 1, (minutesample/256)); addr += 2; minutesample = 0; numbersample = 0; } digitalWrite(11, LOW); } } } digitalWrite(12, LOW); }
void adc()
{
unsigned int sample = 0; int i = 0;
for(i; i<=64; i++)
sample += analogRead(2); sample = sample / 64; minutesample +=sample; numbersample +=1; }
int main(void)
{
init();
setup();
for (;;)
loop();
return 0;
}
Arduino hardware
The schematic is drawn using Eagle: http://www.cadsoft.de/download.htm. The files are in the zip file.
The bread board picture is drawn using Peeble: http://rev-ed.co.uk/picaxe/pebble/. The files are in the zip file.