Serial Monitor. Общаемся с компьютером
Для общения между платой Arduino и компьютером или другим устройством в контроллере используется интерфейс UART или USART, который в сочетании со встроенным в UNO USB-to-UART конвертером, позволит установить двунаправленую связь с компьютером через виртуальный последовательный порт. У некоторых моделей Arduino может быть несколько портов. Порт соединяется через цифровой пин 0 (RX) и 1 (TX) при подключении к компьютеру через USB, поэтому не используйте пины 0 и 1 для ввода/вывода.
Раньше на старых компьютерах были COM-порты, сейчас они создаются виртуально при помощи микросхемы FTDI, когда мы подключаем плату к компьютеру через USB.
Вам часто придётся использовать общение между устройствами для обмена информацией. Можно как посылать сигнал с компьютера, например, с клавиатуры, так и принимать сигналы с платы. Это полезно, чтобы узнать, что вообще происходит с сигналом из нужного вывода платы.
В Arduino IDE есть специальный значок с изображением лупы, который запускает Serial Monitor (монитор порта).
Для корректной работы с портом требуется выполнение двух условий: выбрать правильный COM-порт, выбрать скорость работы в скетче, которая должна совпадать со скоростью, выбранной в мониторе порта.
Для общения используется класс Serial. В методе setup() мы открываем порт для общения функцией Serial.begin() с указанием скорости в бодах (baud). Бод — это количество изменений сигнала в секунду. В нашем случае сигналы могут быть только двоичными, так что скорость в бодах соответствует скорости в битах в секунду. Можно использовать любую скорость, главное чтобы на приёмной и передающей сторонах они были одинаковыми. Доступные скорости можно посмотреть в настройках порта. Значение 9600 является стандартным и его можно не менять (9600 бод — 960 символов — один стартовый бит, восемь бит на сам символ и конечный бит). Если установить неправильную скорость, то вместо данных получим «мусор» — данные, которые нельзя обработать. Для обмена данными между другими компонентами скорость может быть и выше, например между платой и Bluetooth-модулем.
На платах Arduino Mega и Arduino Due доступны также Serial1, Serial2, Serial3.
Чтобы отправить сообщение в порт, используются методы print() (символы идут подряд) или println() (с переводом на новую строку).

Давайте выведем какое-нибудь сообщение. Это можно сделать в методе setup(), так как нам не нужно повторять одну и ту же фразу бесконечно. Метод loop() оставляем пустым.
void setup() < Serial.begin(9600); Serial.println("Hello Kitty!"); Serial.print("Мяу!"); >void loop()
Если посылаем строку, то обрамляем её кавычками. Если число, то кавычки не используем. Изменим функцию setup().
void setup() < Serial.begin(9600); Serial.print("А у кошки "); Serial.print(4); Serial.println(" ноги,"); Serial.print("А сзади у ней длинный хвост."); >void loop()
Можно заменить строки и числа на переменные. Перепишем пример.
String cat = "А у кошки "; int leg = 4; void setup() < Serial.begin(9600); Serial.print(cat); Serial.print(leg); Serial.println(" ноги,"); Serial.print("А сзади у ней длинный хвост."); >void loop()
Немного о числах. При работе с дробными числами, можно указать число знаков после запятой.
void setup() < float number = 9.434346502; Serial.begin(9600); Serial.print(number, 2); >void loop()
Работа с массивами и строками
Разберём пример отправки строк в случайном порядке. Любая строка уже является массивом символов. Поэтому вместо типа String, можно использовать массив char[]. Для примера создадим массив из четырёх имён и будем выводить их в случайном порядке через разные промежутки времени, используя функцию random().
char* catNames[] = < "Барсик", "Васька", "Мурзик", "Рыжик" >; void setup() < Serial.begin(9600); >void loop()

Приём данных
Выводить данные в порт просто. А вот принимать данные с компьютера и других источников сложнее. При отправлении данных, они складываются в буфер, ожидая, когда плата их прочитает. Объём буфера составляет 64 байта. Чтобы постоянно не читать пустой буфер, есть специальная функция проверки буфера Serial.available(). Она возвращает число байт, которые лежат в буфере. Обычно в коде создают условие проверки — если в буфере больше 0 байт, то выполняем какие-то команды.
Для демонстрации создадим странный пример — создадим переменную, присвоим ей данные через Serial.read() и попросим её прислать полученные данные через Serial.print(). Получится круговорот данных или эхо.
void setup() < Serial.begin(9600); >void loop() < if (Serial.available() >0) < int data = Serial.read(); Serial.println(data); >>
Проверяем на числах. Отправляем число 9, а получаем 57. Если вы получаете две строки с числами 57 и 10, то в нижней части окна выберите настройку No line ending вместо Newline.
Попробуем также отправить букву. Опять вместо t возвращается 116. Ерунда какая-то. Всё просто, функция read() работает с символьными значениями и мы видим код символа из стандартной таблицы символов ASCII.
Чтобы решить проблему, нужно изменить тип данных на char.
char data = Serial.read();
Вроде проблема решена. Мы можем принимать отдельные цифры и буквы. Но буквы только английские, а числа только однозначные.
Если мы планируем работать только с однозначными числами, то можно написать такой код.
int data = Serial.read() - '0';
Решение какое-то половинчатое. А как быть с большими числами или словами?
Если отправить двузначное число 23, то ответ разбивается на части — 2 и 3. Получается, что переменная получит последнее число 3 (промежуточные значения перезаписываются). Чтобы обработать всё число, нужно использовать метод parseInt().
int data = Serial.parseInt();
Теперь вы можете вводить любые числа. Но, наверное, вы заметите теперь небольшую задержку в ответах. Метод внутри себя перемалывает данные. Кстати, вы можете использовать и обычные символы. Если набор символов состоит только из букв, то вернётся 0. Если будут попадаться и цифры, то будут возвращаться цифры. Попробуйте комбинировать различные сочетания цифр и букв, чтобы понять, как будут обрабатываться данные.
Управление светодиодом с клавиатуры
Напишем пример управления встроенным светодиодом с клавиатуры. Если нажата клавиша 1, то светодиод должен загореться, при нажатии клавиши 0 выключим светодиод.
int ledPin = 13; byte incomingByte; void setup() < pinMode(ledPin, OUTPUT); Serial.begin(9600); >void loop() < if (Serial.available() >0) < incomingByte = Serial.read(); if(incomingByte == '1')< digitalWrite(ledPin, HIGH); >else if (incomingByte == '0') < digitalWrite(ledPin, LOW); >Serial.print("I received: "); Serial.println(incomingByte, DEC); > delay(10); >
Часть кода нам уже знакома — мы используем встроенный светодиод под номером 13.
Сигнал от компьютера поступает в виде байта. Создаём новую переменную incomingByte для этих целей.
Последовательный порт включается командой begin() с указанием скорости.
Если с компьютера поступает сигнал, то функция available() вернёт количество байт, доступное для чтения. Таким образом, мы просто убеждаемся, что какой-то сигнал пришёл (больше нуля).
После первой проверки мы проверяем введённый символ, который может быть представлен и как байт. Если символ равен единице, то включаем светодиод, как мы делали раньше. Если символ равен 0, то выключаем.
Как это выглядит на практике. Заливаем скетч и запускаем Serial Monitor (Ctrl+Shift+M). В окне Serial Monitor наверху есть текстовое поле. Вводим в него числа 1 или 0 и нажимаем кнопку Send. Можно также нажать клавишу Enter для быстрого ввода.
Для общего развития в скетч добавлены также две строчки кода, определяющие код нажатой клавиши. Таким образом вы можете узнать код для клавиш 0 и 1. Вы также можете нажимать и на другие клавиши, они не повлияют на светодиод, но вы увидите коды клавиш.
Чуть более сложный пример, когда строка задана в виде массива и символы выводятся по очереди.
#define ARRAY_SIZE 12 //global variable definition char hello[ARRAY_SIZE] = < 'h','e','l','l','o',' ','k','i','t','t','y','!'>; void setup() < Serial.begin(9600); >void loop() < //print characters from array to serial monitor for(int x = 0; x < ARRAY_SIZE; x++) < Serial.print(hello[x]); delay(250); >Serial.println(); delay(250); >
Функция Serial.end() закрывает последовательное соединение, порты RX и TX освобождаются и могут быть использованы для ввода/вывода.
В различных уроках вы будете принимать сигналы от платы Arduino. Это полезно, например, для отладки приложения, когда вы выводите сообщения и по ним ориентируетесь, какая часть программа работает, а какая — нет. Способность общения между Arduino и компьютером очень важна. Вы можете принимать сигналы не только в Arduino IDE, но и в других приложениях на компьютере. Например, в связке с Arduino часто используют приложение Processing, в котором рисуют графики поступаемых сигналов.
Если вы больше не нуждаетесь в получении данных, то закрывайте окно Serial Monitor.
Также существует библиотека SoftwareSerial. Она позволяет осуществить последовательную передачу данных через другие цифровые контакты Arduino.
Другие варианты
Чтение данных из последовательного порта возможно другими способами. Ищите расширения, например, Arduino Chrome Serial Monitor. На видео можно посмотреть, как создать расширение самостоятельно.
На C# также можно написать приложение, которое будет уметь считывать данные.
Processing также умеет работать с последовательным портом.
Дополнительное чтение
ASCIITable — распечатываем таблицу символов ASCII в разных форматах
Передача данных в Excel из arduino

