Главная chevron_right Уроки chevron_right Встроенные примеры chevron_right Связь chevron_right Serial Call and Response (handshaking): многобайтовая передача данных

Serial Call and Response (handshaking): многобайтовая передача данных

Передача нескольких переменных с Arduino на компьютер методом запрос-ответ (handshaking) через последовательный порт.

В этом уроке разбирается многобайтовый обмен данными между Arduino и компьютером с использованием метода «запрос-ответ» (call-and-response, или handshaking). Подход прост: Arduino не шлёт данные постоянным потоком, а ждёт сигнала от компьютера — и только тогда отправляет очередную порцию значений.

Скетч при старте посылает символ ASCII A (байт со значением 65) и продолжает повторять его до тех пор, пока не получит ответ от компьютера. После этого Arduino отправляет три значения датчиков в виде одиночных байт и снова ждёт запроса. Такой «рукопожатный» протокол исключает переполнение буфера и рассинхронизацию сторон.

Данные можно смотреть в Serial Monitor Arduino IDE, а также принимать в Processing, Flash, PD или Max/MSP — примеры кода для двух последних приведены ниже.

Необходимое железо

  • Arduino Board
  • 2 аналоговых датчика (потенциометр, фоторезистор, FSR и т.п.)
  • Кнопка (тактовая)
  • 3 резистора по 10 кОм
  • Провода-перемычки
  • Макетная плата

Необходимое ПО

Схема подключения

Подключите аналоговые датчики к пинам A0 и A1, используя резисторы 10 кОм в качестве делителей напряжения. Кнопку подключите к цифровому пину 2 с резистором 10 кОм на GND (подтяжка к земле).

Схема подключения Arduino UNO с макетной платой, двумя датчиками силы (FSR), кнопкой и резистором.

Принципиальная схема

На схеме: Arduino UNO, два датчика-резистора (SENSE RESISTORS) с резисторами 10 кОм, кнопка (PUSH BUTTON) с подтягивающим резистором 10 кОм.

Как это работает

Протокол handshaking строится на простом соглашении между двумя сторонами:

  1. Инициализация. При запуске Arduino отправляет байт 65 (ASCII A). Это сигнал компьютеру: «Я готов, жду твоего ответа».
  2. Подтверждение. Компьютерная программа (Processing, Max/MSP и т.д.) принимает A и отвечает любым байтом — например, тем же A.
  3. Передача данных. Arduino читает ответ, снимает показания с трёх датчиков и отправляет их тремя байтами.
  4. Ожидание следующего запроса. После отправки Arduino замолкает и ждёт нового байта от компьютера. Компьютер, обработав данные, снова шлёт A — цикл повторяется.

Это принципиально отличается от «слепой» непрерывной отправки: данные передаются ровно тогда, когда принимающая сторона готова их обработать.

Код Arduino

/*

  Запрос и ответ по Serial

  Язык: Wiring/Arduino

  При запуске программа отправляет ASCII-символ A (байт со значением 65) и повторяет отправку,

  пока не получит данные. Затем она ждёт байт в последовательном порту и

  отправляет три значения датчиков каждый раз, когда приходит новый байт.

  Схема подключения:

  - потенциометры подключены к аналоговым входам 0 и 1

  - кнопка подключена к цифровому выводу 2

  создано 26 сентября 2005

  автор: Tom Igoe

  изменено 24 апреля 2012

  автор: Tom Igoe and Scott Fitzgerald

  Благодарность Greg Shakar и Scott Fitzgerald за улучшения

  Код этого примера находится в общественном достоянии.

  https://www.arduino.cc/en/Tutorial/SerialCallResponse

*/

int firstSensor = 0;    // первый аналоговый датчик
int secondSensor = 0;   // второй аналоговый датчик
int thirdSensor = 0;    // цифровой датчик
int inByte = 0;         // входящий байт из Serial

void setup() {

  // запускаем последовательный порт на скорости 9600 бод:

  Serial.begin(9600);

  while (!Serial) {

    ; // ждём подключения последовательного порта. Нужно только для плат с нативным USB

  }

  pinMode(2, INPUT);   // цифровой датчик is on digital pin 2

  establishContact();  // отправляем байт для установки связи, пока приёмник не ответит
}

