UART (Universal Asynchronous Receiver-Transmitter) — последовательная связь на Arduino
Подробный разбор протокола UART: как работает последовательная передача данных, формат кадра, скорость обмена и практические примеры для Arduino.
В этой статье разберём основы протокола UART (Universal Asynchronous Receiver-Transmitter) — одного из самых распространённых способов последовательной передачи данных. Именно UART работает «под капотом», когда вы отправляете данные с Arduino на компьютер через метод Serial.print().
UART — один из старейших последовательных протоколов, и несмотря на то что во многих применениях его вытеснили SPI и I2C, он по-прежнему активно используется там, где важны простота, низкая стоимость и лёгкость реализации. Протокол позволяет организовать асинхронную последовательную связь с настраиваемым форматом данных и скоростью передачи.
Для работы с UART в Arduino используется класс Serial, предоставляющий методы чтения и записи данных.
Если хотите сразу перейти к примерам — нажмите here или листайте в конец статьи.
Класс Serial
С помощью класса Serial можно отправлять и принимать данные через USB (соединение с компьютером) или через аппаратные пины RX/TX для связи с другими устройствами.
- Для отправки данных через USB используется
Serial— их можно наблюдать в Serial Monitor среды Arduino IDE. - Для отправки данных через пины RX/TX используется
Serial1. - Платы GIGA R1 WiFi, Mega 2560 и Due также имеют
Serial2иSerial3.
Основные методы класса Serial:
begin()— инициализирует последовательный порт с указанной скоростью (baud rate). Чаще всего используют9600или115200.print()— выводит данные в Serial Monitor.println()— выводит данные в Serial Monitor с переводом строки в конце.available()— проверяет, есть ли входящие данные (например, команда из Serial Monitor).read()— считывает данные из последовательного порта.write()— записывает данные в последовательный порт.
Пример инициализации обоих последовательных портов:
Serial.begin(9600); //инициализация связи через USB
Serial1.begin(9600); //связь через пины RX/TX
Класс Serial поддерживается на всех платах Arduino.
Пины UART на Arduino
По умолчанию пины TX/RX на Arduino — это D0(RX) и D1(TX). Некоторые платы имеют дополнительные последовательные порты:
| Form Factor | RX | TX | RX1 | TX1 | RX2 | TX2 | RX3 | TX3 |
|---|---|---|---|---|---|---|---|---|
| MKR | D0 | D1 | ||||||
| UNO | D0 | D1 | ||||||
| Nano | D0 | D1 | ||||||
| Mega | D0 | D1 | D19 | D18 | D17 | D16 | D15 | D14 |
Техническое описание
Как работает UART
UART передаёт данные побитно в виде последовательности: стартовый бит, биты данных, опциональный бит чётности и стоповый бит. В отличие от параллельной передачи, где несколько бит идут одновременно по разным проводам, UART отправляет один бит за раз по одному проводу. Протокол асинхронный — он не использует общий тактовый сигнал, а вместо этого обе стороны заранее договариваются о скорости (baud rate).

Сравнение последовательной и параллельной передачи данных: при параллельной передаче 8-битного сообщения нужно 8 проводов, при последовательной — один для отправки и один для приёма.
Как видно на схеме, параллельная передача 8-битного сообщения требует восемь проводов, тогда как последовательная обходится одним кабелем для отправки и одним для приёма.
Важно: для корректной работы UART между устройствами обязательно нужен общий провод GND. Без него устройства не смогут правильно различить уровни HIGH и LOW.
Ключевые компоненты
- Передатчик — собирает данные, формирует из них последовательный поток и отправляет через пин TX.
- Приёмник — получает данные через пин RX и преобразует последовательный поток обратно в параллельный формат для хост-системы.
- Baud rate — определяет скорость передачи данных.
Тайминг и синхронизация
В отличие от синхронных протоколов вроде SPI и I2C, UART работает асинхронно — без общего тактового сигнала. Синхронизация достигается за счёт заранее согласованного baud rate.
Baud Rate
Baud rate — это скорость передачи данных, измеряемая в битах в секунду (bps). Оба устройства — и передатчик, и приёмник — должны работать на одинаковом baud rate, иначе данные будут приняты с ошибками.
Чем выше baud rate, тем быстрее передача, но тем точнее должна быть синхронизация. Слишком высокая скорость при длинных линиях связи или некачественных соединениях приводит к ошибкам. Типичные значения baud rate для Arduino: 9600, 115200, 4800, 57600.
Установка baud rate в коде:
Serial.begin(9600);
Управление потоком (Flow Control)
Flow Control — механизм, позволяющий медленному устройству сигнализировать быстрому о необходимости приостановить передачу, чтобы не потерять данные.
Представьте: передатчик T отправляет непрерывный поток байт приёмнику R, который работает медленнее и не успевает обрабатывать данные. R должен уметь попросить T «подождать».
Существует два основных вида flow control:
- Аппаратный (Hardware Flow Control) — использует дополнительные провода; уровень сигнала на них указывает, продолжать или остановить передачу.
- Программный (Software Flow Control) — специальные символы передаются по обычным линиям данных для управления потоком.
Подробнее об управлении потоком UART читайте here.
Формат кадра UART
Каждый кадр UART включает стартовый бит, биты данных, опциональный бит чётности и стоповый бит.