Сегодняшняя статья будет небольшим продолжением старой наработки – термометра, который измеряет температуру в помещении и на улице. На примере температуры я хочу показать, как можно выводить данные не только на индикаторах и дисплеях, но и на мониторе компьютера. Чтобы вывод данных был интересней, можно построить график или диаграмму.
Как передавать данные из arduino на компьютер
В среде разработки arduino ide уже есть готовый интерфейс, для приема данных с микроконтроллера – монитор порта. Чтобы передать данные с МК достаточно просто вывести информацию с помощью набора функций Serial. Они служат для передачи данных через порты ввода/вывода RX и TX или USB. Подобно монитору порта из среды arduino ide, получать данные можно и с помощью своих десктопных программ или скриптов, которые будут читать данные с com-портов.
Но в этой статье мы не будем писать обработку портов, а воспользуемся уже готовым макросом для excel – PLX DAQ, который умеет считывать данные с порта и выводить их в ячейки таблицы. Скачать дополнение можно тут: скачать. А почитать подробнее о макросе можно тут: ссылка.
Настройка PLX DAQ для excel
После установки дополнения PLX DAQ, на рабочем столе появится ярлык «PLX-DAQ Spreadsheet». Нужно его запустить, тогда откроется лист excel с уже запущенным макросом. Для связи с arduino достаточно указать, какой нужно слушать com-порт, и на какой скорости происходит передача данных. Скорость передачи должна соответствовать той, что указана в arduino: Serial.begin(9600);
После настройки порта и скорости передачи данных нужно нажать кнопку «connect» и сразу же начнется получение и вывод данных из arduino. Пример работы можно посмотреть на скриншоте ниже.

Что использовалось в проекте:
- Arduino (я использовал arduino uno, но можно любую другую). Покупал тут: arduino uno
Тестовый скетч для передачи данных в Excel из arduino
Ниже приведен скетч с подробными комментариями, а также его можно скачать себе на компьютер: скачать.
int row_excel = 0; // количество строк int test = 123; // тестовая переменная, которая будет передана excel int test_2 = 456; // вторая тестовая переменная, которая будет передана excel void setup() < Serial.begin(9600); // скорость передачи данных Serial.println("CLEARDATA"); // очистка листа excel Serial.println("LABEL,Time,Test 1, Test 2, Num Rows"); // заголовки столбцов >void loop() < row_excel++; // номер строки + 1 Serial.print("DATA,TIME,"); // запись в excel текущей даты и времени Serial.print(test); Serial.print(","); Serial.print(test_2); Serial.print(","); Serial.println(row_excel); // если строк больше 50, то начинаем заполнять строки по новой if (row_excel >50) < row_excel = 0; Serial.println("ROW,SET,2"); >delay(1000); // задержка >
Послесловие
И в заключение приведу короткий ролик, который показывает, как в экселе выводятся данные, полученные от термометра. И на их основе строится график с двумя переменными – для комнатной и уличной температуры:
Монитор порта, отладка


Как мы с вами знаем из урока “Что умеет микроконтроллер“, у многих микроконтроллеров есть интерфейс UART, позволяющий передавать и принимать различные данные. У интерфейса есть два вывода на плате – пины TX и RX. На большинстве Arduino-плат к этим пинам подключен USB-UART преобразователь (расположен на плате), при помощи которого плата может определяться компьютером при подключении USB кабеля и обмениваться с ним информацией. На компьютере создаётся виртуальный COM порт (последовательный порт), к которому можно подключиться при помощи программ-терминалов и принимать-отправлять текстовые данные. Таким же образом кстати работают некоторые принтеры и большинство станков с ЧПУ.

