#

Как подключить Ардуино к компьютеру. Бинарная передача сигнала.

При создании многоканальных систем регистрации данных, требуется передать относительно большое число измеренных параметров в персональный компьютер, где происходит их обработка и сохранение. Данная задача довольно просто решается на Ардуино (многоканальный вольтметр на Ардуино). Ограничением может быть разве что АЦП Ардуино, разрешения которого (10 бит) может оказаться не достаточно для решения некоторых задач. Поэтому, при необходимости, можно подключать внешний АЦП. Ведь любой электронный датчик преобразует измеряемую величину в напряжение электрического тока.

Проблема здесь заключается в том, что при большом числе измеряемых параметров и высокой частоте опроса датчиков (частоте дискретизации) необходимо успеть передать всю эту информацию через последовательный интерфейс RS-232 (и USB) в персональный компьютер. При текстовой передаче данных информационный пакет получается сильно раздутым и не фиксированной длины. Ведь каждый разряд десятичного числа передается в виде одного байта данных, а значит занимает один пакет протокола RS-232 (или UART микроконтроллера). Это очень не экономичный способ передачи, так как вместо 10…16 бит, требуется передать до 32 бит данных, не считая спецсимволов! Из-за этого могут возникнуть непрогнозируемые потери данных. Поэтому такой метод возможен для целей отладки и при небольшой числе передаваемых параметров.
В данной статье рассмотрим решение задачи построения многоканальной измерительной системы на Ардуино. Её задачами является:

  1. Измерение аналоговых сигналов с датчиков (подключение Ардуино);
  2. Кодирование измеренных параметров в набор бит;
  3. Отправка на персональный компьютер (ПК) в виде набора бит по интерфейсу USB (Ардуино serial);
  4. Получение данных в программе Lazarus и выделение из него отправленных чисел;
  5. Визуализация полученных чисел в форме графика в зависимости от времени;
  6. Сохранение в текстовый файл.

Надо отметить, что в Интернет данная информация разрозненна и сложно найти скомбинированную информацию по этой задаче!

Как получить и передать сигнал в текстовом виде было описано в предыдущей статье. Также желательно ознакомится с подробными описаниями способов сохранения сигнала в текстовом и бинарном форматах.

Подключение Ардуино в среде Lazarus (язык pascal)

Сделано в последней на данный момент версии Lazarus 2.0.8 (в других вервиях возможны незначительное изменения)

Для доступа к последовательному порту будет использован модуль synaser. Файлы synaser.pas, synafpc.pas, synautil.pas, нужно поместить в папку с проектом.

Разработка интерфейса в Lazarus

Вначале необходимо создать пустой проект и настроить его в меню Проект -> Параметры проекта.
Сохранить проект в отдельной папке: Проект -> Сохранить проект как…
На форму поместить следующие элементы:

  1. TEdit (из вкладки Standart) — Edit1 для ввода имени файла
  2. TLabel (из вкладки Standart) — Label1 для отображения надписи «Файл», Label2 для отображения надписи «Порт»
  3. TButton (из вкладки Standart) — StartB для старта мониторинга порта, StopB для остановки мониторинга
  4. TToggleBox (из вкладки Standart) — RecTB кнопка с фиксацией для разрешения записи в файл
  5. TChаrt (из вкладки Chart)
    Должно получиться так:
Как подключить Ардуино к компьютеру. Бинарная передача сигнала.

Рассмотрим вариант измерения на Ардуино двух параметров, по тому же принципу можно измерять большее число параметров.
Под каждый параметр нужно создать серию для визуализации: Правой кнопкой мыши по графику -> Редактор диаграммы -> Добавить -> График.

две серии tchart

Настройка исходного кода Pascal

Теперь нужно создать обработку событий в исходном коде программы.
Понадобятся три процедуры:

  1. Инициализации procedure TForm1.FormShow(Sender: TObject);
    В инспекторе объектов выбрать Form1 -> вкладка События -> OnShow (нажать …) Функция автоматически создается средой
  2. Обработка нажания на кнопку Старт procedure TForm1.StartBClick(Sender: TObject);
    На форме совершить двойное нажатие на кнопку StartB
  3. Обработка нажания на кнопку Стоп procedure TForm1.StopBClick(Sender: TObject);
    На форме совершить двойное нажатие на кнопку StopB