void loop() {

  // если пришёл корректный байт, считываем аналоговые входы:

  if (Serial.available() > 0) {

    // получаем входящий байт:

    inByte = Serial.read();

    // считываем первый аналоговый вход и делим на 4, чтобы получить диапазон 0-255:

    firstSensor = analogRead(A0) / 4;

    // ждём 10 мс, чтобы АЦП успел стабилизироваться:

    delay(10);

    // считываем второй аналоговый вход и делим на 4, чтобы получить диапазон 0-255:

    secondSensor = analogRead(1) / 4;

    // считываем кнопку и переводим значение в 0 или 255

    thirdSensor = map(digitalRead(2), 0, 1, 0, 255);

    // отправляем значения датчиков:

    Serial.write(firstSensor);

    Serial.write(secondSensor);

    Serial.write(thirdSensor);

  }
}

void establishContact() {

  while (Serial.available() <= 0) {

    Serial.print('A');   // отправляем заглавную A

    delay(300);

  }
}

/* Скетч Processing для запуска вместе с этим примером:

  // Код этого примера находится в общественном достоянии.

  import processing.serial.*;

  int bgcolor;           // цвет фона

  int fgcolor;           // цвет заливки

  Serial myPort;                       // последовательный порт

  int[] serialInArray = new int[3];    // массив для полученных данных

  int serialCount = 0;                 // счётчик принятых байтов

  int xpos, ypos;                // начальная позиция шарика

  boolean firstContact = false;        // получили ли уже ответ от микроконтроллера

  void setup() {

    size(256, 256);  // размер области рисования

    noStroke();      // следующий объект рисуется без обводки

    // задаём начальную позицию шарика по центру области

    xpos = width / 2;

    ypos = height / 2;

    // выводим список последовательных портов для отладки

    // если используется Processing 2.1 или новее, применяйте Serial.printArray()

    println(Serial.list());

    // на моём Mac первый порт в списке Serial обычно соответствует FTDI-адаптеру

    // поэтому здесь открывается Serial.list()[0].

    // в Windows такой вариант обычно открывает COM1.

    // откройте тот порт, который используется у вас.

    String portName = Serial.list()[0];

    myPort = new Serial(this, portName, 9600);

  }

  void draw() {

    background(bgcolor);

    fill(fgcolor);

    // рисуем фигуру

    ellipse(xpos, ypos, 20, 20);

  }

  void serialEvent(Serial myPort) {

    // считываем байт из последовательного порта:

    int inByte = myPort.read();

    // если это первый принятый байт и это A, очищаем последовательный

    // буфер и отмечаем, что контакт с микроконтроллером установлен.

    // иначе добавляем входящий байт в массив:

    if (firstContact == false) {

      if (inByte == 'A') {

        myPort.clear();          // очищаем буфер последовательного порта

        firstContact = true;     // первый контакт с микроконтроллером установлен

        myPort.write('A');       // запрашиваем следующую порцию данных

      }

    }

    else {

      // добавляем последний байт из последовательного порта в массив:

      serialInArray[serialCount] = inByte;

      serialCount++;

      // если накопилось 3 байта:

      if (serialCount > 2 ) {

        xpos = serialInArray[0];

        ypos = serialInArray[1];

        fgcolor = serialInArray[2];

        // выводим значения только для отладки:

        println(xpos + "\t" + ypos + "\t" + fgcolor);

        // отправляем заглавную A, чтобы запросить новые показания датчиков:

        myPort.write('A');

        // сбрасываем serialCount:

        serialCount = 0;

      }

    }

  }

*/