В самой Arduino IDE есть встроенная “консоль” – монитор порта, кнопка с иконкой лупы в правом верхнем углу программы. Нажав на эту кнопку мы откроем сам монитор порта, в котором будут настройки:
Если с отправкой, автопрокруткой, отметками времени и кнопкой очистить вывод всё и так понятно, то конец строки и скорость мы рассмотрим подробнее:
- Конец строки: тут есть несколько вариантов на выбор, чуть позже вы поймёте, на что они влияют. Лучше поставить нет конца строки, так как это позволит избежать непонятных ошибок на первых этапах знакомства с платформой.
- Нет конца строки – никаких дополнительных символов в конце введённых символов после нажатия на кнопку отправка или клавишу Enter.
- NL – символ переноса строки в конце отправленных данных.
- CR – символ возврата каретки в конце отправленных данных.
- NL+CR – и то и то.
Объект Serial
Начнём знакомство с одним из самых полезных инструментов Arduino-разработчика – Serial, который идёт в комплекте со стандартными библиотеками. Serial позволяет как просто принимать и отправлять данные через последовательный порт, так и наследует из класса Stream кучу интересных возможностей и фишек, давайте сразу их все рассмотрим, а потом перейдём к конкретным примерам.
Serial.begin(speed)
Запустить связь по Serial на скорости speed (измеряется в baud, бит в секунду). Скорость можно поставить любую, но есть несколько “стандартных” значений. Список скоростей для монитора порта Arduino IDE:
- 300
- 1200
- 2400
- 4800
- 9600 чаще всего используется, можно назвать стандартной
- 19200
- 38400
- 57600
- 115200 тоже часто встречается
- 230400
- 250000
- 500000
- 1000000
- 2000000 – максимальная скорость, не работает на некоторых китайских платах
Прекратить связь по Serial. Также освобождает пины RX и TX.
Serial.available()
Возвращает количество байт, находящихся в буфере приёма и доступных для чтения.
Serial.availableForWrite()Возвращает количество байт, которые можно записать в буфер последовательного порта, не блокируя при этом функцию записи.
Serial.write(val), Serial.write(buf, len)
Отправляет в порт val численное значение или строку, или отправляет количество len байт из буфера buf. Важно! Отправляет данные как байт (см. таблицу ASCII), то есть отправив 88 вы получите букву X: Serial.write(88); .
Serial.print(val), Serial.print(val, format)
Отправляет в порт значение val – число или строку, фактически “печатает”. В отличие от write выводит именно текст, т.е. отправив 88, вы получите 88: Serial.print(88); . Отправляет любые стандартные типы данных: численные, символьные, строковые. Также методы print()/println() имеют несколько настроек для разных данных, что делает их очень удобным инструментом отладки:
Serial.print(78); // выведет 78 Serial.print(1.23456); // 1.23 (по умолч. 2 знака) Serial.print('N'); // выведет N Serial.print("Hello world."); // Hello world. // можно сделать форматированный вывод в стиле Serial.print("i have " + String(50) + " apples"); // выведет строку i have 50 apples // вместо чисел можно пихать переменные byte appls = 50; Serial.print("i have " + String(appls) + " apples"); // выведет то же самоеformat позволяет настраивать вывод данных: BIN, OCT, DEC, HEX выведут число в соответствующей системе счисления: двоичная, восьмеричная, десятичная (по умолчанию) и 16-ричная. Цифра после вывода float позволяет настраивать выводимое количество знаков после точки:
Serial.print(78, BIN); // вывод "1001110" Serial.print(78, OCT); // вывод "116" Serial.print(78, DEC); // вывод "78" Serial.print(78, HEX); // вывод "4E" Serial.print(1.23456, 0); // вывод "1" Serial.print(1.23456, 2); // вывод "1.23" Serial.print(1.23456, 4); // вывод "1.2345"
Serial.println(), Serial.println(val), Serial.println(val, format)
Полный аналог print() , но автоматически переводит строку после вывода. Позволяет также вызываться без аргументов (с пустыми скобками) просто для перевода курсора на новую строку.
Serial.flush()
Ожидает окончания передачи данных.
Serial.peek()Возвращает текущий байт с края буфера, не убирая его из буфера. При вызове Serial.read() будет считан тот же байт, но из буфера уже уберётся.
Serial.read()
Читает и возвращает крайний символ из буфера.
Serial.setTimeout(time)Устанавливает time (миллисекунды) таймаут ожидания приёма данных для следующих ниже функций. По умолчанию равен 1000 мс (1 секунда).
Serial.find(target), Serial.find(target, length)
Читает данные из буфера и ищет набор символов target (тип char ), опционально можно указать длину length. Возвращает true , если находит указанные символы. Ожидает передачу по таймауту.
// будем искать слово hello char target[] = "hello"; void setup() < Serial.begin(9600); >void loop() < if (Serial.available() >0) < if (Serial.find(target)) Serial.println("found"); // вывести found, если было послано >>Serial.findUntil(target, terminal)
Читает данные из буфера и ищет набор символов target (тип char ) либо терминальную строку terminal. Ожидает окончания передачи по таймауту, либо завершает приём после чтения terminal.
Serial.readBytes(buffer, length)
Читает данные из порта и закидывает их в буфер buffer (массив char[] или byte[] ). Также указывается количество байт, который нужно записать – length (чтобы не переполнить буфер).
Serial.readBytesUntil(character, buffer, length)
Читает данные из порта и закидывает их в буфер buffer (массив char[] или byte[] ), также указывается количество байт, который нужно записать – length (чтобы не переполнить буфер) и терминальный символ character. Окончание приёма в buffer происходит при достижении заданного количества length, при приёме терминального символа character (он в буфер не идёт) или по таймауту
Serial.readString()
Читает порт, формирует из данных строку String , и возвращает её (урок про стринги). Заканчивает работу по таймауту.
Serial.readStringUntil(terminator)
Читает порт, формирует из данных строку String , и возвращает её (урок про стринги). Заканчивает работу по таймауту или после приёма символа terminator (символ char ).
Serial.parseInt(), Serial.parseInt(skipChar)
Читает целочисленное значение из порта и возвращает его (тип long ). Заканчивает работу по таймауту. Прерывает чтение на всех знаках, кроме знака – (минус). Можно также отдельно указать символ skipChar, который нужно пропустить, например кавычку-разделитель тысяч (10’325’685), чтобы принять такое число.
Serial.parseFloat()
Читает значение с плавающей точкой из порта и возвращает его. Заканчивает работу по таймауту.Плоттер
Помимо монитора последовательного порта, в Arduino IDE есть плоттер – построитель графиков в реальном времени по данным из последовательного порта. Достаточно отправлять значение при помощи команды Serial.println(значение) и открыть плоттер по последовательному соединению, например построим график значения с аналогового пина A0:
void setup() < Serial.begin(9600); >void loop()

Плоттер поддерживает несколько линий графиков одновременно, для их отображения нужно соблюдать следующий протокол отправки данных: значения выводятся в одну строку, одно за другим по порядку, разделяются пробелом или запятой и в конце обязательно перенос строки.
значение1 пробел_или_запятая значение2 пробел_или_запятая значение3 пробел_или_запятая перенос_строки
Давайте выведем значения с аналоговых пинов A0, A1 и A2:
void setup() < Serial.begin(9600); >void loop()
Получим вот такие графики:


В Arduino IDE с версии 1.8.10 добавили возможность подписать графики, для этого перед выводом нужно отправить названия в виде название 1, название 2, название n с переносом строки, и дальше просто выводить данные:
Отправка в порт

