Arduino и Visual Basic. Часть 1: Приём данных от Arduino

Примечание

Оригинал статьи: martyncurrey.com

Это первая часть руководства по использованию последовательного порта для подключения Arduino к приложению Visual Basic, работающему на ПК. Связь осуществляется через обычный USB-канал последовательного порта (Serial UART).

В первом примере всё довольно просто: данные отправляются с Arduino и отображаются в приложении Visual Basic.

Данное руководство обновлено для Visual Studio / Visual Basic 2022. Однако, чтобы не усложнять, приложение создано с использованием Windows Forms App (.NET Framework).

Необходимые инструменты

Arduino + USB-кабель

ПК с установленной Visual Studio

  • На момент написания последняя версия Visual Studio — 2022. Есть три редакции:

  • Community

  • Professional

  • Enterprise

Варианты Visual Studio

Visual Studio Community — это полнофункциональная IDE и система разработки, бесплатная для студентов, участников open-source проектов и индивидуальных разработчиков. Нюанс — нужна учётная запись Microsoft :-(

Варианты установки Visual Studio

Если вы устанавливаете VS с нуля, обязательно выберите рабочую нагрузку «.NET desktop development». Это можно сделать и позже через Visual Studio Installer.

Если вы новичок в этой IDE, стоит посмотреть несколько обучающих материалов. У Microsoft есть хорошие руководства для начала:

Обмен данными Arduino и Visual Basic

Вместо пошагового построения приложения (хотя я в некоторой степени это делаю в разных частях руководства), проще начать сразу с рабочей версии и объяснить, как работают отдельные части. Начнём со скетча Arduino.

Скетч Arduino

Скетч Arduino довольно простой — он отправляет ASCII-данные (счётчик) через последовательное соединение раз в секунду. Одновременно с отправкой данных он мигает встроенным светодиодом на пине 13.

Стоит отметить, что Arduino не проверяет, слушает ли кто-нибудь. Он «слепо» отправляет данные, пока вы его не выключите.

/*
* Sketch Arduino and Visual Basic Part 1 - Receiving Data From the Arduino
* Send a count over a serial connection once a second
* https://www.martyncurrey.com/arduino-and-visual-basic-part-1-receiving-data-from-the-arduino/
*/

byte LEDpin = 13;
int count = 1000;

void setup()
{
     pinMode(LEDpin, OUTPUT);
     Serial.begin(9600);
}

void loop()
{
     Serial.println(count);
     count = count + 1;      //  count++;  also works, it's the same

     // flash the onboard LED to show data is being sent
     digitalWrite(LEDpin,HIGH);      delay(100);
     digitalWrite(LEDpin,LOW);       delay(900);
}

Чтобы проверить работу скетча, загрузите код и откройте монитор порта. Заодно запишите номер COM-порта. У меня это COM12, у вас скорее всего будет другой.

Монитор порта Arduino IDE

Я использую IDE версии 1. Я использую портативные IDE, а новая V2 IDE пока не поддерживает полную портативную установку.

Всё, что мы собираемся сделать — это заменить монитор порта приложением Visual Basic.

Открытие приложения Visual Basic в Visual Studio

Предполагая, что Visual Studio установлена, скачайте проект Arduino-Visual-Basic-Part-1 VB. Скачивается zip-файл, внутри которого находится папка с файлами проекта.

Папки проекта Visual Basic

Самый простой способ открыть проект — дважды кликнуть на файл .sln.

Открытие проекта

Приложения Visual Basic, как правило, состоят из двух частей: формы и кода. Форма создаётся с помощью дизайнера. Код создаётся в текстовом редакторе.

Примечание для педантов: форму также можно создать в коде, если захотите.

Дважды кликните на файл Arduino_Visual-Basic.sln.sln. Это должно запустить Visual Studio и загрузить проект.

Visual Studio только что открыта

Если Visual Studio откроется с пустым рабочим пространством, кликните правой кнопкой на Form1.vb (вверху справа) и выберите View Designer, или нажмите Shift + F7 (или просто дважды кликните Form1.vb).

View Designer Форма в дизайнере

Главная форма

В этом примере используется очень простая форма, которая содержит всего 5 активных элементов:

  • Выпадающий список COM PORT

  • Кнопка CONNECT

  • Текстовое поле RECEIVED DATA

  • Кнопка CLEAR

  • Метка TIMER

Главная форма

Выпадающий список COM PORT

Это comboBox со стилем dropDownStyle = drop down list.

При первом запуске приложения список COM PORT заполняется всеми доступными COM-портами.

Кнопка CONNECT

Это обычная кнопка.

После выбора COM PORT при нажатии кнопки CONNECT приложение пытается открыть последовательный порт, используя COM-порт, выбранный в выпадающем списке.

Текстовое поле RECEIVED DATA

Это RichTextBox.

Данные, полученные через последовательный канал, копируются в это текстовое поле.

Кнопка CLEAR

Обычная кнопка для очистки текстового поля.

Метка TIMER

Метка для отображения статуса таймера. Таймер используется для периодической проверки наличия новых данных. Обычно показывать статус не нужно. Всё, что приложение получает от Arduino (ASCII), добавляется в текстовое поле.

Через дизайнер также добавлены SerialPort и Timer.

Компоненты формы

Чтобы посмотреть код, снова кликните правой кнопкой на Form1.vb и выберите View Code, или нажмите F7 (или просто дважды кликните внутри формы Form1).

View Code Код формы

Тестирование приложения Visual Basic

Давайте проверим, работает ли приложение. Подключите Arduino к ПК и убедитесь, какой COM-порт использует Arduino. У меня это COM14, у вас скорее всего другой.

В Visual Studio нажмите кнопку Start в верхней части экрана.

Кнопка Start

Код скомпилируется и приложение запустится. Вы должны увидеть главную форму. Пока всё хорошо.

Запущенное приложение

Выберите COM-порт, к которому подключена Arduino, и нажмите CONNECT.

Выбор COM-порта

Если проблем нет, приложение подключится к Arduino и счётчик начнёт появляться в текстовом поле.

Кнопка CONNECT изменится на DIS-CONNECT, и, вы ни за что не угадаете, если нажать кнопку DIS-CONNECT, последовательный канал закроется.

Приём данных

Программа Visual Basic

Вот код:

' Arduino and Visual Basic Part 1: Receiving Data From An Arduino
' A simple example of recieving serial data from an Arduino and displaying it in a text box
' https://www.martyncurrey.com/arduino-and-visual-basic-part-1-receiving-data-from-the-arduino/
'

Imports System
Imports System.IO.Ports
Imports System.Windows.Forms.VisualStyles.VisualStyleElement

Public Class Form1

    ' Global variables.
    ' Anything defined here is available in the whole app
    Dim selected_COM_PORT As String
    Dim receivedData As String = ""

    ' This called when the app first starts. Used to initial what ever needs initialising.
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Timer1.Enabled = False
        selected_COM_PORT = ""
        For Each sp As String In My.Computer.Ports.SerialPortNames
            comPort_ComboBox.Items.Add(sp)
        Next
    End Sub

    ' When the value of the COM PORT drop doewn list changes,
    ' copy the new value to the comPORT variable.
    Private Sub comPort_ComboBox_SelectedIndexChanged(sender As Object, e As EventArgs) Handles comPort_ComboBox.SelectedIndexChanged
        If (comPort_ComboBox.SelectedItem <> "") Then
            selected_COM_PORT = comPort_ComboBox.SelectedItem
        End If
    End Sub

    ' Try to open the com port or close the port if already open
    ' Note: There is no error catching. If the connection attampt fails the app will crash!
    Private Sub connect_BTN_Click(sender As Object, e As EventArgs) Handles connect_BTN.Click
        If (connect_BTN.Text = "CONNECT") Then
            If (selected_COM_PORT <> "") Then

                ' Try allows me to trap an errors such as the COM port not being available
                ' Without it the app crashes.
                Try
                    SerialPort1.PortName = selected_COM_PORT
                    SerialPort1.BaudRate = 9600
                    SerialPort1.DataBits = 8
                    SerialPort1.Parity = Parity.None
                    SerialPort1.StopBits = StopBits.One
                    SerialPort1.DtrEnable = True
                    SerialPort1.RtsEnable = True
                    SerialPort1.Handshake = Handshake.None
                    SerialPort1.Encoding = System.Text.Encoding.Default 'very important!
                    SerialPort1.ReadTimeout = 10000

                    SerialPort1.Open()

                Catch ex As Exception
                    MessageBox.Show(ex.Message + vbCrLf + "Looks like something else is using it.", "Error openning the serial port", MessageBoxButtons.OK, MessageBoxIcon.Error)
                End Try

            Else
                MsgBox("No COM port selected!")
            End If
        Else
            Try
                SerialPort1.Close()
            Catch ex As Exception
                MessageBox.Show("Serial Port is already closed!")
            End Try
        End If

        ' A bit wastful of space but seperating openning the post and setting the variables like this makes the
        ' code a liitle more clear.
        If (SerialPort1.IsOpen) = True Then
            connect_BTN.Text = "DIS-CONNECT"
            Timer1.Enabled = True
            timer_LBL.Text = "TIMER: ON"
        Else
            connect_BTN.Text = "CONNECT"
            Timer1.Enabled = False
            timer_LBL.Text = "TIMER: OFF"
        End If

    End Sub

    ' Process received data. Append the data to the text box
    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        receivedData = ReceiveSerialData()
        recData_RichTextBox.AppendText(receivedData)
    End Sub

    ' Check for new data
    Function ReceiveSerialData() As String
        Dim Incoming As String
        Try
            Incoming = SerialPort1.ReadExisting()
            If Incoming IsNot Nothing Then
                Return Incoming
            End If
        Catch ex As TimeoutException
            Return "Error: Serial Port read timed out."
        End Try

    End Function

    ' Clear the text box
    Private Sub clear_BTN_Click(sender As Object, e As EventArgs) Handles clear_BTN.Click
        recData_RichTextBox.Text = ""
    End Sub

End Class

Подробный разбор программы

Используются две глобальные переменные: selected_COM_PORT и receivedData.

selected_COM_PORT — COM-порт, выбранный пользователем в выпадающем списке.

receivedData — данные, полученные через COM-порт / последовательный канал.

' Global variables.
' Anything defined here is available in the whole app
Dim selected_COM_PORT As String
Dim receivedData As String = ""

Когда программа запускается впервые, автоматически вызывается подпрограмма Form1_Load() — идеальное место для выполнения необходимой инициализации, например, заполнения comboBox / выпадающего списка доступными COM-портами.

Необязательно, но таймер делается неактивным. Это своего рода «подстраховочное» выражение.

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Timer1.Enabled = False
    selected_COM_PORT = ""
    For Each sp As String In My.Computer.Ports.SerialPortNames
        comPort_ComboBox.Items.Add(sp)
    Next
End Sub

После этого программа ожидает действий пользователя.

Когда пользователь наконец выбирает COM-порт в выпадающем списке, выбранное значение копируется в переменную selected_COM_PORT. Это не обязательно, так как выбранное значение можно прочитать напрямую из comboBox в любое время, но мне нравится хранить такие данные в удобных переменных.

Private Sub comPort_ComboBox_SelectedIndexChanged(sender As Object, e As EventArgs) Handles comPort_ComboBox.SelectedIndexChanged
        If (comPort_ComboBox.SelectedItem <> "") Then
            selected_COM_PORT = comPort_ComboBox.SelectedItem
        End If
    End Sub

При нажатии кнопки CONNECT вызывается функция Sub connect_BTN_Click().

Первое, что делает процедура — определяет, подключается пользователь или отключается. Одна и та же кнопка используется для обоих действий.

Если подключение и selected_COM_PORT не пуст — устанавливаются свойства порта, порт открывается и таймер запускается. Если selected_COM_PORT пуст — выводится сообщение, что COM-порт не выбран.

Если отключение — порт закрывается.

Private Sub connect_BTN_Click(sender As Object, e As EventArgs) Handles connect_BTN.Click
    If (connect_BTN.Text = "CONNECT") Then
        If (selected_COM_PORT <> "") Then

            ' Try allows me to trap an errors such as the COM port not being available
            ' Without it the app crashes.
            Try
                SerialPort1.PortName = selected_COM_PORT
                SerialPort1.BaudRate = 9600
                SerialPort1.DataBits = 8
                SerialPort1.Parity = Parity.None
                SerialPort1.StopBits = StopBits.One
                SerialPort1.DtrEnable = True
                SerialPort1.RtsEnable = True
                SerialPort1.Handshake = Handshake.None
                SerialPort1.Encoding = System.Text.Encoding.Default 'very important!
                SerialPort1.ReadTimeout = 10000

                SerialPort1.Open()

            Catch ex As Exception
                MessageBox.Show(ex.Message + vbCrLf + "Looks like something else is using it.", "Error opening the serial port", MessageBoxButtons.OK, MessageBoxIcon.Error)
            End Try

        Else
            MsgBox("No COM port selected!")
        End If
    Else
        Try
            SerialPort1.Close()
        Catch ex As Exception
            MessageBox.Show("Serial Port is already closed!")
        End Try
    End If

Конструкция Try / Catch ex As Exception предназначена для перехвата ошибок. Например, если вы попытаетесь открыть COM-порт, который уже используется другим приложением, VB «взбунтуется».

Попытка открыть занятый COM-порт без обработки ошибок

Попытка открыть COM-порт, который уже используется, без обработки ошибок.

Вместо аварийного завершения приложения, конструкция Try / Catch ex As Exception позволяет обработать проблему. В данном случае — отобразить сообщение об ошибке.

После того как последовательный порт открыт или закрыт, обновляются элементы, связанные с портом.

Если порт открыт — таймер запускается и текст кнопки CONNECT меняется на DIS-CONNECT.

Если порт закрыт — таймер останавливается и текст кнопки устанавливается в CONNECT.

If (SerialPort1.IsOpen) = True Then
    connect_BTN.Text = "DIS-CONNECT"
    Timer1.Enabled = True
    timer_LBL.Text = "TIMER: ON"
Else
    connect_BTN.Text = "CONNECT"
    Timer1.Enabled = False
    timer_LBL.Text = "TIMER: OFF"
End If

Метка timer_LBL предназначена исключительно для отладки.

Таймер используется для проверки входящих данных. Таймер настроен на срабатывание каждые 100 мс (десятая доля секунды), и при срабатывании вызывает процедуру Timer1_Tick(). Для данного примера 500 мс вполне достаточно. Для более сложных задач тайминг может потребовать корректировки.

Если данные получены, они добавляются в текстовое поле.

Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
    receivedData = ReceiveSerialData()
    If (receivedData <> "") Then
        recData_RichTextBox.AppendText(receivedData)
    End If

Timer1_Tick() вызывает вторую подпрограмму, которая выполняет фактическую проверку. Если есть последовательные данные, они возвращаются вызывающей функции.

Function ReceiveSerialData() As String
    Dim Incoming As String = ""
    Try
        Incoming = SerialPort1.ReadExisting()
        If Incoming IsNot Nothing Then
            Return Incoming
        End If
    Catch ex As TimeoutException
        Return "Error: Serial Port read timed out."
    End Try
End Function

Последняя часть — функция обработки кнопки CLEAR. Содержимое текстового поля просто сбрасывается.

Private Sub clear_BTN_Click(sender As Object, e As EventArgs) Handles clear_BTN.Click
    recData_RichTextBox.Text = ""
End Sub

Последовательный порт

Основная часть кода — открытие последовательного порта.

Поскольку пример использует .NET Framework с .NET 4.8, объект последовательного порта можно добавить в приложение через дизайнер. Это означает, что свойства порта доступны в дизайнере.

Свойства последовательного порта в дизайнере

Хотя я добавил serialPort через дизайнер, я устанавливаю свойства в коде. Я предпочитаю такой подход, чтобы в будущем, если потребуется дать пользователю возможность редактировать значения, это можно было сделать быстрее. В данном примере, однако, пользователь выбирает только COM-порт.

SerialPort1.PortName = selected_COM_PORT
SerialPort1.BaudRate = 9600
SerialPort1.DataBits = 8
SerialPort1.Parity = Parity.None
SerialPort1.StopBits = StopBits.One
SerialPort1.DtrEnable = True
SerialPort1.RtsEnable = True
SerialPort1.Handshake = Handshake.None
SerialPort1.Encoding = System.Text.Encoding.Default 'very important!
SerialPort1.ReadTimeout = 10000

SerialPort1.Open()

Скорость передачи (Baud Rate)

Скорость передачи установлена на 9600 как для приложения Visual Basic, так и для скетча Arduino.

Биты данных, чётность и стоповые биты

  • SerialPort1.DataBits = 8

  • SerialPort1.Parity = Parity.None

  • SerialPort1.StopBits = StopBits.One

Эти параметры определяют конфигурацию последовательного порта. Этот набор значений часто называют 8N1:

  • 8 бит данных

  • N — без чётности (No parity)

  • 1 стоповый бит

8N1 — довольно распространённая настройка, используемая по умолчанию многими устройствами.

DTR

SerialPort1.DtrEnable = True

DTR (Data Terminal Ready) — управляющий сигнал, отправляемый подключённому устройству (в данном случае Arduino), чтобы сообщить, что приложение Visual Basic готово к обмену данными.

RTS

SerialPort1.RtsEnable = True

RTS (Request To Send) — ещё один управляющий сигнал, используемый для сообщения подключённому устройству, что приложение имеет данные для отправки. У RTS есть парный сигнал CTS, который в приложении Visual Basic не используется.

Аппаратный последовательный порт Arduino (и программный) не использует RTS и DTR для управления потоком. Однако DTR используется как сигнал сброса. DTR подключён к пину RESET Arduino, поэтому при получении сигнала DTR Arduino автоматически перезагрузится.

Handshake

SerialPort1.Handshake = Handshake.None

Arduino не выполняет рукопожатия при установлении последовательного соединения, поэтому мы устанавливаем Handshake в None.

Устранение неполадок

Это очень простой пример, демонстрирующий основы создания последовательного соединения между Arduino и компьютером. Код можно улучшить:

  • COM-порт остаётся открытым всё время. Для более сложных приложений может быть лучше открывать и закрывать порт по мере необходимости.

  • Приложение просто отображает полученные данные. Данные не проверяются и могут быть чем угодно, включая непечатаемые символы.

Скачать

Скачать скетч Arduino

Скачать проект Visual Basic. Для использования файлов проекта VB необходимо установить Visual Studio.

Следующие шаги

В части 2 мы продвинемся дальше, а в части 3 начнём управлять Arduino.

Дополнительно о работе с последовательными данными

Если вы хотите узнать больше об использовании последовательных данных с Arduino, ознакомьтесь с Arduino Serial Guides.