/* Патч Max/MSP версии 5 для запуска вместе с этим примером:

----------begin_max5_patcher----------

3908.3oc6ckziiaiE9b0+J3XjCIXpp.WzZNMURv.jCInQ5fYNjNngrDssRKK

4nkp6JA4+973hrkrsjncKu0SRiXasQ83G+dKj7QV+4qtaxzrOxKlf9Zzuft6

t+7U2cm7ThSbm936lrL3igIAExaaRJ+CYS+sI2qtTI+ikxSuBMKNojm+N3D4

Aua5KkPwpuoUAkgKhSm+tbdXo5cQXVOhuGwrohuHD4WT7iXzupen3HY4BuqG

rH0kzrrzxzfkb4kdJONHo9JoUKiSS3kRgjt4jYUk0mkznPJh+CYgHewpSqty

xWVwUh3jIqkEYEfmqQEMr.ETbB+YddQbVZix+tIAqV03z203QDX4ukIKHm6W

ep3T0ovqOUN+435m2Rcx+5U0E+FTzVBh9xOsHXIh5YuADg1x4IYgumG0r3mj

shmFmtJmWvSKCJ0um0WNhOKnJo7c6GmZe8YAg7Ne381Rc2j44wQYoBgn0SJN

c8qCHH1RhQqJi7NRCVsmGt.pGUESCxE31zDdCV.PRyxRZeo0MU.WOHMdYPIu

LVIrT75BMd4p73zxVuHdZ.TFKJByyRRZUTpq77dtRDzZFx+PbT4BYY0DJgaO

dUcSvj0XTT7bdQY6yUFLun8YZo71jl0TIt042RYNLa4RfCTWfsznKWDWfJpl

tJHrbgV6t.AZInfzWP.4INpJHA8za91u+6QN1nk7hh.PpQwonxEbTAWzpilV

MimilkmsDtPbo3TPiUdY0pGa9ZShS4gYUJz1pwE1iwCpxbAgJI9DGGwWNzFT

ksLf3z7M0MybG6Hj1WngsD7VEXS8j5q7Wu5U0+39ir8QJJS5GMHdtRimL4m1

0e1EVX0YsE2YssINriYRoFRyWVMoRRUGQvnkmms3pnXDYHbBKMPpIOL5i1s8

3rMPwFcRCsGRyPH780.8HBnpWz.vlEQBWJ+0CSunehJSmJxiIZRtNGhhDYrU

jt3ZQyA2fHJhZDifXIQHUHH8oGYgOREI5nqHIzhFWUndPyBdB3VzHJGwUhkV

rgvRl2UCVNMHcd234lf1DN16HFEIdHt99A5hrp7v5WWMSBQZgMP.Tkwoqig8

W1.Sn1f3h3nn1wLpBypPDzlJ7XinEGkLiMPloWOhrgR7dpZWJQV1faDy35Qj

MThMFkWFGsJChQPqrQp8iorV6Q28HBVF4nMVDJj7f1xyYACFScisg.ruLHOW

uMUS4Am4pI4PTnHi.6bi02HNzSYnDBe4cgAgKzRk1jc8PJLoH3Ydz6.Q.7K8

tfxx73oUkJq1MGuCy5TpAi.POWZ3AenidLOOIaZPhdjZVW3sdk6LXEGzHb7p

Mfr7SEy3SXHyBSxJ3J2ncNNYVJsXG6Me10nj4cfCRFdTFjLo7q3SiCpjjEDM

.nvra.GN39.E2CDTHWXPo8.xzfqrHCHKnf5QUYUVdoZPUjCSC7LU8.XtTUXl

X8vr51GjwFGLC2AlMdLkU4RiaRrnmJuiudnDk0ZW+9p6TuKBe433JUCzp6fU

iOF0SUk2UQYUPNTEkiZubvKa1tsmgL5SCTXGHnnG0CceLpkpR9Rs28IUESWl

EwWNKfHlg.zj6Ee7S+nE8A+m9F7Cu40u9gMm+aRp3kYYkKd3GDOz5y+c7b96

K9gfvuIK68uNO6g2vUUL80WxihCVFD9vlB30e2SOrmxUb527RZ3nZNrljGrR

70vs1J9suWuZ3zaHVdG3RIJLgGj2Gfn6TcGcstEfvtH.hpFLlnBndjOLGQAI

z98BXc6yQxghmOn6gZqj0ShPOXhynLOjzCESt+XwE8TxrCvrdXo16rqnLgvb

HaFmbh29QD+K0DyNdjDwvzQL.NXpoMvoOBxkger0HwMRQbpbCh91fjjG9Idw

prTH9SzaSea5a.GQEPnnh43WNefMlsOgx18n.vgUNO.tKl7tDyI3iHzafJHZ

VVNedVEbGgYIY42i93prB0i7B7KT1LnnCiyAiinpBnsPV7OG.tYKfBsrJOkG

UG5aq26iJw6GyJ4eM5mEgEKaNQPMEBUp.t8.krplOVTlZdJAW27bjvGK7p2p

HQPgLOSJDYv4E9gQBYBjMUselRxDy+4WplIzm9JQAWOEmfb.E364B43CAwp5

uRRDEv8hWXprjADMUOYpOg9.bVQpEfhKgGCnAnk.rghBJCdTVICA3sDvAhE5

oU4hf67ea5zWPuILqrD8uiK+i477fjHIt9y.V88yy3uMsZUj7wnxGKNAdPx5

fAZMErDZOcJU4M01WFQokix.pKa+JE1WacmnKFeYd7b.0PeIzB8Kk+5WIZpB

Ejt34KJeHgOCh4HK8Y3QiAkAfs8TRhhOkG7AAGQf0qxyfmQxa+PLb8Ex.2PS

4BdO5GB9Hvg+cfJCMofAIMu9Qz+UPCjckqVJlEmyA8Bf.rC6.3hAEuG8TdTU

bZljQ0nr1ayIqmTwQYfyRGafZhur5vfuyMSqYNWmtAPwWHalDSuUgT0Bosh.

JpAR89Y6Ez5QEfPTQO4J0DHLInIliz8BZV2JfV3Bd36qsQwAVVXbr1BGXp6s

Sd5sSDruo74wofx.HxUgxQwTnMLqTXvRmiGh2PUZr5pBynKChjl6feNUjSRn

hEUfRPT1GfG9Ik4TQBm.hEZZ.bc38HjAMKGzDRijEm1ifx1dbgzQyKh6FZc3

wOCkRJH+KUh0daWs6wzltWx1puXxlWW6NZWY2JiTBzzILRIANku02NourySM

VI1VJTvQZff32AJr+dS9e34QAoA6EGXlGFH9yk7yyQAlVd3SR94g+TxOu1sU

Flgd6ICI96LzazyPu1cgqsZ8r74SgF.65+efbMf4pGHT7lgHh30Sha3N5Ia.

oqjMf7nsuMwycf7iYDybiAAVr3eC.oTMjpzEr8GDRc9bFRGHYXDrzg.Tlx+q

NW8TY1IkzCfZ2IftkQstbB08HUezoDS+oFyI.cWIhWBaDiUo7qIrDO7f.L6n

AXqCmyNT9act.z+Iv.GR0uES0ZXfjdz.IczAxQOUR+zvRsUTigRxmyPYeNlj

yXv8Peef2ZFzuLzWPPeAE8ELzWXYlhe8WzAcUg+b1UkIoCLzIH60zwASGXau

a1Dq2nUY.sox4vng+m0nACePngC9lEMLZMBPodOxf+yx5d4uMCTHm3kJvIIG

jcLMedEQldkjpoBkQyjY1Hk.hmSY95Iwos8NDb9VSlIWOIntqgxryUjL6bCJ

y1lli5tWWxrQ7YmqGYlc6shK1iY2dr0wtNjYxgHyzaq0OznY235awCr8zSz6

EGd1QNUKf.74dADTBbTbeotjpW95IolY0WpKYONY8M83Rx2MChx3fL+iG5Mm

tXpdmvXj8uTvaAL1WjbbarQD4Z6kXBpnm6a69oKV2PY9WY174IbC3CaRQ9iK

Q4sYGQpwdtZ5wFrc7n569.M83OOR5ydSB1ZcAWCxdbKuavz9LILxfD.wWO.W

Nq+Zu4Es+AP6s5p9jDWH8ET+c85+XbW0.N1nDCTD7U4DGc6ohnU019fS7kQ0

o43luuOGjv5agHp0DT.CysOfgLR3xXlXTUKm16RivRsn3z0O6cl3YScAvtrb

hwekGB7BZuqESUzBJWmCvK7t9HF8Ts6cUAPoFWso3aP8ApWyJ3wqOPo2pJDC

BQ0NI0Pj8QCQ2r1L5vKaU5lDRYX7yRur1UYYZmJQ9iDHwN9dndB5n5ejflmm

UsBwLHnDkKXWRuAkb3NeuzqRstiQGP.fCQFdHNzaE.8u58Nz9svFE9SGIE1X

kv9Iwfl1BdNWjA7xcThsWCS847loyFD8pZq2E2F04lYULzBTDYhrFSDDJdjo

fisN2NUN26e4xRu51zD5ZseJ4HC63WyIX6jRqsp0jangBnK.Qlo58PCpWevt

ahzqK7fbKsdX6R64aao8LmWhBPh9jKVAPMzb5a2cV6opdWHneMmqMEmAGsPh

ieigIjV+4gF1GgbMNXg+NH44YaRYyd..S1ThHzKhFwwGRaWVITqyj9FvPqMT

d0pDuSqDrOGF.Uogf.juCFi9WAUkYR+rFPanDcPG8SbrtjyG03ZQ8m3AqC5H

NcUUoXSwVrqXKVcZu.5ZnkwIfIVdXVZTwAuTTUiYuxwjZDK6ZgnRtYV8tJmP

hEcuXgz2Goxyaiw35UkaWbpqtfzD02oUkkYqi.YQbZqIIWrIljFolsdmMKFR

wCJ2+DTn.9QlkOld+d9Qy9IJdpLfy05Ik2b8GsG9h8rdm1ZFx1FrmmlA2snw

qI9Mcdi2nr6q3Gc87nLawurbw1dda+tMyGJ9HaQmlkGwy6davisMgrkM65oz

eulfYCzG46am8tSDK144xV4cEvVMTRXq9CIX8+ALNWb6sttKNkiZetnbz+lx

cQnb1Nds2C0tvLNe14hwQtxYbxhqc17qHfamUcZZ3NYSWqjJuiDoizZ+ud2j

naRK4k3346IIVdR1kKiQjM39adMamvc6n+Xp36Yf3SIGh3uKbquqs1JksTII

kuJ7RrZSFb2Cn9j5a6DT8cMo0iczU+lsYaU8YNVh5k5uzJLU26ZcfuJE6XLY

0mcRp9NTCp+L+Ap+in7Xf3b9jFQBLtIY06PbrGhcrU6N00Qlaf9N0+QPo9nS

P6qsI7aYNLSNOHpsAxis0ggnZLjYqyyFkdSqinVsPaqSDZaYBZ6c93uLCjGm

iCroJVLzU45iNE.pIUfs3TWb.0FejHp9uANr0GcJPTroFDNOHpkIweLnI1QT

dHl3P7LhOF3Ahd9rnvLwAMy5JSdNezGlsIsW9mW44r26js+alhxjlkdhN0YE

YqiH5MTeWo6D4Qm.ieLS7OynmuVGSbmbFUlnWWhiQlhOeN+Yl35bq.tGo9JR

cj8AVqdz7nSgVB9zNj.FTOU68o5d9KO5TUOGxVMw+jTO8T6wqD0hEiHsOJO5

TTOMoS.zlqN0SpZjz6GcH05ylVM0jwuidlkmAif374ih5M5QPfccr8Hqifff

otN8pt3hUcaWu8nosBhwmD0Epw5KmoF.poxy4YHbnjqfPJqcM3Y2vun7nS.i

f3eETiqcRX2LR.4QmhZrkoCSGwzZrqKHrVR8caari+55d2caPqmq5n.ywe8Q

WrZL9fpwVXeaogMByE6y1SMdjk+gbavbN7fYvVtt1C2XwHJSzpk+tidUO25H

UB9onw9mlFQ10fhpZBaDatcMTTEGcJpwzqg92qqiVtM6Cu0IRQ0ndEdfCAqV

l0qYAUmPrctbxO4XCuPMa1asYzKDks1D52ZCne6Mednz9qW8+.vfqkDA

-----------end_max5_patcher-----------

*/

