65.9K
CodeProject 正在变化。 阅读更多。
Home

Arduino 与 Visual Basic

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (15投票s)

2009年9月23日

CPOL

4分钟阅读

viewsIcon

185591

downloadIcon

4798

如何使用Visual Basic与Arduino 2009开发板。

引言

我有一个配置了简单温度监控器的Arduino 2009开发板:http://arduino.cc/en/Main/ArduinoBoardDuemilanove。我想使用Visual Basic与开发板通信并构建一个图形显示页面。Arduino为各种其他语言提供了接口代码。Andrew Craigie还提供了一个非常专业的Visual Basic接口,使用的是他编写的Fermata库:http://www.acraigie.com/programming/firmatavb/default.html。这个库编写成一个可以添加到工具箱的组件。然后可以简单地将此组件拖放到任何窗体上,使其在项目中的代码中可用。使用这种方法可以向Visual Basic程序员隐藏实际的接口代码。在你的程序下面还有一层额外的代码。

Firmata库实现了与主机软件通信的Firmata协议。因此,必须将该库包含在Arduino开发板固件中,从而占用宝贵的程序存储空间:http://arduino.cc/en/Reference/Firmata。因为我不需要此应用程序中提供的大多数方法,并且希望使程序尽可能简单,所以我决定编写自己的接口代码。

所有函数都位于类中,因为这为编码带来了纪律性。

程序需求

Arduino开发板每10秒采样一次温度。经过6次采样(1分钟)后,开发板检查串口输入。如果收到“SSSSSS”,它将发送采样值到串口输出。如果没有,则样本将存储在EEPROM中。因此,如果用户的计算机未发送“SSSSSS”,则样本将存储在EEPROM中,直到EEPROM满(1024字节)。

如果串口输入为“U”,则所有(如果有)存储的EEPROM样本都将发送到串口输出。因此,用户的计算机将首先发送“U”以检查并上传存储的样本。之后,它将每1分钟使用“SSSSSS”查找样本。

关闭时,用户计算机将样本保存在用户文件夹中。这些先前存储的样本将在程序运行的任何时间都可供用户查看。

串口连接

Arduino使用FTDI USB转串口芯片。这使得开发板可以通过USB连接显示为串口。首次连接时,我将Windows引导至FTDI驱动程序。加载驱动程序后,我的计算机为Arduino分配了com8。然后通信只是在com8上发送和接收字符串的问题。

Upload_stored类

当用户窗体加载时,我将上传任何可用的存储样本。由于这在窗体激活之前运行,所以我让这段代码在窗体的线程上运行。

串口输入代码包含在嵌套的Try Catch块中。外部块用于com8被其他程序使用或Arduino未连接的情况。下一个块捕获在20秒内没有响应的情况。内部块捕获每100毫秒的任何连接丢失。

Try
    Dim com8 As IO.Ports.SerialPort = My.Computer.Ports.OpenSerialPort("com8", 9600)
    com8.ReadTimeout = 20000 'board will respond within 10 seconds
    com8.Write("U")  'arduino board command for upload on restart
    Try
        Dim addr_pointer As Integer = CInt(com8.ReadLine) ' get rid of cr lf
        If addr_pointer > 0 Then  'we have saved samples
            For i = 0 To (addr_pointer / 2 - 1) 'read all stored samples
                com8.ReadTimeout = 100  'if we dont get a new byte in 100 ms
                Try
                    display_data(i) = _
                      software_dcoffset - CInt(com8.ReadLine) * software_scalefactor 
                    rx_number += 1  'updata the sample buffer pointer
                Catch ex As TimeoutException
                    MsgBox("we have lost the circuit")
                End Try
            Next
        End If
        com8.Write("SSSSSS")  'arduino board command for upload single samples
        User_interface.Timer1.Enabled = True  'start the sampling proces
        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") 'cant connect to port
End Try

Read_current类

串口代码与upload_stored非常相似,但是由于我们现在需要在串口通信期间访问窗体,因此这段代码在其自身的线程上运行。这会使程序复杂化,因为Windows控件不是线程安全的。这意味着我们必须不遗余力地从我们启动的线程中使用它们。这涉及使用一个委托子程序,该子程序将在拥有窗体底层句柄的线程上运行。这是使用Invoke(委托)方法实现的:http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx

''''
 _frm.Invoke(New messdelegate(AddressOf showmessage)) 'register sample
''''
Delegate Sub messdelegate()
'a delegate will run on the main window thread
    Private Sub showmessage()
    'this runs on the main window thread because control not thread safe
        'register sample
        Upload_stored.rx_number = Upload_stored.rx_number + 1
        'reset value***and save full sample
        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 'OK lets check for another sample
    End Sub

在一个类中,我们可以将标签引用为form.label2,但我们不能对form.invoke做同样的事情。它可以正常编译,但会引发运行时错误,提示没有可用的句柄。我们必须在类中创建窗体的实例,然后在代码中引用它。

Public Sub New(ByVal f As User_interface) 'constructor runs at start
    _frm = f 'need an instance of form to allow operation of Invoke
    Dim readthread As Threading.Thread = New Threading.Thread(AddressOf read)
    readthread.Start()  'see if have data to read using worker thread
End Sub
Private _frm As User_interface  'need instance of mainform to give invoke a handler

我们通过以下方式实例化类

 Dim c As New Read_current(Me) 'must pass form to constructor