Для доступа к компонентам порта нужно дописать в раздел uses исходного кода модуля: synaser,jwawinbase, jwawinnt.

Затем нужно задать объект доступа к порту (ser: TBlockSerial;) и признак разрешения мониторинга порта (mon:boolean;). Все это объявляется в разделе глобальных переменных var (перед implementation).

Протокол передачи данных

Теперь необходимо принять протокол передачи информации. Например, такой:

  1. Начало посылки отмечается двумя байтами, заполненными логическими единицами, т.е. каждый равен 255 или B11111111. Так как принимаемые из АЦП числа 10 бит, то два байта (16 бит) они никогда не заполнят полностью и они не будут повторять выбранный маркер старта. 2. измерение первого канала, состоящее из двух байт целого числа без знака. Вначале передается первая (младшая) часть числа, а затем остаток старшая часть). Причем если число больше 16 бит, то нужно выделять большее число байт под него. Но в Ардуино 10 битный АЦП и 2 байта достаточно.
  2. Измерение второго канала по такому же принципу, что и первое.
  3. Если параметров больше — можно добавлять в посылку еще измерения. Разделять их нет необходимости, так как размер точно фиксирован в 2 байта.

Коротко протокол выглядит так: FFLHLH (где F — стартовый байт, L — младший байт, H — старший байт)
Можно сравнить экономичность данного протокола с текстовым для двух параметров: бинарный требует 48 бит, текстовый — до 80 бит. Т.е. даже в случае 2 параметров выигрыш в 32 бита, который будет расти с повышением числа каналов измерения!

Разработка функций обработки событий на Рascal

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

Полный исходный код Pascal модуля Lazarus с пояснениями представлен ниже:

unit Unit1;
{ Обучающий модуль приема многоканальных данных из Ардуино в бинарном виде.
 (с) Роман Исаков, 2020
 (с) LabData.ru, 2020
}
{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, TAGraph, TASeries, Forms, Controls, Graphics,
  Dialogs, StdCtrls,
  synaser,jwawinbase, jwawinnt; // модули подключения последовательного порта


type

  { TForm1 }

  TForm1 = class(TForm)
    Chart1LineSeries1: TLineSeries;
    Chart1LineSeries2: TLineSeries;
    Edit1: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    StopB: TButton;
    StartB: TButton;
    Chart1: TChart;
    COMselectCB: TComboBox;
    RecTB: TToggleBox;
    procedure FormShow(Sender: TObject);
    procedure StartBClick(Sender: TObject);
    procedure StopBClick(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;
  ser: TBlockSerial; // Переменная доступа к последовательному порту
  mon:boolean; // переменная хранит признак мониторинга порта

implementation

{$R *.lfm}

{ TForm1 }
// Инициализация
procedure TForm1.FormShow(Sender: TObject);
var
i:Integer;   // переменная текущего номера порта
Phandle:Thandle; // объект доступа к порту
begin
     // Процедура определения занятого порта
     for i:=1 to 30 do  // цикл перебора номеров порта от 1 до 30
     begin
          // Создание соединения с последовательным портом номер i
          Phandle:=CreateFile(Pchar('COM'+intToStr(i)), Generic_Read or Generic_Write,0,nil, open_existing,file_flag_overlapped,0);
          // Проверка на доступность соединения
          if Phandle<>invalid_handle_value then // если доступно соединение
          begin
               COMselectCB.Items.Add('COM'+ IntToStr(i)); // Добавить порт в список доступных
               CloseHandle(Phandle); // Закрыть соединение
          end;
     end;
     COMselectCB.ItemIndex:=COMselectCB.Items.Count-1; // Выбрать последний доступный порт
     mon:=false; // Запретить мониторинг порта
end;
// Кнопка старт
procedure TForm1.StartBClick(Sender: TObject);
const
  Fs = 200; // Установка частоты дискретизации АЦП
var
    fid:TextFile; // Переменная доступа к файлу
    val1, val2: Word; // Переменные размером в 2 байт для хранения принятых переменных
    t:Double;         // Текущая точка времени в сек
    recv_b: byte;     // Переменная для хранения количества фактически принятых байт
    High_b, Low_b: byte; // Переменная для хранения старшего и младшего байта в пачке
begin
  ser:= TBlockSerial.Create; // Создание объекта последовательного порта в памяти
  ser.RaiseExcept := True; // Установка прерывания в случае ошибок соединения
  ser.LinuxLock := False; // это требуется для работы в Linux.
  AssignFile(fid,Edit1.Text+'.csv'); // Связать файловую переменную с именем файла имя.csv, имя получается из поля ввода Edit1
  Rewrite(fid);  // Создается или перезаписывается выбранный файл
  ser.Connect(COMselectCB.Text); // открытие порта
  ser.Config(19200, 8, 'N', 0, false, false); // указываются параметры передачи данных Ардуино (скорость 19200 bpm, 8бит в пакете, без контроля четности)
  mon:=true; // Разрешить мониторинг порта
  Chart1LineSeries1.Clear; // Очистка 1 последовательности графика
  Chart1LineSeries2.Clear; // Очистка 2 последовательности графика
  Chart1.Extent.XMin:=0;  // Установка точки отсчета на графике времени от 0 сек
  Chart1.Extent.XMax:=10; // Установка предела по оси времени на 10 сек
  t:=0; // Обнуление времени
  while mon do  // Цикл пока разрешен мониторинг
  begin
       repeat // Цикл ожидания 2 стартовых байт (слова)
       begin
            Low_b  := ser.RecvByte(2000); // Получить первый бит
            High_b := ser.RecvByte(2000); // Получить второй бит
            { Примечание: в функции RecvByte в скобках указывается период ожидания байта из порта
            }
       end
       until ((Low_b = 255) and (High_b = 255)); // Условие двух подряд заполненных байт (255)
       // Получение пакета данных
       recv_b := ser.RecvBufferEx(@val1, 2, 2000); // Получение первой последовательности из 2 байт
       recv_b := recv_b + ser.RecvBufferEx(@val2, 2, 2000); // Получение второй последовательности из 2 байт
       { Примечание:
         функция RecvBufferEx(addr, baits, wait)
         считывает заданное в baits число байт данных (при заданных настройках протокола) из входного буфера порта и помещает их по адресу памяти addr.
         Если данных пока нет, то она будет ждать заданное в wait число миллисекунд.
         Возвращает число поученных байт, поэтому если оно отличается от baits - значит ошибка!
         Обратите внимание, что в эту функцию нужно передавать не саму переменную, а её адрес, через символ @
       }
       if recv_b <> 4 then // Проверка на полное считывание пакета
       begin // Если нет - закрыть соединение
             CloseFile(fid); // Закрыть файл
             ser.CloseSocket; // Закрыть порт
             FreeAndNil(ser); // Освободить и удалить объект порта
             exit; // Выйти из функции чтения
       end;
       // По аналогии можно получать еще параметры ...
       if RecTB.Checked then
          Writeln(fid,FloatToStr(t)+';'+FloatToStr(val1)+';'+FloatToStr(val2)); // Если переключатель записи активирован - записать в файл с разделителем ";"
       t:=t+1/Fs; // Добавить следующее время исходя из установленной Fs. Следующее время увеличивается на период равный обратный частоте дискретизации.
       Chart1LineSeries1.AddXY(t,val1); // вывод на график первого числа.
       Chart1LineSeries2.AddXY(t,val2); // вывод на график второго числа.
       // Просмотр графика с фиксированным временным окном
       if t> Chart1.Extent.XMax then // Если текущее время стало больше установленого предела на графике, то ...
       begin
            Chart1.Extent.XMax:=t;  // Установить текущий максимум равный текущему времени
            Chart1.Extent.XMin:=t-10; // Установить текущий минимум равный текущему времени - 10 сек (окно времени)
            Chart1LineSeries1.Delete(0); // Удаление первого элемента в серии графика
       end;
      Application.ProcessMessages; // Позволить приложению отобразить новые данные
  end;
  CloseFile(fid); // Закрыть файл
  ser.CloseSocket; // Закрыть порт
  FreeAndNil(ser); // Освободить и удалить объект порта
end;
// Кнопка стоп
procedure TForm1.StopBClick(Sender: TObject);
begin
  mon:=false; // Запретить мониторинг порта
end;

end.

Работа с Ардуино

Теперь нужно собрать тестовый макет на базе Ардуино. Здесь представлен пример на Arduino UNO, однако должно работать на всех базовых моделях.

Как подключить Ардуино к компьютеру. Бинарная передача сигнала.

Можно сделать подключение только одного датчика, а второй эмулировать программно.

Программирование Ардуино

В среде Arduino IDE необходимо создать новый проект: Файл -> Новый
Затем сохранить его: Файл -> Сохранить как…
Затем можно написать программу для микроконтроллера.
Данная программа должна через строго определенное время выполнять аналого-цифровое преобразование (АЦП) и отправлять посылку в порт по определенному выше протоколу. Внимание! Использование обычной задержки delay не обеспечивает пригодной точности задания частоты дискретизации.

Ниже представлен исходный код для Arduino, реализующий правильный алгоритм задания периода дискретизации. Он основан на вычислении времени, прошедшего с момента предыдущего измерения, привязавшись к внутреннему таймеру. Данный таймер стабилизирован кварцевым резонатором на плате Ардуино, поэтому достаточно точен.

Код Arduino:

//Обучающий модуль передачи многоканальных данных в ПК в бинарном виде.
// (с) Роман Исаков, 2020
// (с) LabData.ru, 2020    
 
// Объявление внешних подключений
const int analogInPin = A0;  // Разъем для подключения аналогового датчика
const int Fs = 200;          // Частота дискретизации сигнала (Гц)
// Объявление переменных
unsigned int sensorValue1 = 0; // Результат измерения 1
unsigned int sensorValue2 = 0; // Результат измерения 2
uint32_t ms_old = 0;         // Время предыдущего измерения
// Процедура инициализации
void setup() {
  Serial.begin(19200);          // Настройка скорости передачи на ПК (бод)
  pinMode( analogInPin, INPUT );// Настройка входа аналоговой части 
  ms_old = millis();          // Инициализировать предыдущее время измерения текущим временем в мс
}
// Процедура отправки оного числа в порт
void SendValue(unsigned int Value) { 
  byte LB = 0, HB = 0; // Инициализировать переменные старшего и младшего байтов
  // Разбить число на два 8 битных пакета
  LB = Value  & B11111111; // Выбор первых 8 бит из числа
  HB = Value >> 8; // Сдвинуть на 8 бит и записать первые из них
  Serial.write(LB); // Отправть на ПК младший байт
  Serial.write(HB); // Отправить на ПК старший байт
}
// Процедура формирования протокола передачи и отправки двух переменных в порт
void SendPacket(unsigned int Value1, unsigned int Value2) {
 // Сфомировать протокол передачи
 Serial.write(B11111111); // Стартовый байт 1
 Serial.write(B11111111); // Стартовый байт 2
 SendValue(Value1);       // Первое число
 SendValue(Value2);       // Второе число
 // можно добавлять еще...
}
// Главный цикл работы
void loop() {
    if (millis()-ms_old >= (1000/Fs)){ // Ожидание периода дисткретизации
      sensorValue1 = analogRead(analogInPin); // Провести АЦП
      sensorValue2 = round(random(0,500)); // Сгенерировать параметр (якобы второй датчик)
      ms_old = millis(); // Сразу после АЦП нужно отметить время измерения
      SendPacket(sensorValue1, sensorValue2);   // Запуск передачи
    }
}

После этого нужно подключить макет к персональному компьютеру и не забыть выбрать правильный порт Инструменты -> Порт, а также используемую версию Ардуино: Инструменты -> Плата.

Если при загрузке не возникло ошибок, можно проверить выходные данные в мониторе порта среды Ардуино (выбрать частоту передачи 19200). Должна пойти одна строчка, состоящая из непонятных символов, включая буквы «я» (это стартовые биты). Значит микроконтроллер передает данные!

Тестирование системы

Затем можно проверить программу в Lazarus (не забыв закрыть монитор порта!).

Проверяем программу:

Здесь изображено подключение ардуино к потенциометру (датчик).

Arduino UNO подключена к потенциометру
Видео тестирования системы

Нажав Старт начнется мониторинг сигнала, нажав после на Запись, начнется регистрация в файл. По окончании нужно нажать на Стоп.

интерфейс программы с двумя сигналами, получаемыми из ардуино

Если нажималась Запись, то в папке с проектом появится файл data.csv. Его можно открыть в текстовом редакторе или в табличном процессоре типа Excel.

Все работает!
Кстати, есть модифицированные варианты Ардуино, которые имеют более точный АЦП!
При возникновении вопросов, пишите в комментариях здесь или в сообществе ВК!

(с) Роман Исаков