Формат кадра UART: idle → start → data bits → parity → stop
Стартовый бит
Перед каждым кадром передаётся один стартовый бит. Он всегда имеет логический уровень LOW (0) и сигнализирует приёмнику о начале нового кадра данных.

Стартовый бит в кадре UART — всегда логический 0
Биты данных
Биты данных несут полезную информацию. Наиболее распространённая конфигурация — 8 бит (один байт), но UART поддерживает и другие варианты:
- 8 бит — самый популярный вариант; позволяет передавать байт данных, включая ASCII-символы и числа.
- 7 бит — меньше накладных расходов, 128 возможных значений.
- 6 бит — компактный формат, 64 возможных значения.

Биты данных в кадре UART
Данные кодируются в двоичном формате: каждый бит соответствует степени числа 2. Это делает UART универсальным — от текстовых символов до произвольных бинарных данных.
Важно: передатчик и приёмник должны быть настроены на одинаковое количество бит данных. Несоответствие конфигурации — одна из самых частых причин ошибок в UART-коммуникации.
Бит чётности (Parity)
Опциональный бит чётности используется для обнаружения ошибок передачи. Он обеспечивает, чтобы суммарное количество единичных битов в кадре было чётным (even) или нечётным (odd) — в зависимости от выбранного режима. Если принятое значение не совпадает с ожидаемым, фиксируется ошибка.

Бит чётности в кадре UART
Стоповый бит
Один или два стоповых бита завершают каждый кадр. Они сигнализируют об окончании передачи байта. Обычно используется один стоповый бит; два применяют там, где нужна повышенная надёжность. Полярность стопового бита зависит от конкретной конфигурации UART.

Стоповый бит в кадре UART
Serial USB: Примеры
Для передачи данных между Arduino и компьютером подключите плату кабелем USB.
Базовый пример: вывод данных
Этот пример каждую секунду отправляет строку Hello World! с Arduino на компьютер с помощью функции Serial.println().
void setup(){
Serial.begin(9600); //инициализация последовательной связи на скорости 9600 baud
}
void loop(){
Serial.println("Hello world!");
delay(1000);
}
Serial.print() / Serial.println() — основа отладки практически любого Arduino-скетча. Понимая, что происходит на плате, вы можете проектировать программы, которые сообщают о важных событиях.
Чтение данных
Чтобы отправить данные с компьютера на Arduino (из Serial Monitor), используем функции Serial.available() и Serial.read(). Сначала проверяем, есть ли входящие данные, затем считываем и выводим их обратно.
Этот скетч «эхом» возвращает всё, что вы вводите в Serial Monitor.
int incomingByte = 0; // ожидание входящих данных
void setup() {
Serial.begin(9600); //инициализация последовательной связи на скорости 9600 baud
}
void loop() {
// отправлять данные только при получении данных:
if (Serial.available() > 0) {
// читаем входящий байт:
incomingByte = Serial.read();
// выводим полученное:
Serial.print("I received: ");
Serial.println(incomingByte, DEC);
}
}
Практическое замечание: если в Serial Monitor ничего не отображается, убедитесь, что выбранный baud rate в выпадающем меню монитора совпадает со значением в begin(). Это одна из самых частых ошибок новичков.
Примеры с пинами RX/TX
В этом разделе рассмотрим примеры обмена данными между двумя платами Arduino. Соедините TX одной платы с RX другой (и наоборот) по схеме ниже:

