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 Community — это полнофункциональная IDE и система разработки, бесплатная для студентов, участников open-source проектов и индивидуальных разработчиков. Нюанс — нужна учётная запись Microsoft :-(
Если вы устанавливаете 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, у вас скорее всего будет другой.
Я использую IDE версии 1. Я использую портативные IDE, а новая V2 IDE пока не поддерживает полную портативную установку.
Всё, что мы собираемся сделать — это заменить монитор порта приложением Visual Basic.
Открытие приложения Visual Basic в Visual Studio
Предполагая, что Visual Studio установлена, скачайте проект Arduino-Visual-Basic-Part-1 VB. Скачивается zip-файл, внутри которого находится папка с файлами проекта.
Самый простой способ открыть проект — дважды кликнуть на файл .sln.
Приложения Visual Basic, как правило, состоят из двух частей: формы и кода. Форма создаётся с помощью дизайнера. Код создаётся в текстовом редакторе.
Примечание для педантов: форму также можно создать в коде, если захотите.
Дважды кликните на файл Arduino_Visual-Basic.sln.sln. Это должно запустить Visual Studio и загрузить проект.
Если Visual Studio откроется с пустым рабочим пространством, кликните правой кнопкой на Form1.vb (вверху справа) и выберите View Designer, или нажмите Shift + F7 (или просто дважды кликните Form1.vb).
Главная форма
В этом примере используется очень простая форма, которая содержит всего 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).
Тестирование приложения Visual Basic
Давайте проверим, работает ли приложение. Подключите Arduino к ПК и убедитесь, какой COM-порт использует Arduino. У меня это COM14, у вас скорее всего другой.
В Visual Studio нажмите кнопку Start в верхней части экрана.
Код скомпилируется и приложение запустится. Вы должны увидеть главную форму. Пока всё хорошо.
Выберите COM-порт, к которому подключена Arduino, и нажмите CONNECT.
Если проблем нет, приложение подключится к 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-порт, который уже используется, без обработки ошибок.
Вместо аварийного завершения приложения, конструкция 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.