Рассмотрим самый классический пример для всех языков программирования: Hello World!
Отправка в порт позволяет узнать значение переменной в нужном месте программы, этот процесс называется отладка. Когда код работает не так, как нужно, начинаем смотреть, где какие переменные какие значения принимают. Или выводим текст из разных мест программы, чтобы наблюдать за порядком её работы. Во взрослых средах разработки и более серьёзных микроконтроллерах есть аппаратная отладка, которая позволяет наблюдать за ходом выполнения программы и значениями любых переменных без вывода в порт.
Давайте вспомним урок циклы и массивы и выведем в порт элементы массива:
void setup() < Serial.begin(9600); byte arr[] = ; for (byte i = 0; i < 8; i++) < Serial.print(arr[i]); Serial.print(' '); >> void loop()
Вывод: 0 50 68 85 15 214 63 254 – элементы массива, разделённые пробелами.
Чтение из порта
Проблемы возникают при попытке принять данные в порт. Дело в том, что метод read() читает один символ, а если вы отправите длинное число или строку – программа получит его по одному символу. Чтение сложных данных называется парсинг. Его можно делать вручную, об этом мы поговорим в отдельном уроке из блока “Алгоритмы”. В рамках этого урока рассмотрим встроенные инструменты для парсинга Serial.
Чтобы не нагружать программу чтением пустого буфера, нужно использовать конструкцию
if (Serial.available()) < // тут читаем >
Таким образом чтение будет осуществляться только в том случае, если в буфере есть какие-то данные.
Парсинг цифр
Для чтения целых цифр используем Serial.parseInt() , для дробных – Serial.parseFloat() . Пример, который читает целое число и отправляет его обратно:
void setup() < Serial.begin(9600); >void loop() < if (Serial.available()) < int val = Serial.parseInt(); Serial.println(val); >>
Если при парсинге у вас появляются лишние цифры – поставьте “Нет конца строки” в настройках монитора порта
Вы заметите, что после отправки проходит секунда, прежде чем плата ответит в порт. Эта секунда является таймаутом, о котором мы говорили чуть выше. Программа ждёт секунду после принятия последнего символа, чтобы все данные успели прийти. Секунда это очень много, ожидать, скажем, 50 миллисекунд. Это можно сделать при помощи метода setTimeout() .
void setup() < Serial.begin(9600); Serial.setTimeout(50); >void loop() < if (Serial.available()) < int val = Serial.parseInt(); Serial.println(val); >>
Теперь после отправки цифры программа будет ждать всего 50 мс и ответит гораздо быстрее!
Парсинг текста
Проще всего прочитать текст в String-строку (урок про них). Это максимально не оптимально, но зато довольно просто для восприятия:
void setup() < Serial.begin(9600); Serial.setTimeout(50); >void loop() < if (Serial.available()) < String str = Serial.readString(); Serial.println(str); >>
Данный пример выводит любой текст, который был отправлен в монитор порта.
Управляющие символы
Существуют так называемые управляющие символы, позволяющие форматировать вывод. Их около десятка, но вот самые полезные из них
- \n – новая строка
- \r – возврат каретки
- \v – вертикальная табуляция
- \t – горизонтальная табуляция
Также если в тексте вы захотите использовать одинарные кавычки ‘ , двойные кавычки « , обратный слэш \ и некоторые другие символы – их нужно экранировать при помощи обратного слэша, он просто ставится перед символом:
- \» – двойные кавычки
- \’ – апостроф
- \\ – обратный слэш
- \0 – нулевой символ
- \? – знак вопроса
Выведем строку с кавычками:
Serial.println("\"Hello, World!\""); // выведет "Hello, World!"Комбинация \r\n переведёт строку и вернёт курсор в левое положение:
Serial.print("Hello, World!\r\nArduino Forever"); // выведет // Hello, World! // Arduino ForeverСимволы табуляции позволят удобно отправлять данные для последующей вставки в таблицу. Например выведем несколько степеней двойки в виде таблицы, используя символ табуляции \t :
for (byte i = 0; i < 16; i++) < // i от 0 до 16 Serial.print(i); // степень Serial.print("\t"); // табуляция Serial.println(round(pow(2, i))); // 2 в степени i >
Результат скопируем и вставим в excel Удобно!
Видео
Полезные страницы
- Набор GyverKIT – большой стартовый набор Arduino моей разработки, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
- Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
- Полная документация по языку Ардуино, все встроенные функции и макросы, все доступные типы данных
- Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
- Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
- Поддержать автора за работу над уроками
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])
Урок 48. Обмен данными между платой Ардуино и компьютером через интерфейс UART.

В уроке подключим локальный контроллер на базе Ардуино к компьютеру. Для связи будем использовать стандартный последовательный интерфейс UART.
Это самый простой вариант подключения платы Ардуино к компьютеру. Ничего не добавляем к плате. Подключаем к ней трех проводной кабель или используем стандартный USB кабель.
Коротко об интерфейсе UART.
Это самый распространенный последовательный интерфейс современных микроконтроллеров. Я достаточно подробно описывал его в уроке 12. Сейчас только подчеркну особенности соединения UART устройств между собой.
Для обмена данными в UART есть 2 сигнала:
- RX – вход, через который происходит прием данных:
- TX – выход, через который данные передаются.
Оба сигнала дискретные, имеют логические уровни CMOS, т.е.:
- уровень логического 0 около 0 В;
- уровень логической 1 около 5 В.
Активный уровень сигналов – низкий. В режиме ожидания сигналы находятся в высоком уровне.
Обмен данными осуществляется в асинхронном, дуплексном режиме. Это значит, что в одном микроконтроллере передача данных может происходить одновременно с приемом. Аппаратные части передатчика и приемника UART полностью независимые.
Для работы с UART интерфейсом в системе Ардуино есть встроенный класс Serial. Использование функций Serial значительно облегчает разработку приложений для последовательного порта. Класс считывает данные с контроллера UART и записывает их в программный буфер. Это происходит по прерыванию от контроллера, незаметно для основной программы. Передача данных также происходит под управлением класса Serial. Для этого достаточно загрузить данные в программный буфер функцией Serial.write(). Класс Serial подробно описан в уроке 12.
Интерфейс UART радиальный, т.е. по линиям связи одного интерфейса подключаются только 2 устройства. Выход TX одного устройства подключается к входу RX второго. Сигнал TX второго UART устройства соединяется с входом RX первого.

У нас UART устройством может быть плата Ардуино или преобразователь интерфейсов USB-UART, подключенный к компьютеру.
Схема локального контроллера.
Разработаем схему локального контроллера, которую будем использовать и в последующих уроках. Только придется изменять схему интерфейса связи. В этом варианте используется стандартный интерфейс UART.
Я решил подключить к контроллеру:
- датчик температуры DS18B20;
- переменный резистор, с помощью которого на аналоговом входе можно задавать напряжение;
- кнопку – датчик дискретного сигнала;
- светодиод – дискретное исполнительное устройство.
Я постарался подключить достаточно разнообразные устройства: сложный датчик с цифровым интерфейсом, аналоговый датчик, дискретный датчик.
Для локального контроллера я использовал плату Arduino Nano. Вот моя схема.

Собранный локальный контроллер выглядит так.