Соединение двух плат Arduino по UART: TX ↔ RX, RX ↔ TX, GND ↔ GND
Передача и приём сообщений
Этот пример позволяет двум устройствам обмениваться текстовыми сообщениями. Загрузите скетч на обе платы:
String sendMessage;
String receivedMessage;
void setup() {
Serial.begin(9600); // инициализация Serial монитора для отладки
Serial1.begin(9600); // инициализация Serial1 для отправки данных
}
void loop() {
while (Serial1.available() > 0) {
char receivedChar = Serial1.read();
if (receivedChar == '\n') {
Serial.println(receivedMessage); // вывод полученного сообщения в Serial монитор
receivedMessage = ""; // сброс полученного сообщения
} else {
receivedMessage += receivedChar; // добавление символов к полученному сообщению
}
}
if (Serial.available() > 0) {
char inputChar = Serial.read();
if (inputChar == '\n') {
Serial1.println(sendMessage); // отправка сообщения через Serial1 с символом новой строки
sendMessage = ""; // сброс сообщения
} else {
sendMessage += inputChar; // добавление символов к сообщению
}
}
}
Для хранения входящих и исходящих сообщений объявляем две переменные типа String:
String sendMessage;
String receivedMessage;
Внутри setup() инициализируем оба порта — Serial и Serial1 — со скоростью 9600 baud:
Serial.begin(9600);
Serial1.begin(9600);
Чтение сообщений
Основная логика находится в loop(). Если получен новый байт — то есть Serial1.available() больше 0 — начинаем его обработку:
while (Serial1.available() > 0) {
...
}
Считываем один символ из буфера Serial1:
char receivedChar = Serial1.read();
Если встречается символ новой строки '\n', полученное сообщение выводится в Serial Monitor и переменная receivedMessage сбрасывается в пустую строку для следующего сообщения.
Символ '\n' — это «нажатие Enter». Он сигнализирует о конце введённой строки. Именно по нему мы понимаем, что сообщение отправлено целиком.
if (receivedChar == '\n') {
Serial.println(receivedMessage);
receivedMessage = "";
}
Отправка сообщений
Проверяем буфер Serial — есть ли там введённый текст (Serial.available() > 0):
if (Serial.available() > 0) {
...
}
Когда встречается символ новой строки '\n', сообщение отправляется через Serial1, а переменная sendMessage сбрасывается:
char inputChar = Serial.read();
if (inputChar == '\n') {
Serial1.println(sendMessage);
sendMessage = "";
}
Если введённый символ не является символом новой строки, он добавляется к текущему сообщению:
else {
sendMessage += inputChar; // добавление символов к сообщению
}
Управление встроенным светодиодом
Следующий пример позволяет управлять встроенным светодиодом через UART-сообщения.
Приёмник (Receiver)
void setup() {
pinMode(LED_BUILTIN, OUTPUT); // настройка пина LED как выход
digitalWrite(LED_BUILTIN, LOW); // выключить LED
Serial1.begin(9600); // инициализация UART со скоростью 9600 baud
}
void loop() {
while (Serial1.available() >= 0) {
char receivedData = Serial1.read(); // читаем один байт из буфера и сохраняем в receivedData
if (receivedData == '1') {
digitalWrite(LED_BUILTIN, HIGH); // включить LED
}
else if (receivedData == '0') {
digitalWrite(LED_BUILTIN, LOW); // выключить LED
}
}
}
Передатчик (Transmitter)
void setup() {
pinMode(LED_BUILTIN, OUTPUT); // настройка пина LED как выход
digitalWrite(LED_BUILTIN, LOW); // выключить LED
Serial.begin(9600); // инициализация последовательной связи на скорости 9600 baud:
Serial1.begin(9600); // инициализация UART со скоростью 9600 baud
}
void loop() {
if (Serial.read() == '1'){
Serial1.println('1');
digitalWrite(LED_BUILTIN, HIGH);
Serial.println("LEDS ON");
}
else if (Serial.read() == '0'){
Serial1.println('0');
digitalWrite(LED_BUILTIN, LOW);
Serial.print("LEDS OFF");
}
}
Разбор кода приёмника
Настраиваем встроенный светодиод как OUTPUT и задаём начальное значение LOW:
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
Внутри setup() инициализируем UART-соединение с baud rate 9600:
Serial1.begin(9600);
Если получен новый байт — Serial1.available() больше 0 — проверяем его содержимое:
while (Serial1.available() > 0) {
...
}
Считываем байт через read и проверяем, равен ли он '1'. Если да — включаем светодиод, записывая HIGH:
char receivedData = Serial1.read(); // читаем один байт из буфера и сохраняем в receivedData
if (receivedData == '1') {
digitalWrite(LED_BUILTIN, HIGH); // включить LED
}
Если байт равен '0' — записываем LOW, выключая светодиод:
else if (receivedData == '0') {
digitalWrite(LED_BUILTIN, LOW); // выключить LED
}
Разбор кода передатчика
Настраиваем встроенный светодиод как OUTPUT с начальным значением LOW:
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
Внутри setup() инициализируем оба порта — Serial и Serial1 — со скоростью 9600 baud. Передатчику нужно соединение с Serial Monitor для ввода команд:
Serial.begin(9600);
Serial1.begin(9600);
Внутри loop() проверяем, ввёл ли пользователь '1' в Serial Monitor (Serial). Если да — отправляем '1' через Serial1, включая светодиод на приёмнике:
if (Serial.read() == '1'){
Serial1.println('1');
digitalWrite(LED_BUILTIN, HIGH);
Serial.println("LEDS ON");
}
Если введено '0' (Serial) — отправляем '0' через Serial1, выключая светодиод:
if (Serial.read() == '1'){
Serial1.println('1');
digitalWrite(LED_BUILTIN, HIGH);
Serial.println("LEDS ON");
}
Типичные ошибки при работе с UART
- Несовпадение baud rate — самая частая причина «мусора» в Serial Monitor. Убедитесь, что скорость в коде и в настройках монитора одинакова.
- Нет общего GND — устройства не смогут правильно интерпретировать уровни сигналов. Всегда соединяйте GND обеих плат.
- TX к TX, RX к RX — неправильная распиновка. Правило: TX одной платы идёт в RX другой, и наоборот.
- Использование D0/D1 при загрузке скетча — пины D0 и D1 разделяются с USB-UART преобразователем. Если к ним подключено внешнее устройство, загрузка скетча может не работать. Отключайте внешние устройства перед прошивкой.
- Переполнение буфера — если приёмник не успевает читать данные, буфер переполняется и данные теряются. Используйте flow control или снижайте скорость передачи.