В уроке рассказывается о последовательном интерфейсе UART платы Ардуино, библиотеке для работы с ним и использовании порта UART для отладки программ.
Предыдущий урок Список уроков Следующий урок
Среда Arduino IDE не содержит отладчика, что создает определенные проблемы в поиске ошибок кода программы. Без ошибок программы сразу не пишутся. Формальные ошибки выявляются при компиляции, а с алгоритмическими и вычислительными ошибками намного сложнее.
В интернете описывается много способов отладки программ, которые используют дополнительные библиотеки, программы, аппаратные адаптеры. Но основная функция отладки это увидеть состояние программы, узнать значение переменных. Это можно сделать, передав нужную информацию на компьютер через последовательный интерфейс. Физическое подключение платы Ардуино к компьютеру через USB кабель существует всегда. Среда Arduino IDE имеет монитор последовательного порта, позволяющий получать и посылать данные обмена с платой. Можно передать на компьютер любую информацию о состоянии программы и вывести ее на дисплей. Меня такой способ отладки вполне устраивает. Только вместо монитора Arduino IDE я иногда использую свои программы, которые выводят данные в удобном мне виде.
Конечно, интерфейс UART в Ардуино может быть использован для связи с другими контроллерами или периферийными устройствами, но пока он нам интересен с точки зрения связи с компьютером.
Последовательный интерфейс UART.
UART в переводе это универсальный асинхронный приемопередатчик. Данные UART передаются последовательным кодом в следующем формате.
Каждый бит передается за равные промежутки времени. Время передачи одного бита определяется скоростью передачи. Скорость передачи указывается в бодах (бит в секунду). Кроме битов данных интерфейс UART вставляет в поток биты синхронизации: стартовый и стоповый. Таким образом, для передачи байта информации требуется 10 битов, а не 8. Погрешность временных интервалов передачи битов должна быть не более 5% (рекомендуется не более 1,5%).
Я описываю наиболее распространенный формат. Существуют варианты с разным количеством битов данных, битов синхронизации, может быть добавлен бит контроля четности и т.п. Но эти форматы используются редко. Главное запомнить:
- в неактивном режиме выход UART находится в высоком состоянии;
- передача байта начинается со стартового бита (низкого уровня);
- передача байта заканчивается стоповым битом (высокого уровня);
- данные передаются младшим битом вперед;
- для передачи байта требуется 10 битов;
- время передачи одного байта рассчитывается исходя из скорости передачи и количества битов (10).
Часто используются следующие стандартные скорости передачи интерфейса UART.
Скорость передачи, бод |
Время передачи одного бита, мкс | Время передачи байта, мкс |
4800 | 208 | 2083 |
9600 | 104 | 1042 |
19200 | 52 | 521 |
38400 | 26 | 260 |
57600 | 17 | 174 |
115200 | 8,7 | 87 |
Обмен информацией через UART происходит в дуплексном режиме, т.е. передача данных может происходить одновременно с приемом. Для этого в интерфейсе UART есть два сигнала:
- TX – выход для передачи данных;
- RX – вход для приема данных.
При соединении двух UART устройств выход TX одного устройства соединяется со входом RX другого. А сигнал TX второго UART подключается к входу RX первого.
Последовательный интерфейс UART в Ардуино.
Любая плата Ардуино имеет, как минимум, один аппаратный последовательный интерфейс UART. Платы Arduino Mega и Arduino Due имеют по три порта.
Когда я употребляю выражение аппаратный интерфейс, я подчеркиваю, что в контроллере существует электронный узел, в регистр которого программа только загружает байт для передачи, а формирование сигналов обмена и все остальные операции делает этот узел. Может быть реализована и программная передача данных по протоколу UART. В этом случае все сигналы формируются программой. Конечно, это занимает ресурсы процессора.
Плата Arduino UNO имеет один порт UART, сигналы которого подключены к выводам 0 (сигнал RX) и 1 (сигнал TX). Сигналы имеют логические уровни TTL (0…5 В). Через эти выводы (0 и 1) можно подключить к плате другое устройство имеющее интерфейс UART.
Кроме функции связи с другими контроллерами порт UART платы Arduino UNO используется для загрузки в контроллер программы из компьютера. Для этого к этим же сигналам (RX и TX) подключены соответствующие выводы микросхемы ATmega16U2 - преобразователя интерфейса USB/UART. Микросхема преобразователя подключена через резисторы сопротивлением 1 кОм.
Фрагмент схемы платы Arduino UNO R3.
Таким образом, при свободных выводах 0 и 1 платы Ардуино сигналы с микросхемы ATmega16U2 поступают на контроллер ATmega328. А если к плате подключить внешнее UART устройство, то его сигналы будут иметь приоритет, т.к. ATmega16U2 подключена через резисторы.
Преобразователь интерфейса ATmega16U2 позволяет подключать плату Ардуино к компьютеру через USB порт. На компьютер устанавливается драйвер. Он создает на компьютере виртуальный COM порт. Через него и происходит обмен. Такая технология описана на примере другого преобразователя интерфейсов PL2303 USB-UART по этой ссылке. Там же есть информация о вариантах UART интерфейсах с разными уровнями сигналов.
Важно понимать, несмотря на то, что плата подключена к компьютеру через USB порт, все программы обмениваются данными через виртуальный COM порт, не подозревая, что порт виртуальный.
Библиотека Serial для работы с UART Ардуино.
Для работы с аппаратными UART контроллерами в Ардуино существует встроенный класс Serial. Он предназначен для управления обменом данными через UART. Перед тем, как рассказать о функциях класса Serial я хочу пояснить разницу в формате данных обмена.
Через последовательный интерфейс данные всегда передаются в двоичном коде. Вопрос как эти данные интерпретировать, как воспринимать. Например, передан двоичный код “01000001” (десятичный 65). Как его отобразить на экране? Может быть передано число 65 и на экране надо вывести ”65”. А может это код буквы ”A”, тогда на экране надо написать ”A”. Просто необходимо знать в каком формате передаются данные.
В классе Serial данные могут передаваться в двух форматах:
- как бинарный код;
- как ASCII символы.
Например, монитор последовательного порта в программе Arduino IDE принимает данные как ASCII текст. Для того, чтобы он вывел на экран компьютера число “65” надо передать коды символов “6” и “5”. А код ”65” монитор отобразит как символ “A”.
Основные функции класса Serial.
void begin(long speed)
Разрешает работу порта UART и задает скорость обмена в бод (бит в сек). Для задания скорости передачи данных рекомендуется использовать стандартные значения (таблица в разделе “Последовательный интерфейс UART”).
Serial.begin(38400); // инициализация порта, скорость 38400 бод
void end(void)
Отключает порт UART, освобождает выводы RX и TX.
Serial.end(); // закрыть порт UART
int available(void)
Возвращает количество байт, принятых последовательным портом и записанных в буфер. Буфер последовательного порта может хранить до 64 байт. В случае пустого буфера возвращает 0.
int n;
n= Serial. available(); // в n число принятых байтов
int read(void)
Возвращает очередной байт из буфера последовательного порта. Если буфер пуст – возвращает число – 1 (0xffff).
receiveByte= Serial.read(); // чтение байта из буфера
void flush(void)
Ожидает окончания передачи данных из буфера последовательного порта.
Serial.flush(); // ждем окончания передачи
print()
Выводит данные через последовательный порт UART в виде ASCII символов. Функция имеет различные формы вызова для разных форматов и типов данных.
print(char d) | Если аргумент типа char выводит в порт код символа
char d= 83; |
print(byte d)
|
Данные типа byte выводятся кодом числа
byte d= 83; |
print(int d) | Если аргумент – целый тип, то выводит строку с десятичным представлением числа
int d= 83; |
print(float) | Вещественные типы выводятся символами ASCII, два знака после запятой
float d= 7.65432; |
print(* str) | Если аргумент указатель на массив или строка, то массив или строка побайтно передается в порт.
char letters[3]= {65, 66, 67}; |
print(int d, DEC) | Выводит строку ASCII - десятичное представление числа
int d= 83; |
print(int d, HEX) | Выводит строку ASCII – шестнадцатиричное представление числа
int d= 83; |
print(int d, OCT) | Выводит строку ASCII – восьмеричное представление числа
int d= 83; |
print(int d, BIN) | Выводит строку ASCII – двоичное представление числа
int d= 83; |
print(int d, BYTE) | Выводит код младшего байта числа
int d= 0x0283; |
print(float d, N) | Для вещественных чисел параметр N задает количество цифр после запятой.
Serial.print(7.65432, 0); // выводит строку “7” |
println()
Выводит данные через последовательный порт UART в виде ASCII символов с добавлением символов переноса строки (\r, код 13) и (\n, код 10). Т.е. следующее сообщение будет отображаться с новой строки. В остальном аналогична функции print().
int d= 83;
Serial.print(d, DEC); // вывод строки “83”
Serial.println(d, DEC); // вывод строки “83 \r \n”
int write()
Выводит двоичные данные через последовательный порт UART. Возвращает количество переданных байтов.
int write(val) | Передает байт
Serial.write(83); // передает байт 83 |
int write(str) | Передает строку, как последовательность байтов
int bytesNumber; // число байтов |
int write(* buf, len) | Передает байты из массива, число байтов – len.
char buf= “Строка”; |
int peek(void)
Возвращает следующий байт из буфера последовательного порта, не удаляя его из буфера. Если буфер пуст, то возвращает значение -1. Функция возвращает то же значение, что и функция read().
int readBytes(* buf, len)
Считывает байты, поступающие на последовательный порт, и записывает их в буфер. Прекращает работу после приема заданного количества байтов или в случае тайм-аута. Возвращает количество принятых байтов. Тайм-аут задается функцией setTimeout().
setTimeout(long time)
Задает время тайм-аута для функции readBytes(). Время time указывается в мс, по умолчанию оно равно 1000 мс.
void serialEvent()
Вызывается при поступлении данных в последовательный порт. По сути – прерывание по приему данных последовательным портом.
serialEvent() {
// код для обработки данных порта
}
Применение класса Serial.
Класс Serial встроенный. Для него не надо искать библиотеку и подключать ее. Чтобы использовать UART достаточно в setup() разрешить работу порта и задать скорость:
void setup() {
Serial.begin(9600); // инициализируем порт, скорость 9600
}
Теперь можно передавать данные с помощью функций print() или write().
Serial.println("Message to monitor"); // сообщение в монитор последовательного порта
Если через порт выводится несколько байтов, то класс Serial записывает их в программный буфер и последовательно по байту передает данные в контроллер UART по мере передачи.
Вот программа, которая каждую секунду выводит в монитор последовательного порта сообщение и номер прохода.
unsigned int i=0;
void setup() {
Serial.begin(9600); // инициализируем порт, скорость 9600
}
void loop() {
Serial.print("Cycle: ");
Serial.println(i);
i++;
delay(1000);
}
При поступлении данных на вход UART класс Serial записывает их в программный буфер. Для чтения данных из буфера необходимо сначала проверить, есть ли они там.
if( Serial.available() > 0 ) {
// есть данные
Вот программа, которая считывает байт из последовательного порта и отсылает его в порт. Т.е. все, что мы вводим в мониторе последовательного порта должно появляться в окне монитора для принятых данных.
unsigned int i=0;
void setup() {
Serial.begin(9600); // инициализируем порт, скорость 9600
}
void loop() {
if( Serial.available() > 0 ) {
// есть данные
Serial.write(Serial.read());
}
}
Отладка программ с помощью последовательного порта на Ардуино.
Принцип отладки простой.
- С помощью последовательного порта и функций класса Serial мы можем передать на компьютер информацию о состоянии программы.
- С помощью монитора последовательного порта Arduino IDE или другой программы мы можем эти данные увидеть на экране компьютера.
- Этими же программными средствами можно передать данные в программу Ардуино и повлиять на ее работу.
Например, мы проверяем работу кнопок и функций обработки сигналов кнопок. В блок программы, который выполняется по нажатию на кнопки, мы вставляем команду передачи сообщения на компьютер.
Для кнопок, подключенных по схеме предыдущего урока, скетч программы выглядит так.
// отладка кнопок с помощью порта UART
#include <MsTimer2.h>
#include <Button.h>
#define BUTTON_1_PIN 12 // кнопка 1 подключена к выводу 12
#define BUTTON_2_PIN 11 // кнопка 2 подключена к выводу 11
Button button1(BUTTON_1_PIN, 20); // создание объекта кнопка 1
Button button2(BUTTON_2_PIN, 20); // создание объекта кнопка 2
void setup() {
Serial.begin(9600); // инициализируем порт, скорость 9600
MsTimer2::set(2, timerInterupt); // задаем прерывания по таймеру с периодом 2 мс
MsTimer2::start(); // разрешаем прерывание
}
void loop() {
// проверка кнопки 1
if ( button1.flagClick == true ) {
button1.flagClick= false;
Serial.println("Button 1 was pressed"); // сообщение для монитора
}
// проверка кнопки 2
if ( button2.flagClick == true ) {
button2.flagClick= false;
Serial.println("Button 2 was pressed"); // сообщение для монитора
}
}
// обработка прерывания
void timerInterupt() {
button1.scanState(); // вызов метода обработки сигнала кнопки 1
button2.scanState(); // вызов метода обработки сигнала кнопки 2
}
Теперь на каждое нажатие кнопки 1 через последовательный порт платы Ардуино на компьютер будет передаваться сообщение “ Button 1 was pressed”, а по нажатию кнопки 2 – сообщение “Button 2 was pressed”. Приходится писать по английски. Монитор не воспринимает русские символы. В дальнейших уроках исправим.
Чтобы увидеть эти сообщения надо запустить монитор порта: Инструменты -> Монитор порта. Теперь можно нажимать на кнопки и видеть реакцию в окне монитора.
Только необходимо, чтобы скорости обмена были одинаковы в программе и мониторе (у меня 9600 бод).
Также можно передавать значения переменных, результаты вычислений, режимы и т.п. В последующих уроках практически в каждой программе мы будем использовать последовательный порт для контроля состояния программы.
В следующем уроке научимся считывать значения сигналов на аналоговых входах платы Ардуино.
Предыдущий урок Список уроков Следующий урок
У Меги 4 последовательных порта, не три.
Поэксерементировал с serialEvent() на Меге, на которой повесил gps на 2 й порт. — функция serialEvent2() тупо не вызывается , хотя данные приходят. Да и в чужом коде видел явный вызов данной функции из loop. Сначала удивился, но после практического теста понял, что иначе не работает. А я было обрадовался, что действительно как прерывание работает. 🙁
Вместо …функция serialEvent2() тупо не вызывается… надо бы вставить код и глядишь всё заработает!
Как вам помочь, если вместо кода вы пишете слова)
По синхронизации с ПК, может кто-то подсказать?
Ардуина выдает в Serial интенсивный поток (в том числе периодически строку «Im UNO\n») и готова принимать…
При втыкании в USB (COM?-порт), надо определиться с номером COM?-порта, начать принимать и передавать данные…
Подскажите как понимать буфер приема 64 байта, в даташите прямо конечно не сказано но все говорит что буфер 2 байта?
Если 64 байта тогда можно без прерываний обходиться для приема данных с порта, как на самом деле?
Для ATmega328 буфер Serial 64 байта. Посмотрите тему на форуме сайта http://mypractic-forum.ru/viewtopic.php?t=11
«…Только необходимо, чтобы скорости обмена были одинаковы в программе и мониторе (у меня 9600 бод)…»
А если мы подключаемся не через USB, а через реальный COM (соответственно, через преобразователь уровней), то как выставлять параметры COM?
В мониторе последовательного порта Arduino IDE есть выбор номера COM и скорости обмена.
Кабель использую «крестовой» (2-3,3-2), на мониторе скорости перебрал все, COM только один в наличии «1»…
Не грузится (сброс нажимаю), не прослушивает…
Не понятно, то как выставлять параметры COM в Ардуино?
А как Вы в плату Ардуино программу загружаете?
У вас же задан номер порта В Arduino IDE Инструменты -> Порт ->.
У вас драйвер виртуального порта установлен?
Виртуального порта нет, USB не использую.
Подключение к COM ПК кабелем и через COM-UART.
Питание Ардуино: источник 5В.
Надо убедиться, что COM есть в системе: Настройки-> Диспетчер оборудования -> COM. Убедиться, что он нормально работает — нет желтого ! и посмотреть его номер.
Затем установить его в Arduino IDE.
Попробовать послать данные с монитора и посмотреть, мигает ли в этот момент светодиод RXD на плате Ардуино.
Можете еще попробовать другим монитором. Мне нравится CoolTerm.
Итак, по непосредственному подключению к COM ПК, обмен данными работает.
Надо соединять везде: Rx-Rx, Tx-Tx, COM кабель прямой (2-2, 3-3).
А как задать другие параметры СОМ порта,например
dcb.ByteSize
dcb.Parity
dcb.StopBits?
Доброго вечера… Подскажите, может кто сталкивался:
сможет ли Pro mini 3,3 в (8Мгц) нормально работать на скорости 115200. Дело в том, что плата GSM при включении по умолчанию работает на этой скорости.
Выбор pro mini обусловлен уровнями ттл 3,3 в на GSM модуле.
Доброго времени суток. Эдуард, что-то не пойму, каково назначение ключа \n в строке Serial.print() ?
Здравствуйте!
Это управляющий символ перевода строки. Следующая печать символа будет начинаться с новой строки. Аналог функции println.
Здравствуйте!
Расскажите пожалуйста как работает команда millis() в связке с Serial.println
По всем описаниям millis() возвращает количество миллисекунд с момента начала выполнения текущей программы на плате Arduino. Это количество сбрасывается на ноль, в следствие переполнения значения, приблизительно через 50 дней.
но в программе
unsigned long time;
void setup(){
Serial.begin(9600);
}
void loop(){
Serial.print(«Time: «);
time = millis();
//выводит количество миллисекунд с момента начала выполнения программы
Serial.println(time);
// ждет секунду, перед следующей итерацией цикла.
delay(1000);
}
при открытии монитора порта отсчет начинается с 0 и нормально увеличивается на 1000.
Но при закрытии без отключения питания ардуины и повторном открытии порта видим сброс и новый отсчет с нуля. Разве такой счетчик не должен работать непрерывно независимо от того открыт монитор или нет?
Здравствуйте!
У меня работает также. Очевидно при открытии порта формируется импульс на выходе DTR USB-UART конвертера. От него и сбрасывается контроллер. Об этом написано в уроке про плату Arduino PRO Mini. Кстати, с платой Arduino PRO Mini такого эффекта не происходит.
Здравствуйте! Хотелось бы яснее понять принцип UART.
Вот Serial.write (переменная типа int) отправит в порт один байт. Если число в переменной большое, то оно обрежется до байта.
Почему же тогда не компилируется код, если слать Массив типа int? А только char, byte? По идее должны бы также само слаться переменные массива, обрезанные до байта.
Спасибо.
Здравствуйте!
Любая функция с аргументом типа byte обрежет int. Существует преобразование типов данных по умолчанию.
При использовании массивов в качестве аргумента используется указатель на массив (его имя). Функция write() берет подряд байты по этому указателю. Поэтому тип данных указателя должен строго выдерживаться. Преобразуйте указатель массива int явно (byte *)massiv. Компилироваться должно, но работать будет неправильно.
Понятно, значит у Serial.write аргумент типа byte.
Можно еще тогда спросить?
Как грамотно отсылать в порт большие числа (для которых нужно больше одного байта)?
Мне не код а принцип. По одному байту, используя цикл, одновременно с помощью строки или др?
Здравствуйте!
По разному. Для передачи массива можно организовывать цикл, можно передать имя массива и длину. Чтобы передать тип float байтами надо использовать указатели (Урок 15). INT можно передать через указатели, можно сдвигом на 8.
Спасибо, и с Праздником!
Здравствуйте!
Подскажите, как очистить буфер у Mega328 после передачи данных, если они там остались? Ну к примеру принимающая сторона не смогла принять весь пакет, а только часть. При последующей передачи данных приходит этот недостающий пакет, а не новые. Очень давно, команда Serial.flush() после передачи данных очищала буфер, теперь этого нет. Как быть?
Здравствуйте!
Считать все данные из буфера.
while (true) { if (Serial.read() == 0xffff) break;}
Спасибо, попробую.
Serial.print(7.65432, 0); // выводит строку “1”
Возможно это опечатка. Должно быть 7
Serial.print(7.65432, 2); // выводит строку “7.65”
Serial.print(7.65432, 4); // выводит строку “7.6543”
Спасибо. Исправлю.
Здравствуйте Эдуард.
Я поторопился с комментарием. Там идёт округление и должно быть 8.
Доброго времени!
На просторах сети находил вот такой код для принятия данных в строку.
void loop() {
while (Serial.available() > 0) {
char inChar = Serial.read();
inString += inChar;
}
Serial.print(inString);
Но если в цикл не добавить delay, то serial.print срабатывает до завершения передачи всех символов с порта.
Подскажите, пожалуйста, правильно ли использовать в этом случае delay, или нужно по-другому организовывать код?
Можно простейший пример с использованием Serial.flush(); (ожидания окончании передачи)? Не могу понять, как ее использовать.
С первым вопросом разобрался. Нужно посчитать колличество байтов.
if (Serial.available()>0){ delay(5);
serAva = Serial.available();
for (i=0; i<serAva; i++)
inputBytes[i] = Serial.read();
Здравствуйте!
Насколько я понимаю, flush подвешивает программу. Я не использую эту функцию. Для определения окончания передачи я использую прямое чтение аппаратного флага контроллера UART. Пример можете посмотреть в библиотеке Tiny_ModBusRTU_Slave (урок 57).
Благодарю!
Добрый день.У меня некорректно отображаются русские буквы в мониторе порта — квадратики. В следующих уроках не нашел, как с этим бороться. Подскажите?
Здравствуйте!
Никак. Пишите латинским символами. Используются разные кодировки символов в мониторе и функции print. Можете попробовать передавать кодами символов, но тогда теряется весь смысл функции print — легко выводить сообщения.
Эдуард, подскажите пожалуйста — код написанный для Ардуино UNO (имеется ввиду код, касающийся приема-передачи данных по UART) — должен без всяких изменений работать на Ардуино Mega? Mega подключена через USB к ПК. Программа в нее благополучно загружается. Далее программа ждет команды из UART и должна отвечать на них. Команды посылаю через терминал (или через монитор порта — без разницы). Такое ощущение что Mega в упор не слышит из порта вообще ничего. Этот же самый код на UNO благополучно работает
Здравствуйте!
Если используется порт Serial (не Serial1 и т.д.) и управление происходит только через функции класса, то на Maga все должно работать. Если где-то происходит прямое обращение к регистрам, то могут быть проблемы.
Проверьте с помощью самого простого кода — инициализация, посылка в цикле данного.
Может быть это MSTimer2 не работает на Меге…?
Прошу прощения за беспокойство. Действительно MSTimer2 не работает на Mega, следовательно не работает ничего, в частности приращение счетчика тайм-аута ожидания данных из UART. Нужно использовать, как правильно указали в комментариях Ваши читатели из урока 10, библиотеку http://playground.arduino.cc/Main/FlexiTimer2
«int available(void)
Возвращает количество байт, принятых последовательным портом и записанных в буфер. Буфер последовательного порта может хранить до 64 байт. В случае пустого буфера возвращает 0.»
Истинное количество принятых байт эта функция НЕ ВОЗВРАЩАЕТ! Правильно возвращает только true и false (буфер не пустой или пустой). Намучился из-за этого. Делал отладку. Посылал Ардуине гарантированные ТРИ (3) байта. И ничего более. Функция возвращала 3, 5, 7, 9, 11 (и даже более) байт в буфере. Причём первые 3 были ПРАВИЛЬНЫМИ, а далее шёл какой-то повторяющийся мусор. Аппаратная проблема исключена. Это именно глюк функции. Перед очередным приёмом очищал буфер Rx вычитыванием его в null: while (Serial.available() {Serial.read();}
«void flush(void)
Ожидает окончания передачи данных из буфера последовательного порта.
Serial.flush(); // ждем окончания передачи»
Тоже не работает, как описано!
Программа (Ардуино) отсылала на скорости 115200 другому устройству пакеты по 256 байт (большой массив, поделённый на такой размер). После отсылки пакета ставилось Serial.flush(); На втором пакете сбивалась синхронизация на той стороне. Заработало только после того, как Serial.flush(); заменил на банальную паузу delay(200); между пакетами.
Добрый день. Подскажите пожалуйста, проблема с прошивкой. Имеется 2 аппаратных uart ( плата на atmega 4808 nano every). На rx1 tx1 это USB, а на tx0, rx0 модуль gps. Без физического отключения модуля gps прошивка не проходит — ошибка синхронизации. Есть какой то способ прошивать Ардуино с несколькими аппаратными uart без отключения остальных модулей?