Код Processing

Скопируйте скетч Processing из примера выше. По мере изменения показаний аналогового датчика на экране будет двигаться шар — примерно так:

Окно вывода SerialCallResponse: белый круг на чёрном фоне, положение которого определяется данными с датчиков.

Шар появляется только при нажатой кнопке — это третий байт, который Arduino передаёт как цифровое состояние пина 2.

Код Max/MSP

Патч Max/MSP выглядит следующим образом. Скопируйте текст из примера выше.

Патч считывает буфер последовательного порта каждые 10 мс (Read serial input buffer every 10 milliseconds), ждёт ASCII A для начала обмена, затем группирует три входящих байта и использует их для управления координатами и цветом круга.

Практические замечания

  • Масштаб значений. analogRead() возвращает числа от 0 до 1023, но Serial.write() отправляет один байт (0–255). Поэтому в скетче показания делятся на 4 с помощью map() или сдвига вправо. Не забывайте об этом при разработке собственного приёмника.
  • Скорость порта. В примере используется 9600 бод. Если вы меняете скорость в скетче, обязательно меняйте её и в Processing/Max-патче — иначе данные придут в виде мусора.
  • Кнопка без дребезга. Резистор 10 кОм на GND удерживает пин 2 в состоянии LOW, пока кнопка не нажата. Программный debounce здесь не нужен, так как данные читаются один раз за цикл запрос-ответ.
  • Только один получатель. Serial Monitor и Processing не могут одновременно читать один порт. Закройте Serial Monitor перед запуском Processing-скетча.

Типичные ошибки

СимптомВероятная причина
Шар не двигается, хотя скетч загруженSerial Monitor открыт — закройте его
Шар «прыгает» хаотичноСкорость порта в Processing не совпадает со скетчем
Arduino не выходит из режима ожидания AProcessing-скетч не отправил ответный байт при старте
Значения датчиков «зависают» на максимумеДелитель напряжения собран неверно, A0/A1 висят в воздухе

Как проверить результат без Processing

Откройте Serial Monitor (скорость 9600, режим No line ending). Введите символ A и нажмите «Send». Arduino ответит тремя байтами — вы увидите их в мониторе. Повторяйте отправку A, чтобы получать новые данные. Это удобный способ убедиться, что скетч работает правильно, ещё до подключения Processing или Max/MSP.

Дополнительные материалы

Больше базовых примеров вы найдёте в разделе built-in examples.

Также доступен language reference — подробный справочник по языку программирования Arduino.

Последнее обновление: 29.07.2015, SM