Вы можете использовать другие типы плат, другие датчики.
Линии связи с конвертером USB-UART подключаются непосредственно к сигналам TX и RX платы Ардуино. Могут быть два физических варианта реализации связи с компьютером:
- Использовать конвертер интерфейсов USB-UART, например модуль CH340. В этом случае модуль подключается к USB порту компьютера, а провода связи (3 провода) тянутся к локальному контроллеру.
- Использовать USB-UART преобразователь, встроенный в плату Ардуино. Соединение сигналов конвертера интерфейсов и микроконтроллера происходит на плате. Логически этот вариант не отличается от предыдущего, а выглядит это как подключение платы Ардуино к компьютеру через USB порт.
Протокол обмена данными через UART.
Минимальная единица информации, которой можно обмениваться по UART это байт. Мы передаем байты, а нам надо передавать числа, команды, текст. То как интерпретировать последовательность байтов при обмене данными определяет протокол.
Протокол обмена данными – это набор соглашений, правил, которые определяют обмен данными между программами или устройствами. В нашем случае между локальным контроллером и центральным контроллером или компьютером.
Протоколы бывают числовыми и текстовыми.
Числа, данные в текстовом протоколе передаются как коды символов. Например, число “132” в текстовом протоколе передается как 3 байта 0x31, 0x33 и 0x32. Это коды символов ”1”, ”3” и ”2”. В числовом протоколе это же число передается одним байтом со значением 132.
Очевидно главное преимущество числовых протоколов перед текстовыми. Для передачи одинакового количества информации им требуется значительно меньше передаваемых данных.
Достоинство текстовых протоколов – возможность использования для контроля и отладки стандартных средств и программ – текстовых терминалов. В качестве примера можно привести монитор последовательного порта Arduino IDE. Данные в нем выводятся в человеческом, понятном виде.
Резюмируя о текстовых и числовых протоколах применительно к интерфейсам передачи данных:
- Числовые протоколы требуют передачи по линии связи намного меньше данных, чем текстовые протоколы для того же количества информации.
- При числовых протоколах обработка данных обмена требует значительно меньше ресурсов микроконтроллера. Это связано с тем, что нет необходимости преобразовывать текстовые строки в двоичные числа и наоборот. А это крайне ресурсоемкие операции.
- При текстовых протоколах для контроля и отладки можно использовать стандартные текстовые терминалы. Числовые протоколы можно отлаживать только специальными программами. Это достоинство текстовых протоколов несколько принижается при использовании в протоколах контрольных кодов. Посчитать их вручную сложная, практически невыполнимая задача.
Я категоричный сторонник числовых протоколов. Особенно при использовании микроконтроллеров невысокой производительности. Текстовые протоколы просто сожрут все ресурсы дешевых плат Ардуино.
Итак, я выбрал числовой протокол обмена данными.
Разработка протокола обмена данными локального контроллера.
В последующих уроках мы будем использовать стандартный протокол ModBus. Реализуем на нем и обмен данными системы описанной выше. Но в этом уроке я собираюсь применить нестандартный протокол. Я хочу продемонстрировать, что:
- разрабатывать свои протоколы совсем несложно;
- специализированные протоколы часто на много эффективнее стандартных;
- протокол можно оптимизировать под свою конкретную задачу.
В общем виде любой протокол обмена выглядит так:
Команда: Адрес -> Код операции -> Данные -> Контрольный код ->
Ведущее устройство, то, что инициирует обмен, посылает команду. В общем случае команда должна содержать информацию:
- Адрес — адрес устройства, с которым происходит обмен. У нас интерфейс радиальный. Устройство может быть только одно, но при использовании шинных интерфейсов в сети может быть несколько контроллеров.
- Код операции – информация о том, что надо сделать.
- Данные – собственно информация обмена.
- Контрольный код – код позволяющий обнаружить ошибки данных при передаче.
В ответ устройство посылает: запрошенную информацию и контрольный код.
Все эти составляющие определяют свойства и качества протокола. Выбирая их можно оптимизировать протокол под свои требования по следующим критериям:
- Минимум данных передаваемых по каналу связи, а значит высокая скорость передачи данных.
- Минимальные требования к ресурсам контроллеров, т.е. простота обработки данных.
- Высокая надежность передачи данных, требуется повторная передача ошибочных данных.
- Высокая достоверность данных, т.е. высокая вероятность обнаружения ошибок передачи. Это требование к контрольному коду.
Я собираюсь оптимизировать протокол по критериям: минимум данных обмена и минимум ресурсов микроконтроллера. Т.е. я собираюсь реализовать самый простой протокол обмена.
Какие данные нам надо передавать:
- температура;
- напряжение;
- состояние кнопки.
Получать необходимо состояние светодиода.
Я выбрал такой формат команды.
Номер байта Формат числа Назначение 0 byte Код операции + адрес контроллера (0x10) 1 byte Состояние светодиода (младший бит) 2 byte Контрольный код ( байт 0 ^ байт 1 ^ 0xe5) Код операции я решил совместить в одном байте с адресом. Старшие 4 бита – код операции, младшие – адрес. В принципе у нас всегда только одно устройство и одна команда. Можно вообще отказаться от этого байта. Но я оставил его, чтобы показать такой вариант протокола.
Контрольный код я рассчитываю очень простым способом – делаю операцию “исключающее или” с двумя байтами и кодом 0xe5. Последнее необходимо, чтобы определить часто встречающуюся ошибочную ситуацию, когда все байты равны нулю.
Несмотря на простоту реализации, для данных состоящих из двух байтов это надежный контрольный код.
Ответ в моем протоколе выглядит так.
Номер байта Формат числа Назначение 0 … 3 float Температура 4 … 7 float Напряжение 8 byte Состояние кнопки (младший бит) 9 byte Резерв 10, 11 int Контрольная сумма (сумма байтов 0 … 9 ^ 0xa1e3) Данных здесь больше. Контрольный код представляет собой сумму 10 байтов с последующим ”исключающим или”. Реализуется расчет такого контрольного кода достаточно просто.
В протоколе всего одна команда, которая передает данные для светодиода и одновременно запрашивает данные с локального контроллера. Т.е. минимум данных и простые контрольные коды.
Разработка резидентной программы локального контроллера.
Принципиальный вопрос – я собираюсь реализовать обмен данными с центральным контроллером параллельным процессом.
Это значит, что где то в прерывании по таймеру работает программный модуль, о котором основная программа может даже не знать. Есть массив, в котором хранятся данные для передачи. Основная программа кладет данные в массив. А модуль обмена данными по сети при запросе от центрального контроллера передает эти данные центральному контроллеру.
Основная программа и модуль управления обменом по сети работают совершенно независимо друг от друга. У них есть только область общих данных. Программа может что-то измерять, регулировать и т.п. Те данные, к которым нужен доступ из сети программа хранит в области памяти, доступной модулю управления обменом по сети. Данные из сети основная программа берет из другой области памяти, области принятых из сети данных.
Будем строить программу по такому принципу.
Для начала я реализовал обработку всех датчиков. Я сделал это простым способом в цикле loop(). Заметьте, что программа при отработке паузы 0,9 секунд для ожидания измерения датчика DS18B20 не зависает. Но если применить для этого функцию delay() все будет работать.
Временно добавил блок вывода информации на компьютер для проверки.
// локальный контроллер
// радиальный интерфейс UARTOneWire sensTmp (3); // датчик подключен к выводу 3
Button button1(2, 30); // кнопка подключена к выводу 2float temperature; // температура
float voltage; // напряжение
byte bufSensTmp[9]; // буфер данных датчика температуры
int timeCount; // счетчик времениvoid setup()
MsTimer2::set(1, timerInterrupt); // прерывания по таймеру 1 мс
MsTimer2::start(); // разрешаем прерывание по таймеру
Serial.begin(9600); // скорость 9600
>void loop()
// ——————————- измерение температуры
if (timeCount < 50)
timeCount= 50;
sensTmp.reset(); // сброс шины
sensTmp.write(0xCC, 1); // пропуск ROM
sensTmp.write(0x44, 1); // инициализация измерения
>
// задержка 0,9 сек
if(timeCount > 950)
timeCount= 0;
sensTmp.reset(); // сброс шины
sensTmp.write(0xCC, 1); // пропуск ROM
sensTmp.write(0xBE, 1); // команда чтения памяти датчика
sensTmp.read_bytes(bufSensTmp, 9); // чтение памяти датчика, 9 байтов
if ( OneWire::crc8(bufSensTmp, 8) == bufSensTmp[8] ) < // проверка CRC
// данные правильные
temperature= (float)((int)bufSensTmp[0] | (((int)bufSensTmp[1])
>
else temperature= -200.; // ошибка измерения температуры
>// —————————— проверка измерений
Serial.print(«T color: #000080;»>Serial.print(temperature);
Serial.print(» U color: #000080;»>Serial.print(voltage);
if (button1.flagPress == true) Serial.println(» PRESS»);
else Serial.println(» FREE»);
>// ————————————— обработчик прерывания 1 мс
void timerInterrupt()
timeCount++;
button1.scanState(); // вызов метода обработки состояния кнопки
>Можно загрузить скетч по ссылке:
Зарегистрируйтесь и оплатите . Всего 60 руб. в месяц за доступ ко всем ресурсам сайта!
Проверил, все работает. Правильно измеряет температуру и напряжение, реагирует на нажатие кнопки.

Дальше я удалил блок вывода данных на компьютер и сделал перегрузку измеренных данных в массив dataSerialBuf. Данные из этого массива будут передаваться на центральный контроллер. Это и есть общая область данных для основной программы и модуля управления обменом по сети.
// —————————— перегрузка результатов измерений в буфер
noInterrupts;
* (float *)dataSerialBuf = temperature;
* (float *)(dataSerialBuf+4) = voltage;
if (button1.flagPress == true) dataSerialBuf[8]= 1;
else dataSerialBuf[8]= 0;
dataSerialBuf[9]= 0;
interrupts;
>Обратите внимание на то, что при перезагрузке я запрещаю прерывания. Прерывание может произойти в любой момент. Если основная программа загрузит в массив младший байт, например, температуры, а старший байт не успеет. То будут переданы неправильные данные: младший байт от нового значения температуры, старший – от старого.
Теперь программа считывает показания датчиков и кладет их в массив dataSerialBuf.
Осталось сделать доступ к массиву dataSerialBuf из сети.
Естественно модуль управления обменом по сети расположен в обработчике прерывания по таймеру. Вот его скетч.
// ——————— обмен данными
timeOutCount++;
n= Serial.available(); // число принятых байтовif (n == 0) timeOutCount= 0; // данных нет
else if (n > 3)
// принято больше данных, неправильно, сброс порта
timeOutCount= 0;
while (true)
>else
// не все байты приняты, проверка тайм-аута
if (timeOutCount > TIME_OUT)
// сброс порта
timeOutCount= 0;
while (true)
>
>Модуль управления обменом вызывается с периодом 1 мс.
- Проверяется, есть ли данные в буфере последовательного порта.
- Если данных нет, то сбрасывается счетчик тайм-аута приема данных.
- Если данные есть, то ожидается прием 3 байтов команды. Одновременно отсчитывается время тайм-аута приема. Оно задано псевдо оператором:
#define TIME_OUT 6 // время таймаута приема команды (мс)
- Если прием байтов растянулся на время более 6 мс, то определяется ошибка приема команды. Все сбрасывается, буфер последовательного порта очищается.
- Если приняты 3 байта команды, то проверяется контрольная сумма и формируется ответ.
Полностью скетч резидентной программы локального контроллера можете загрузить по ссылке:
Зарегистрируйтесь и оплатите . Всего 60 руб. в месяц за доступ ко всем ресурсам сайта!
Проверить работу локального контроллера можно только подключив его к компьютеру со специальной программой верхнего уровня. Не поверите, но у меня контроллер заработал сразу. Я не сделал ни одной ошибки. Наверное, благодаря тому, что очень простой протокол.
Программа верхнего уровня.
Локальный контроллер обменивается данными не с компьютером, а с программой на компьютере.
Я написал очень простую программу мониторинга параметров контроллера. Она отображает состояние всех датчиков, позволяет управлять светодиодом контроллера, показывает состояние обмена данными, фиксирует каждую ошибку обмена.

Загрузить программу можно по этой ссылке:
Зарегистрируйтесь и оплатите . Всего 60 руб. в месяц за доступ ко всем ресурсам сайта!
Необходимо разархивировать файл, выбрать COM порт. Все как в программах верхнего уровня из предыдущих уроков.
У меня все работает идеально. Ни одной ошибки обмена.
В следующем уроке будем управлять этим локальным контроллером не от компьютера, а от другой платы Ардуино.

Автор публикации
не в сети 3 месяца
Эдуард
Комментарии: 1919 Публикации: 197 Регистрация: 13-12-2015
Рубрика: Уроки Ардуино. Вы можете добавить постоянную ссылку в закладки.97 комментариев на « Урок 48. Обмен данными между платой Ардуино и компьютером через интерфейс UART. »
Здорово, понравилось. Очень ценю ваш фундаментальный подход!
Спасибо за добрые слова.
Спасибо за уроки, просто и понято.
Константин
а если Arduino с ком портом по uart «общаться» — какой переходник нужен? RS485 => RS232?Если на компьютере стандартный COM порт с уровнями +10/-10 , то нужен преобразователь уровней RS232. Например микросхема MAX232, SP232, ADM232. Через один урок я напишу об этом. RS485 здесь непричем.
Эдуард, возможно ли сделать в будущем пару уроков по программе верхнего уровня? Хотя бы самый минимальный уровень.
Здравствуйте!
Это сложная, объемная, совершенно другая тема. Может быть когда-нибудь инфопродукт сделаю. Подумаю, но в ближайшее время вряд ли.Здравствуйте, большое спасибо за уроки. Очень интересная тема по программам для компа верхнего уровня. Не могли бы Вы для начала выложить исходник программы UART_Arduino_PC.exe? Кому интересно, тот сам разберется что там к чему. Спасибо.
Здравствуйте!
У меня есть планы сделать инфопродукт по разработке программ верхнего уровня. Но когда до этого дойдут руки — не знаю. Сейчас очень занят.А пока исходник выложите
Эдуард. а насколько сложно подключить к СКАДА системе Trace Mode ?
Наверное, все возможно. Я с системой СКАДА не работал. Разбираться надо.Обратите внимание на проект «Мажордомо», если интересует система «Умный дом» своими руками. Активное сообщество, целая куча наработок и т.д. и т.п.
Здравствуйте, я не очень понял про время таймаута, вот например если я хочу чтобы данные передавались не каждую секунду, а каждые 2 мс, то какое тогда необходимо время таймаута, и как в таком случае необходимо изменить программу,
Здравствуйте!
Тайм-аут это нечто другое. В примере урока команда от компьютера состоит из 3 байтов. Представьте, что от компьютера на контроллер было передано 2 байта команды, а третий не пришел. Контроллер будет бесконечно ожидать 3го байта команды. Система приема данных зависнет.
Чтобы такого не произошло, контроллер отсчитывает время между приходом байтов команды. Если оно превысило время тайм-аута, то команда считается ошибочной и прерывается.Замечательные уроки!
Спасибо вам большое, нашел уйму полезной информации!
Хотел бы узнать, в какой среде разработки вы делаете программы верхнего уровня? Сложно ли это?Здравствуйте!
Спасибо за добрые слова.
Программы верхнего уровня я пишу в среде Borland C++ Builder.
Сложно ли это, не знаю. Все на свете и сложно и просто. Информации, конечно, надо знать много.Антон, погугли среду разработки Small Basic, с её помощью очень несложно, даже проще ардуины, мне кажется.
Что лучше использовать
MsTimer2::start(),MsTimer2::stop() или interrupts,noInterrupts?
Здравствуйте!
Только первый вариант. noInterrupts запрещает все прерывания, в том числе и системные.Добрый день. Эдуард подскажите. А если есть необходимость подключить два модуля к NANO. Можно ли как-то использовать другие пины в качестве Rx Tx? Заранее спасибо.
Здравствуйте!
В принципе можно, но UART будет программный и параллельной задачей обмен не реализовать. Для локальной сети лучше использовать аппаратный UART.Что означает в коде:
* ((byte *)(& sum))
? СпасибоЗдравствуйте!
* ((byte *)(& sum))
Об этом написано в уроке 15.
С помощью указателей формируется младший байт от переменой sum типа int.
& sum — получается адрес переменной sum
(byte *)(& sum) — адрес преобразуется в адрес переменной типа byte
* ((byte *)(& sum)) считывается значение по этому адресу.Добрый день, Эдуард!
Можете ли Вы поделиться исходным кодом программы для компьютера к этому уроку, exe-файл которой Вы выложили?
Заранее спасибо!
Виктор.Здравствуйте!
Это совершенно другая, сложная тема. Я подумываю написать инфопродукт об этом, но сейчас не хватает времени.Доброго времени суток!
Спасибо за скорый ответ.
Уроки к Ардуино отличные!
Буду с нетерпением ждать продолжения, желательно к delphi!
С уважением ,
ВикторДень добрый. Урок прекрасный, но возникла сложность. Внутри обработчика прерывания по таймеру «не работает» чтение данных c UART. Т.е. данных в буфере нет (Serial.available не возвращает положительного результата). В сети пишут, что это из-за того, что получение данных Serial само реализуется через прерывания «и вообще не нужно ничего по UART в обработчике слать». Но у вас, судя по всему, всё работает. Подскажите, пожалуйста, что может быть не так?
Здравствуйте!
Все правильно. В обработчике прерывания Serial работать не будет. При вызове любого прерывания, остальные запрещаются. Разрешаются только при выходе из функции-обработчика. А зачем вам использовать Serial внутри обработчика? Функции обработки прерываний должны быть как можно короче, занимать меньше времени.Тогда я, видимо, не совсем поняла урок. «Естественно модуль управления обменом по сети расположен в обработчике прерывания по таймеру. Вот его скетч». И в полном варианте программы по ссылке вроде бы тоже весь обмен по UART реализован именно в обработчике прерывания timerInterrupt()
Здравствуйте!
Может я неправильно выразился. Я имел ввиду, что в обработчике прерывания нельзя ждать завершения каких-либо процессов класса Serial. Можно только проанализировать состояние и выйти из обработчика.Эдуард, здравствуйте. У Вас потрясающие уроки!
Дабы получше усвоить материал, пытаюсь написать свою модификацию протокола и программу верхнего уровня (параллельно учу Python). Но возникла проблема: ардуино Мега 2560 возвращает всегда нулевое значение светодиода, независимо, горит он или нет. Чтобы исключить возможные ошибки логики убрал из скетча все лишнее, оставил только кнопку и один светодиод. Ситуация не изменилась, в логе одно и тоже нулевое значение, при этом светодиод отрабатывает идеально.
Ссылка на файлы проекта: https://www.dropbox.com/sh/xoxraiatujf1xfv/AAA3nHeTgRpGe-cpUDlg6ISda?dl=0 В процессе написания комментария родилась мысль писать в буфер не состояние кнопки, как в Вашем уроке, а состояние светодиода. И все заработало. Но очевидны недостатки. Хочется читать сенсор/датчик, а не промежуточную переменную.Здравствуйте!
Спасибо за оценку уроков.
Вопроса я не понял. С одной стороны вы пишите «ардуино Мега 2560 возвращает всегда нулевое значение светодиода, независимо, горит он или нет». С другой — «писать в буфер не состояние кнопки, как в Вашем уроке, а состояние светодиода. И все заработало».
Если сомневаетесь, то проверьте работу светодиода в цикле loop. Занесите в него 0, считайте и передайте через последовательный порт. Затем тоже самое с 1.Да, наверно действительно несколько непонятно написал. Проблема в том, что по какой то причине, конструкция
////////////////////////
noInterrupts();
if (button1.flagPress == true) dataSerialBuf[0] = 1;
else dataSerialBuf[0] = 0;
interrupts();
////////////////////////
не пишет в буфер состояние кнопки, простейшая программа верхнего уровня показывает в порту нули, независимо, жму я на кнопку или нет.
При замене проверки состояния кнопки на проверку состояния светодиода, которым рулит кнопка, программа верхнего уровня начинает показывать и единицы. Но первоначальной задачей было все же отслеживать состояние кнопкиНе понятно. Все должно работать. А простая программа в цикле loop() работает?
Зачем вы прерывания запрещаете? Прерывания надо запрещать если проверяете тип данных длиной более одного байта. Часть данных может измениться в прерывании. Получится половина данных новая, половина старая.Хотелось бы посмотреть исходник программы для компьютера
Здравствуйте…..у меня почему_то пояснение в виде каких то иероглифов , как их сделать читаемыми? подскажите пожалуйста…….
Здравствуйте! Это ваш браузер так выводит. Нажмите на ссылку. Откроется окно с иероглифами. Правой кнопкой мыши, выбрать сохранить как. Будет сохранено в исходном виде.
Доброго времени) Вы делаете в основном цикле «погрузку» данных в массив с предварительной отменой прерываний и последующим их возобновлением.
А если операцию погрузки в массив делать в самом прерывании, непосредственно перед отправкой, на что это повлияет?Здравствуйте!
Смысл в том, чтобы развязать операции формирования данных массива и отправки. Вы просто кладете данные в массив, а параллельный процесс эти данные по запросу отсылает на компьютер. Основная программа не знает, что существует параллельный процесс. Он может отослать данные в любой момент. Для того, чтобы не было отослано половина старых, а половина новых данных и запрещается прерывания.Спасибо! Для чего запрещаются прерывания я понимаю, просто в моем случае я сначала жду массив данных, а в ответ отправляю такой же массив других данных. И если соответствующие операции извлечения данных и их погрузки делать в самом прерывании, я бы мог обойтись одним массивом, потому и спросил, как лучше:) Еще такая проблема — я похожую связку сделал между Attiny45 и ESP8266 NodeMCU, и ESP у меня при обрыве связи начинает бесконечно перезагружаться в том случае, если я очистку буфера вашим способом делаю. Без очистки всё идеально, можно обрывать и восстанавливать связь, ничего не зависнет. С чем может быть такое связано? C особенностями ESP?
Много причин может быть. Например, вы с размером массива промахиваитесь и портите какую нибудь переменную.
Всё, нашел причину) Дело всё-таки в ESP. Ваш способ очистки буфера /* while (true) < if (mySerial.read() == 0xffff) break;>*/ на ней работать не хочет, зато сработал вот такой: /* while (Serial.available()) < Serial.read(); >*/ Хотя на других платах Ваша очистка отлично отрабатывает
здраствуйте! у меня такая проблема==>
‘timerInterrupt’ was not declared in this scope почему такая ошибка не понялЗдравствуйте!
Прежде всего проверьте, что у вас в пути к файлам Ардуино нет папок с именами на кириллице.добрый день! решил эту проблемуб но у меня вопрос есть.я хочу передавать инвормайию с ардуино на другой микроконтролер по uart ,как сообщение должно быть длиной 10 символов(10 байт) если оно отправляется с ардуино платы на другой и 13 символов если оно принимается ардуино платой с другой.
Первый символ всегда 0x1. Он означает начало сообщения.
Второй символ указывает на тип сообщения, 0x20 для сообщения отправляемого ардуино на другой плату, и 0x21 для сообщения отправляемого 2.платой на ардуино.
Следующие 8 символов содержат в себе любые данные которые нужно передать. в моцй задаче это измерения двух датчиков.вот я не очень знаю как протокол написать по этому поводу.я иностранный студент в Москве и это задача часть моей дипломной работы и мне надо ваш помощь.есть другой человек он пишет коды для 2. микроконтролер, моя задача коду ардуино.ни понятие веши вот протокол как писат и понимат,ваш урок похож видимо решить мою задачу.вы можете помогать?А вы не хотите использовать библиотеку протокола ModBus из последующих уроков? Ей очень удобно работать, обмен делается очень просто.
если бы я использоыал датчик DHT11 ,то я должен использовать команды (0xBE),(0x44, 1),(0xСС, 1)
.
или все это команды для DS18B20?Здравствуйте!
Конечно. В схеме установлен датчик DS18B20, значит и протокол для него.
значить все это команды использовать ,но только библиотеку менять на?Другой датчик, другой протокол, другая библиотека, другие команды.
Вы все-таки, если есть возможность, примите мой совет использовать для обмена протокол ModBus и библиотеки для нее из последующих уроков. Будет проще реализовывать обмен данными.ок спасибо болшое!
Урок 58. Обмен данными между платами Ардуино через UART по протоколу ModBus. Библиотека Tiny_ModBusRTU_Master.
Урок 61. Аппаратная реализация интерфейса RS-485. Объединение плат Ардуино в локальную сеть RS-485.и мне просьба ,вы можете готовить урок ,как пишиться собственные протоколы для обмена данных между двумя микроконтролерами и их коды что зачем конкретна ,потому что для начинаюших это трудно или если у вас есть докуманты об этом можете разпубликовать?за все инфо спасибо большое!
Добрый день, Эдуард. Не поможете ли разжевать одну фразу из кода (участок перегрузки данных в буфер): * (float *)(dataSerialBuf+4) = voltage; Что в ней означает каждый символ, по такому примеру:
temperature= (float)((int)bufSensTmp[0] | (((int)bufSensTmp[1])Здравствуйте! dataSerialBuf — это имя массива, а значит указатель на него.
+ 4 — смещение указателя на нужные нам данные.
(float *) — явное преобразование указателя на указатель данных типа float.
* — запись данного по указателю типа float, т.е. запись данного типа float.
Урок 15 посвящен указателям.Спасибо за столь скорый ответ.
Поправьте пожалуйста, если я допустил неточность. Очень-очень надо. Скопировать кусок чужого кода в свой проект легко, но мне непременно надо разобраться, как самому написать этот кусок кода. Поэтому такая дотошность.
В программе объявлена глобальная переменная типа «массив» с именем dataSerialBuf[], которая имеет 10 ячеек по 1 байту и расположена в памяти контроллера по адресу, назначенному (компилятором?). Пусть это будут ячейки с адресом от 15600 (для первого байта) и до 15600+9 для последнего (всего 10). Верно? Когда из программы есть обращение к имени массива — это равносильно что напрямую называть адрес первой ячейки, т.е. 15600, в которой лежит первый байт массива, а именно dataSerialBuf[0]. Использование указателей позволяет не заморачиваться с ТОЧНЫМ расположением в памяти микроконтроллера переменных, достаточно назвать её имя. И указатель, как стрелочник, добросовестно направит к адресу с первым байтом. Если это переменная типа int, то она имеет два байта и для неё компилятором зарезервировано два байта в памяти. Если float, то четыре. Здесь я лично для себя прибег к методу ассоциаций и представил переменную типа float, как четыре вагона поезда. В каждый вагон мы можем положить 8 бит(записать), и в каждом вагоне можем посмотреть что там лежит (прочитать). Таким образом, переменную dataSerialBuf[] можно представить в виде 10-ти вагонов. Нумерация вагонов начинается с нуля. С «0» по «3» (четыре вагона-байта) это для переменной temperature типа float, с «4» по «7» (ещё четыре вагона-байта) для переменной voltage типа float, один вагон (№8) для того, чтоб передать состояние светодиода всего одним битом из восьми, один вагон (№9) зарезервирован и полезного груза пока не несет.
Тогда запись вида: * (float *)(dataSerialBuf+4) = voltage; можно интерпретировать как, в вагон №4 состава dataSerialBuf[] положить переменную voltage, которая имеет тип float, а потому займет 4 вагона с №№ 4-7.
Сильно не пинайте за очевидные для Вас вещи. Я, как и многие, учусь по урокам на сайте. За сайт и возможность поучиться у более сильного программиста, выражаю Вам отдельную благодарность.Да, все правильно. Добавить нечего.
Если вы реализовываете практическую задачу, то будет проще сделать это с помощью библиотек, поддерживающих протокол ModBus. Этому посвящены несколько последующих уроков, и в разделе Умный дом практически реализовывается обмен по ModBus.Спасибо.
Да собственно MODBUS это и есть моя цель. Точнее MODBUS RTU на RS-485. Так что здесь для меня просто кладезь информации. Потом мне надо ещё построить мост MODBUS_RTU — Mosquitto.Или может здесь применена какая-то разрешенная в С форма записи вроде такой: sum ^= 0xa1e3; тоже самое что и sum = sum ^ 0xa1e3;
Здравствуйте
Подскажите пожалуйста, что означает конструкция
while (true) < if (Serial.read() == 0xffff) break;>?
и что вообще означает понятие «сброс порта»?Здравствуйте!
В буфере порта могли остаться несчитанные данные. Эта конструкция очищает буфер. Считывает данные, пока не встретится код 0xffff, что означает, что буфер пуст.Здравствуйте, еще можно вопрос?
Формула для контрольной суммы (сумма байтов 0 … 9 ^ 0xa1e3) как-то зависит от количества байт? Другими словами, если будет 5 байт или 15 байт — формула изменится (ну кроме количества слагаемых)?При данном способе не зависит. Это просто сумма с переполнением.
Эдуард, подскажите, пожалуйста такой момент. Контрольная сумма объявлена как unsigned int sum. А если слагаемые могут быть отрицательными (по логике передаваемых данных) — нужно ли объявить контрольную сумму как знаковую (int)? Или логика передаваемых данных не влияет на подсчет контрольной суммы?
Здравствуйте!
Объявляйте сумму, как беззнаковую переменную. Хотя при суммировании не важно, как объявлены слагаемые и переменная суммирования. Результат будет одним и тем же. Используются знаковые или беззнаковые переменные повлияет только при интерпретации результата — сравнение больше меньше и т.п.
Правильнее слагаемые явно преобразовать в беззнаковые byte.Эдуард, всю голову сломал. Подскажите пожалуйста — при каком значении n программа попадет вот сюда:
else // не все байты приняты, проверка тайм-аута
if (timeOutCount > TIME_OUT) Вообще — при каких обстоятельствах она туда может попасть?Обычно такие конструкции отрабатывают тайм-ауты, т.е. зависание обмена по непредсказуемым причинам. Например, программа ждет 5 байтов, а пришло только 3. Провод оборвался не вовремя или помеха на линию связи. Без отработки такой ситуации программа зависнет навечно.