Display类

这使用位图上的简单图形来显示温度样本。

Private part As New Bitmap(1570, 1070, Drawing.Imaging.PixelFormat.Format24bppRgb)
    Public display_now(1439) As Int16  'only 0 to 1023 possible
    ''''
    For i = 0 To 1438
        If display_now(i + 1) = 0 Then Exit For 'only display the real samples
        g.DrawLine(Rpen, 62 + i, 1040 - display_now(i), 63 + i, 1040 - display_now(i + 1))
    Next
    ''''
    User_interface.PictureBox1.Image = part

Display_files类

这显示了程序保存的24(或更少)小时的样本。样本的日期时间用作文件名,以便可以在图形上显示时间和日期。

Public Shared user_folder As String = _
  My.Computer.FileSystem.SpecialDirectories.MyMusic & "\met_samples\"

''''
'get access to file
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  'read the number of samples in tyhe stored file
        display_now(i) = br.ReadInt16
    Next
End Using

''''
Dim sts As DateTime = rt.Replace(".", ":")  'fix up the stored time
Dim dt = (sts.Subtract(starttime)).ToString("t") 'just need short time
Dim yt = (sts.Subtract(starttime)).ToString("d MMM yyyy")
Dim d As New Display
d.display_now = display_now

Arduino固件

此处显示带注释的C++代码。Arduino草图包含在zip文件中。

#include <EEPROM.h>
#include "WProgram.h"
void setup();
void loop();
void adc();
int inByte =0;  //received byte
unsigned int minutesample = 0;  //adc averaged over a minute
byte numbersample = 0;  //the number of samples so far -6 for 1 minute
unsigned long previousMillis = 0;        // will store last time we looked
int addr = 0;  //address of next data to be stored in the EEPROM
void setup()
{
    Serial.begin(9600);  // initialize serial communication with computer 
    pinMode(12, OUTPUT);      // sets the digital pin as output
    pinMode(11, OUTPUT);      // sets the digital pin as output
    analogReference(EXTERNAL);  //can be 1.8 to 5.5v we use 3.3v
}
void loop()
{ 
    if (millis() - previousMillis > 10000)  //every 10 seconds
    {
        // remember the last time we sampled the pressure
        previousMillis = millis();
        //flash the led
        digitalWrite(12, HIGH);
        digitalWrite(11, HIGH);
        //flash the led
        //is the computer comunicating

        if (Serial.available() > 0)
        // read the oldest byte in the serial buffer:
        {
            //if it is "U" we can upload saved samples to computer
            inByte = Serial.read();
            if (inByte == 'U')
            {
                //send current address pointer to computer
                Serial.println(addr);
                if (addr > 0)
                //have we got stored values to upload
                {
                    int i = 0;  //counter for upload
                    for  (i; i < addr; i +=2)
                    {
                        //restore integer value 
                        minutesample = ((EEPROM.read(i + 1)) * 256) +  
                                         EEPROM.read(i);
                        //send stored sample to computer
                        Serial.println(minutesample);
                    }//end of upload stored values
                    addr = 0; //we have now sent the lot
                    minutesample = 0;  //reset the sample
                    numbersample = 0;  //reset the counter
                } //end of have we got stored values  
            }//end of upload any stored data
            if (inByte == 'S')
            {    
                adc(); 
                if (numbersample == 6 )
                //we have 6 samples over 1 minute
                {
                    Serial.println(minutesample/6); //send it to computer
                    minutesample = 0;  //reset the sample
                    numbersample = 0;  //reset the counter
                }//end of have 6 samples
            } //end of received "S"
        }//end of serial available 
         //computer is not communicating
        else
        //no serial sent so the computer is not connected
        {
            //we need to store the samples in EEPROM untill reconnects
            if (addr < 1024)
            //328 has 1024 bytes ie 0 to 1023
            {
                //only write if have spare space in EEPROM
                adc(); 
                if (numbersample == 6 )
                //we have 6 samples over 1 minute
                {
                    minutesample /= 6;  //get average of 6 samples
                    EEPROM.write(addr, (minutesample%256));  //store least byte
                    EEPROM.write(addr + 1, (minutesample/256));  //store most byte
                    addr += 2;  //next available storage for sample 
                    minutesample = 0;  //reset the sample
                    numbersample = 0;  //reset the counter
                }//end of have 6 samples
                digitalWrite(11, LOW);  //turn led off
            } //end of < 1024  then full so dont write
        }//end of serial not available
    }//end of wait to sample pressure 
    digitalWrite(12, LOW);  //turn led off
}//end of loop

void adc()
{
    unsigned int sample = 0;   //reset the sample value
    int i = 0;
    for(i; i<=64; i++)
    sample += analogRead(2);  //get 64 samples added together 1024 x 64 max
    sample = sample / 64;  //get the average of 64 samples
    minutesample +=sample;  //add the 10 second sample
    numbersample +=1; //the next sample
}//end of adc

int main(void)
{
    init();
    setup();
    for (;;)
    loop();
    return 0;
}

Arduino硬件

原理图是使用Eagle绘制的:http://www.cadsoft.de/download.htm。文件位于zip文件中。

面包板图片是使用Peeble绘制的:http://rev-ed.co.uk/picaxe/pebble/。文件位于zip文件中。

© . All rights reserved.