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 строится на простом соглашении между двумя сторонами:
- Инициализация. При запуске Arduino отправляет байт
65(ASCIIA). Это сигнал компьютеру: «Я готов, жду твоего ответа». - Подтверждение. Компьютерная программа (Processing, Max/MSP и т.д.) принимает
Aи отвечает любым байтом — например, тем жеA. - Передача данных. Arduino читает ответ, снимает показания с трёх датчиков и отправляет их тремя байтами.
- Ожидание следующего запроса. После отправки 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 не выходит из режима ожидания A | Processing-скетч не отправил ответный байт при старте |
| Значения датчиков «зависают» на максимуме | Делитель напряжения собран неверно, 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