Объект string в arduino и команды через последовательный порт

Объект String в Arduino и команды через последовательный порт

Сколько ни изучаю Arduino, она не перестаёт удивлять меня своей простотой. К примеру, собирая и тестируя систему «умного дома», я думал, что подача команд с компьютера будет самой сложной частью — это же надо принимать строку из последовательного порта, распознавать её, следить, чтобы не возникало ошибок. Однако оказалось достаточно почитать сайт Arduino.cc да потестить пару примеров, как стало ясно — разработчики постарались, чтобы оградить нас от написания длинного и унылого кода. К слову сказать, с задачей в итоге я справился за вечер, под конец даже подумывая: «а какую бы ещё команду прикрутить. «

Итак, предположим, вы уже умеете программировать Arduino и можете разобраться в своём или чужом коде. Одним из основных понятий являются переменные и их типы. Ну-ка навскидку? byte, int, long, char, string. Два последних — по сути одно и то же, ибо string — массив переменных типа char (Кто-нибудь сейчас должен возразить, что char представляется в виде байтового числа, но речь не об этом). Итак, всё, что принимается из последовательного порта, следует читать, как char:

Это первый пример, который может прид ти в голову . Создаём пустую строку, затем, если есть, что читать из последовательного порта, посимвольно её заполняем. Функция Serial.available() возвращает количество байт, доступных для чтения, а если там пусто — то 0, очевидно. Этим можно пользоваться, чтобы узнать длину поданной команды, хотя мы и так её узнаем в приведённом примере — это величина переменной z на выходе из цикла. Да, строка из пробелов (ASCII код пробела — не ноль!) — это терпимо, но всё-таки не очень хорошо, по возможности избегайте этого. А догадливый читатель сможет похвалить себя, если сразу догадается, что стоит исправить в вышеприведённом коде. Для тех, кто не догадался — подсказка: char inchar[6] — строка длиной 6 символов. Если строке присваивается значение, компилятор позволяет не указывать явно её длину, поэтому в примере квадратные скобки пустые.

Кстати, не стоит забывать прописать в setup()

А во встроенном мониторе порта, в который, собственно, и будут отправляться команды, указать скорость — 9600 бод, иначе увидите крякозябры.

Далее, что делать с полученной строкой? Те, кто предложат сравнивать побайтово строку с известными значениями (а такая мысль наверняка кому-то может придти в голову) после прочтения статьи переместятся вперёд во времени лет на 20. Из прошлого, я имею в виду 🙂

Поиск по документации Arduino IDE даёт два варианта, что такое string. Это сам string как строка char’ов, и String, являющийся объектом. Что такое объект? Согласно википедии, это «некоторая сущность в виртуальном пространстве, обладающая определённым состоянием и поведением, имеющая заданные значения свойств (атрибутов) и операций над ними (методов)». Другими словами — переменная со встроенными функциями, делающими что-то с этой переменной. Чтобы начать работать с этим объектом, напишем что-нибудь такого вида:

Здесь к input будут добавляться всё новые символы, пока буфер не иссякнет. Тогда можно будет анализировать полученную строку, например, так:

Код использует очень удобные функции, встроенные в объект String. Это startsWith(), которая возвращает единицу, если строка начинается с того, что записано в скобках, substring(), возвращающая кусок строки, начинающийся в данном случае с 5-го символа (считается, начиная с нуля), trim(), отбрасывающий всё лишнее по краям строки, ну и toInt(), превращающий то, что осталось, в число типа Int. Это число неплохо ещё и проверить на предмет попадания в рамки ожидаемого. В итоге, если дать команду «PauSe 567 «, то МК подождёт ровно 567 миллисекунд.

Про trim() стоит написать отдельно. Он нужен не только для того, чтобы отбросить пробел в начале получившейся строки, но в первую очередь — чтобы избавиться от символов в её конце. Это служебные символы, добавляющиеся при отправке сообщения — NL (новая строка) и CR (возврат каретки). Они нужны как раз для того, чтобы сигнализировать о конце команды, но могут и помешать. Поэтому, несмотря на то, что в мониторе порта можно выбрать, какие из этих символов посылать или не посылать ничего, лучше перестраховаться. Тем более, что делается это в одну строчку кода.

А вот и список функций (методов) объекта String.

charAt() — возвращает символ, стоящий на указанном месте

concat() — функция конкатенации, т.е слияния двух строк в одну. Правда string1 = string1 + string2 это то же самое, что и string1.concat(string1, string2), а записывается проще и понятнее.

equals() — возвращает единицу, если строка посимвольно равна тому, что написано в скобках. Есть ещё equalsIgnoreCase(), который игнорирует регистр (верхний или нижний)

endsWith() — который работает аналогично startsWith()

indexOf() — возвращающий место в строке символа(или строки) в скобках. Ищет с конца и возвращает -1, если не найдено.

length() — выдающий длину строки

setCharAt() — требующий место и символ, который надо поставить на это место, например: string1.setCharAt(3, ‘d’) поставит d третьим символом в строке взамен того, что там стояло

  • И ещё несколько других, которые вряд ли вам понадобятся, если вы не в силах залезть на arduino.cc и прочитать о них 🙂
  • Вот и всё, что хотел рассказать. Надеюсь, эта статья поможет не бояться ООП и научить вашего домашнего робота на Arduino повиноваться сигналам с компа

    Строки и массивы символов

    Мы с вами уже познакомились с символами в уроке про типы данных. Напомню: символ является переменной (или константой) типа char и хранит в себе код буквы в таблице символов. Создан для удобства программиста, чтобы он мог работать не кодами, а с читаемыми символами. Как и в жизни, символы соединяются в слова, здесь они называются строки. У нас есть два набора инструментов по работе с ними: обычные строки (массивы символов) и String-строки.

    • Массив символов (с англ. char array) – это просто массив данных типа char , про массивы мы уже недавно говорили и вы должны понять, о чём идёт речь. Основные особенности: максимальный размер массива строки должен быть известен заранее, и к каждому элементу такой строки можно обратиться при помощи квадратных скобок. Любой текст, явно заключённый в «двойные кавычки» , воспринимается программой как массив символов.
      • Информация для тех, кто читал урок про указатели: будучи обычным массивом, строка является указателем на свой первый элемент (то есть программа конкретно знает только о том, где строка начинается). Встроенные функции по работе со строками ориентируются на нулевой символ, который обязательно находится в конце строки. Таким образом и определяется длина строки: от начала и до нулевого символа.
    • Основным отличием String-строки от массива символов является то, что строка – динамический массив, у которого не нужно указывать размер, он может меняться в процессе работы программы. Также строка является не просто типом данных, а объектом очень мощного класса одноимённой библиотеки String , которая автоматически подключается в код и добавляет огромную кучу удобных инструментов для работы с текстом: разделением, обрезкой, поиском и заменой и т.д. Строка может быть создана из любого типа данных и преобразована обратно почти во все из них.

    String-строки

    Давайте рассмотрим большой пример, из которого будет понятно, как объявить строку и как с ней работать, а также учтены некоторые тонкости:

    Как вы могли заметить, строки можно объявлять большим количеством способов, а также буквально складывать строки, как числа, оператором + . Я уже говорил, что строки являются объектами класса String , и у этого класса есть огромное количество удобных методов по работе со строками, далее мы их все рассмотрим с некоторыми примерами. Но для начала запомните вот что: строки – очень тяжёлый инструмент, очень медленный и занимающий кучу памяти: уже просто само наличие строк (от одной и более) в прошивке занимает +5% Flash памяти, т.к. подключается сам “инструмент” – класс String . Для небольших проектов это не страшно, памяти всегда будет навалом. Также неаккуратное использование строк может приводить к фрагментации оперативной памяти и зависанию программы, подробнее читайте ниже.

    Инструменты для String

    Помимо набора методов, библиотека String имеет несколько перегруженных операторов, благодаря которым мы можем:

    • Работать с элементами String-строки как с массивами: myString[2] = ‘a’;
    • Сравнивать String-строки между собой: if (myString1 == myString2)
    • Сравнивать String-строки с массивами символов: if (myString1 == «kek»)
    • Инициализировать String-строки любым численным типом данных, символом, массивом символов и массивом символов внутри макроса F(): String myString = 10.0;
    • Прибавлять к строке любой численный тип данных, символ или массив символов: myString += 12345;
    • “Собирать” строки сложением из любых типов данных. Если первое (левое) слагаемое не является String – нужно преобразовать к (String) . Остальные “подтянутся” сами: String str = (String)10 + » value» + var + ‘,’ + 3.14;

    Во всех перечисленных случаях можно считать, что данные “сами превратятся в String” и будут взаимодействовать со String-строкой, стоящей слева от оператора. Это не совсем так, но для понимания достаточно. Итак, методы для работы со строками. Как и все методы, они применяются к своим объектам (к строкам) через точку. В рассмотренных ниже примерах строка называется myString.

    • Возвращает отрицательное число, если myString идёт до myString2
    • Возвращает положительное число, если myString идёт после myString2
    • Возвращает 0, если строки одинаковы

    Длина строки

    Небольшой комментарий по поводу длины строки: в отличие от char array, узнать длину String строки можно только при помощи метода length() (потому что String-строка является динамическим массивом, а sizeof() выполняется на этапе компиляции):

    Меры предосторожности

    Строка по сути является динамическим массивом, её размер может меняться по ходу программы. Это очень удобно, но и очень опасно, потому что память может фрагментироваться, закончиться и т.д. Рассмотрим несколько базовых принципов безопасной работы со строками:

    • Если вам нужно передать String-строку в функцию – делайте это по ссылке (подробнее в этом уроке). Это избавит программу от дублирования куска данных, ведь из-за достаточно большой строки оперативная память может закончиться и программа зависнет! Пример: void someFunc(String &str); – функция принимает ссылку на строку. На использовании функции это никак не скажется, но при её вызове не будет создаваться копия строки!
    • Не вызывайте лишний раз преобразование в String, библиотека сделает это за вас! Например не нужно писать myString += String(value); , достаточно просто myString += value; . При создании длинной строки путём прибавления новых данных “по кусочку” это спасёт от фрагментации памяти.
    • Оборачивайте участки кода с объёмной работой со строками в <фигурные скобки>: локально созданные String-строки будут удалены из памяти сразу после закрывающей > , что может предотвратить фрагментацию и переполнение памяти.

    Массивы символов

    Массивы символов, они же “char array”, являются ещё одним способом работы с текстовыми данными. Этот вариант имеет гораздо меньше возможностей по работе с текстом, но зато занимает меньше места в памяти (не используется библиотека String ) и работает значительно быстрее. К массиву символов применяются те же правила, какие работают для обычных массивов. Рассмотрим пример, в котором объявим массивы символов разными способами:

    Можно работать с элементами строк как с массивами:

    В отличие от строк, массивы символов нельзя:

    Для этого существуют специальные функции, о которых мы поговорим ниже.

    При инициализации массива символов “текстом в кавычках” создаётся массив с размером на 1 больше, чем количество символов в тексте: компилятор дописывает в конец строки нулевой символ NULL , благодаря которому различные инструменты по работе со строками будут видеть длину строки: от первого символа и до NULL .

    Длина строки char array

    Для определения длины текста можно использовать оператор strlen() , который возвращает количество символов в массиве. Сравним его работу с оператором sizeof() :

    Здесь оператор sizeof() вернул количество байт, занимаемое массивом. Массив я специально объявил с размером бОльшим, чем содержащийся в нём текст. А вот оператор strlen() посчитал и вернул количество символов, которые идут с начала массива и до нулевого символа в конце текста (без его учёта). А вот такой будет результат при инициализации без указания размера массива:

    Массив строк

    Очень мощной фишкой массивов символов является возможность создать один массив с несколькими строками, и обращаться к ним по номеру. Выглядит это следующим образом:

    Данный способ работы со строками хорош тем, что строки хранятся под номерами, и это крайне удобно при работе с дисплеями и в частности создании текстового меню: практически все библиотеки дисплеев умеют выводить массив символов одной командой.

    F() macro

    Строки (массивы символов) являются очень тяжёлым грузом, ведь текст в строке хранится в оперативной памяти микроконтроллера, а её не так уж и много. Есть готовый инструмент, позволяющий удобно хранить текстовые данные во Flash памяти микроконтроллера. Этот способ хорош для вывода фиксированных текстовых данных, например, в монитор порта или на дисплей:

    Строка “Hello, World!” будет записана во Flash память и не займёт 14 байт (13 + нулевой) в оперативной.

    Экономия памяти

    “Строки” в массиве строк тоже хранятся в оперативной памяти, а рассмотренный выше F() macro к ним нельзя. То есть вот такой код приведёт к ошибке:

    Как же быть? Массив строк можно сохранить в PROGMEM, программной памяти микроконтроллера, Flash. Вот такую конструкцию можно использовать как шаблон:

    Да, сложно и громоздко, но при большом объёме текстовых данных это может спасти проект! Например при создании устройства с текстовым меню на дисплее. Более подробно о хранении строк в PROGMEM читайте в уроке про PROGMEM.

    Инструменты для char array

    Есть готовые функции, позволяющие конвертировать различные типы данных в строки:

    • itoa(int_data, str, base) – записывает переменную типа int int_data в строку str с базисом* base.
    • ltoa (long_data, str, base) – записывает переменную типа long long_data в строку str с базисом* base.
    • ultoa (unsigned_long_data, str, base) – записывает переменную типа unsigned long unsigned_long_data в строку str с базисом* base.
    • dtostrf(float_data, width, dec, str) – записывает переменную типа float float_data в строку str с количеством символов width и знаков после запятой dec.

    * Примечание: base – основание системы исчисления, тут всё как при выводе в Serial:

    • DEC – десятичная
    • BIN – двоичная
    • OCT – восьмеричная
    • HEX – шестнадцатеричная

    И наоборот, можно преобразовывать строки в численные данные (функция вернёт результат):

    • atoi(str) – преобразование str в int
    • atol(str) – преобразование str в long
    • atof(str) – преобразование str в float

    2 кБ Flash памяти!! Максимально избегайте их применения в крупном проекте. Для преобразования можно сделать свою функцию, практически готовые варианты для всех типов данных можно найти в стандартной ардуиновской Print.cpp (ссылка на файл на гитхабе Arduino).

    Массивы символов не так просты, как кажутся: их возможности сильно расширяет стандартная библиотека cstring. Использование всех доступных фишек по работе с массивами символов позволяет полностью избавить свой код от тяжёлых String-строк и сделать его легче, быстрее и оптимальнее. Подробно обо всех инструментах можно почитать в официальной документации. Очень интересный пример с манипуляцией этими инструментами можно посмотреть здесь. А мы вкратце рассмотрим самые важные. Важный момент: библиотека работает со строками как с указателями, и многие функции возвращают как результат именно указатель. Как это понимать, если вы не читали урок про указатели и/или тема слишком сложная? Указатель – первый символ в строке, работа со строкой начнётся с него. Последним символом является нулевой NULL символ, и для программы строка существует именно в этом диапазоне. Если какая-то функция возвращает указатель на конкретный символ в строке – по сути она возвращает кусок строки начиная с этого символа и до конца строки. Например, мы искали символ , в строке «Hello, world!» . Программа вернёт нам указатель на эту запятую, по сути это будет кусочек той же самой строки, содержащий «, world!» . Просто “начало” строки сместится.

    Arduino String – работа со строками в ардуино

    Arduino String – основная библиотека для работы со строками в ардуино. С ее помощью существенно упрощается использование массивов символов и строк в скетче. Объект типа String содержит множество полезных функций для создания и объединения строк, преобразований string to int (парсинг чисел) и int to string (форматирование чисел). Строки используются практически в любых проектах, поэтому и вероятность встретить String в скетче очень высока. В этой статье мы постараемся рассмотреть основные методы этого класса и наиболее часто возникающие ситуации.

    Для чего нужен String в ардуино

    Стандартным способом работы со строками в языке C является использование массива символов. Это все означало необходимость работы с указателями и понимания адресной арифметики. В ардуино и C++ у программистов появилось гораздо больше возможностей. Все “низкоуровневые” операции по работе со строкой выделены в отдельный класс, а для основных операций даже переопределены операторы. Например, для объединения срок мы просто используем хорошо знакомый знак “+”, а не зубодробильные функции типа malloc и strcpy. С помощью String мы работаем со строкой как с целым объектом, а не рассматриваем его как массив символов. Это позволяет сосредоточиться на логике скетча, а не деталях реализации хранения символов в памяти.

    Естественно, у любого “упрощения” всегда есть свои подводные камни. String всегда использует больше оперативной памяти и в некоторых случаях функции класса могут медленнее обрабатываться. Поэтому в реальных больших проектах придется тщательно взвешивать все плюсы и минусы и не забывать, что никто не мешает нам работать со строками в стиле С. Все обычные функции обработки массивов char остаются в нашем арсенале и в arduino.

    Создание строк в ардуино с помощью String

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

    • char myCharStr [ ] = “Start”; – массив типа char с завершающим пустым символом;
    • String myStr = “Start”; – объявляем переменную, создаем экземпляр класса String и записываем в него константу-строку.
    • String myStr = String(“Start”); – аналогичен предыдущему: создаем строку из константы
    • String myStr(myCharStr); – создаем объект класса String с помощью конструктра, принимающего на вход массив типа char и создающего из char String.
    • String myStr = String(50); – создаем строку из целого числа (преобразование int to string).
    • String myStr = String(30, H); – создаем строку – представление числа в 16-чной системе (HEX to String)
    • String myStr = String(16, B); – создаем строку – представление числа в двоичной системе (Byte to String).

    Каждый раз, когда мы объявляем в коде строку с использованием двойных кавычек, мы создаем неявный объект класса String, являющийся константой. При этом обязательно использование именно двойных кавычек: “String” – это строка. Одинарные кавычки нужны для обозначения отдельных символов. ‘S’ – это символ.

    Функции и методы класса String

    Для работы со строками в String предусмотрено множество полезных функций. Приведем краткое описание каждой из них:

    • String() – конструктор, создает элемент класса данных string. Возвращаемого значения нет. Есть множество вариантов, позволяющих создавать String из строк, символов, числе разных форматов.
    • charAt() возвращает указанный в строке элемент. Возвращаемое значение – n-ный символ строки.
    • compareTo() – функция нужна для проверки двух строк на равенство и позволяет выявить, какая из них идет раньше по алфавиту. Возвращаемые значения: отрицательное число, если строка 1 идет раньше строки 2 по алфавиту; 0 – при эквивалентности двух строк; положительное число, если вторая строка идет раньше первой в алфавитном порядке.
    • concat() – функция, которая объединяет две строки в одну. Итог сложения строк объединяется в новый объект String.
    • startsWith() – функция показывает, начинается ли строка с символа, указанного во второй строке. Возвращаемое значение: true, если строка начинается с символа из второй строки, в ином случае false.
    • endsWith() – работает так же, как и startsWith(), но проверяет уже окончание строки. Также возвращает значения true и false.
    • equals() – сравнивает две строки с учетом регистра, т.е. строки «start» и «START» не будут считаться эквивалентными. Возвращаемые значения: true при эквивалентности, false в ином случае.
    • equalsIgnoreCase() – похожа на equals, только эта функция не чувствительна к регистру символов.
    • getBytes() – позволяет скопировать символы указанной строки в буфер.
    • indexOf() – выполняет поиск символа в строке с начала. Возвращает значение индекса подстроки val или -1, если подстрока не обнаружена.
    • lastIndexOf() –выполняет поиск символа в строке с конца.
    • length() – указывает длину строки в символах без учета завершающего нулевого символа.
    • replace() – заменяет в строке вхождения определенного символа на другой.
    • setCharAt() – изменяет нужный символ в строке.
    • substring() – возвращает подстроку. Может принимать два значения – начальный и конечный индексы. Первый является включительным, т.е. соответствующий ему элемент будет включаться в строку, второй – не является им.
    • toCharArray() – копирует элементы строки в буфер.
    • toLowerCase() – возвращает строку, которая записана в нижнем регистре.
    • toUpperCase() – возвращает записанную в верхнем регистре строку.
    • toInt() – позволяет преобразовать строку в число (целое). При наличии в строке не целочисленных значений функция прерывает преобразование.
    • trim() – отбрасывает ненужные пробелы в начале и в конце строки.

    Объединение строк Arduino

    Объединить две строки в одну можно различными способами. Эта операция также называется конкатенацией. В ее результате получается новый объект String, состоящий из двух соединенных строк. Добавить к строке можно различные символы:

    • String3 = string1 + 111; // позволяет прибавить к строке числовую константу. Число должно быть целым.
    • String3 = string1 + 111111111; // добавляет к строке длинное целое число
    • String3 = string1 + ‘А’; // добавляет символ к строке
    • String3 = string1 + “aaa”;// добавляет строковую постоянную.
    • String3 = string1 + string2; // объединяет две строки вместе.

    Важно осторожно объединять две строки из разных типов данных, так как это может привести к ошибке или неправильному результату.

    Arduino string to int и string to float

    Для конвертации целочисленных значений string to int используется функция toInt().

    String MyStr = “111”;

    int x = MyStr.toInt();

    Если нужно конвертировать объект с плавающей запятой, применяется функция atof().

    String MyStr = “11.111”;

    MyStr.toCharArray(myStr1, MyStr.length()); // копируется String в массив myStr1

    float x = atof(myStr1); // преобразование в float

    Преобразование int to string

    Для создания строки из числа не требуется делать особых телодвижений. Мы можем просто объединить строку и число:

    String str = “Строка номер “+ i;

    Можем создать объект, используя конструктор

    String str = String(50);

    Можем объединить оба способа:

    String str = “Строка номер “+ String(50);

    Преобразование String в массив char

    Тип данных Char позволяет объявлять текстовые строки несколькими способами:

    • char myStr1[10]; – в данном случае объявлен массив определенного размера.
    • char myStr2 [6] = <‘a’, b, ‘c’, ‘d’, ‘e’>; – объявлен сам массив. Конечный символ не записанявно, его прибавит сам компилятор.
    • char myStr3[6] = <‘a’, b, ‘c’, ‘d’, ‘e’’/0’>; – объявлен массив, при этом в конце прописан признак окончания строки.
    • char myStr4 [ ] = “abcde”; – инициализация массива строковой постоянной. Размер и завершающий символ добавляются автоматически компилятором.
    • char myStr5 [6 ] = “abcde”; – инициализация массива с точным указанием его размера.
    • char myStr 6[30 ] = “abcde”; – аналогично, но размер указан больше для возможности использования строк большей длины.

    Еще раз напомним, что в типе данных char строковые константы нужно записывать в двойные кавычки «Abcde», а одиночные символы – в одинарные ‘a’.

    Конвертировать строку в массив сhar array можно при помощи следующего кода:

    String stringVar = “111”;

    Можно сделать обратное преобразование – char to string.

    char[] chArray = “start”;

    Пример преобразования String to const char*. Указание звездочкой char*означает, что это массив указателей.

    String stringVar=string (`start);

    Char charVar[ sizeof [stringVar)];

    Заключение о String и ардуино

    В этой статье мы рассмотрели основные вопросы использования String для работы со строками arduino. Как показывают примеры, ничего страшного и сложного в этом классе нет. Более того, зачастую мы можем даже не догадываться, что работаем с классом String: мы просто создаем переменную нужного типа, присваиваем ей строку в двойных кавычках. Создав строку, мы используем все возможности библиотеки String: можем без проблем модифицировать строку, объединять строки, преобразовывать string в int и обратно, а также делать множество других операций с помощью методов класса.

    В ситуациях, когда скетч большой и перед нами встает дефицит памяти, использовать String нужно осторожно, по возможности заменяя на char*. Впрочем, в большинстве первых проектов начинающего ардуинщика таких ситуаций не много, поэтому рекомендуем использовать String без опаски – это предотвратит появление ошибок адресной арифметики, возникающих при работе с массивами char.

    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() оставляем пустым.

    Если посылаем строку, то обрамляем её кавычками. Если число, то кавычки не используем. Изменим функцию setup().

    Можно заменить строки и числа на переменные. Перепишем пример.

    Немного о числах. При работе с дробными числами, можно указать число знаков после запятой.

    Работа с массивами и строками

    Разберём пример отправки строк в случайном порядке. Любая строка уже является массивом символов. Поэтому вместо типа String, можно использовать массив char[]. Для примера создадим массив из четырёх имён и будем выводить их в случайном порядке через разные промежутки времени, используя функцию random().

    Приём данных

    Выводить данные в порт просто. А вот принимать данные с компьютера и других источников сложнее. При отправлении данных, они складываются в буфер, ожидая, когда плата их прочитает. Объём буфера составляет 64 байта. Чтобы постоянно не читать пустой буфер, есть специальная функция проверки буфера Serial.available(). Она возвращает число байт, которые лежат в буфере. Обычно в коде создают условие проверки — если в буфере больше 0 байт, то выполняем какие-то команды.

    Для демонстрации создадим странный пример — создадим переменную, присвоим ей данные через Serial.read() и попросим её прислать полученные данные через Serial.print(). Получится круговорот данных или эхо.

    Проверяем на числах. Отправляем число 9, а получаем 57. Если вы получаете две строки с числами 57 и 10, то в нижней части окна выберите настройку No line ending вместо Newline.

    Попробуем также отправить букву. Опять вместо t возвращается 116. Ерунда какая-то. Всё просто, функция read() работает с символьными значениями и мы видим код символа из стандартной таблицы символов ASCII.

    Чтобы решить проблему, нужно изменить тип данных на char.

    Вроде проблема решена. Мы можем принимать отдельные цифры и буквы. Но буквы только английские, а числа только однозначные.

    Если мы планируем работать только с однозначными числами, то можно написать такой код.

    Решение какое-то половинчатое. А как быть с большими числами или словами?

    Если отправить двузначное число 23, то ответ разбивается на части — 2 и 3. Получается, что переменная получит последнее число 3 (промежуточные значения перезаписываются). Чтобы обработать всё число, нужно использовать метод parseInt().

    Теперь вы можете вводить любые числа. Но, наверное, вы заметите теперь небольшую задержку в ответах. Метод внутри себя перемалывает данные. Кстати, вы можете использовать и обычные символы. Если набор символов состоит только из букв, то вернётся 0. Если будут попадаться и цифры, то будут возвращаться цифры. Попробуйте комбинировать различные сочетания цифр и букв, чтобы понять, как будут обрабатываться данные.

    Управление светодиодом с клавиатуры

    Напишем пример управления встроенным светодиодом с клавиатуры. Если нажата клавиша 1, то светодиод должен загореться, при нажатии клавиши выключим светодиод.

    Часть кода нам уже знакома — мы используем встроенный светодиод под номером 13.

    Сигнал от компьютера поступает в виде байта. Создаём новую переменную incomingByte для этих целей.

    Последовательный порт включается командой begin() с указанием скорости.

    Если с компьютера поступает сигнал, то функция available() вернёт количество байт, доступное для чтения. Таким образом, мы просто убеждаемся, что какой-то сигнал пришёл (больше нуля).

    После первой проверки мы проверяем введённый символ, который может быть представлен и как байт. Если символ равен единице, то включаем светодиод, как мы делали раньше. Если символ равен 0, то выключаем.

    Как это выглядит на практике. Заливаем скетч и запускаем Serial Monitor (Ctrl+Shift+M). В окне Serial Monitor наверху есть текстовое поле. Вводим в него числа 1 или 0 и нажимаем кнопку Send. Можно также нажать клавишу Enter для быстрого ввода.

    Для общего развития в скетч добавлены также две строчки кода, определяющие код нажатой клавиши. Таким образом вы можете узнать код для клавиш 0 и 1. Вы также можете нажимать и на другие клавиши, они не повлияют на светодиод, но вы увидите коды клавиш.

    Чуть более сложный пример, когда строка задана в виде массива и символы выводятся по очереди.

    Функция Serial.end() закрывает последовательное соединение, порты RX и TX освобождаются и могут быть использованы для ввода/вывода.

    В различных уроках вы будете принимать сигналы от платы Arduino. Это полезно, например, для отладки приложения, когда вы выводите сообщения и по ним ориентируетесь, какая часть программа работает, а какая — нет. Способность общения между Arduino и компьютером очень важна. Вы можете принимать сигналы не только в Arduino IDE, но и в других приложениях на компьютере. Например, в связке с Arduino часто используют приложение Processing, в котором рисуют графики поступаемых сигналов.

    Если вы больше не нуждаетесь в получении данных, то закрывайте окно Serial Monitor.

    Также существует библиотека SoftwareSerial. Она позволяет осуществить последовательную передачу данных через другие цифровые контакты Arduino.

    Другие варианты

    Чтение данных из последовательного порта возможно другими способами. Ищите расширения, например, Arduino Chrome Serial Monitor. На видео можно посмотреть, как создать расширение самостоятельно.

    На C# также можно написать приложение, которое будет уметь считывать данные.

    Processing также умеет работать с последовательным портом.

    Дополнительное чтение

    ASCIITable — распечатываем таблицу символов ASCII в разных форматах

    Работа с последовательным портом UART (serial) на Arduino

    Последовательный интерфейс (serial) предназначен передачи данных через универсальный асинхронный порт UART. Порт UART очень часто используется для передачи данных с Ардуино на компьютер, и обратно, а также для связи нескольких плат ардуин между собой.

    Для многопортовых DUE/MEGA см. здесь.

    Основные функций для работы с последовательным портом (Serial)

    Serial.begin(rate) — Открывает последовательный порт и задаёт скорость для последовательной передачи данных. Типичная скорость обмена для компьютерной коммуникации — 9600.

    Очевидно, когда задействован последовательный порт, выводы 0 (RX) и 1 (TX) не могут использоваться для других целей.

    Serial.println(data) — Передаёт данные в последовательный порт, сопровождая автоматическим возвратом каретки и переходом на новую строку.

    Serial.print(data) — тоже самое без возврата каретки и перехода на новую строку.

    Serial.begin(скорость_передачи); — Инициализация порта. Задает скорость передачи в битах в секунду. Нормированные скорости: 300, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, или 115200.

    count = Serial.available(); — Принимаемые по последовательному порту байты попадают в буфер микроконтроллера, откуда Ваша программа может их считать. Функция возвращает количество накопленных в буфере байт. Последовательный буфер может хранить до 128 байт.

    char = Serial.read(); — Считывает следующий байт из буфера последовательного порта. возвращает -1 если нет входящих данных

    Serial.flush(); — Ожидает окончания передачи исходящих данных (до версии Arduino 1.0 функция очищала буфер последовательного соединения)..

    Разные варианты функции print:

    Serial.print(b, DEC); — выводит ASCII-строку — десятичное представление числа b.

    Serial.print(b, BYTE) — выводит младший байт числа b.

    (аналогично HEX, OCT, BIN).

    Serial.print(str) // если str — строка или массив символов, побайтно передает str на COM-порт.

    Serial.println(); — отличие заключается в том, что после данных дополнительно выводятся два символа – символ возврата каретки (ASCII 13, или ‘r’) и символ новой линии (ASCII 10, или ‘n’).

    Функция write:

    Serial.write(uint8_t c); — Записывает данные в последовательный порт. Данные посылаются как байт или последовательность байт.

    Serial.write(val); // где val — переменная для передачи, как единственный байт

    Serial.write(str); // где str — строка для передачи, как последовательность байт

    Serial.write(buf, len); // где buf — массив для передачи, как последовательность байт; len — длина массива.

    Пример 1. Передача данных по Serial-порту с Arduino на компьютер

    Инициализация порта со скоростью 9600 бот и передача данных (от Arduino на внешние устройства, например на компьютер):

    Пример 2. Передача данных по Serial-порту с компьютера на Arduino

    serialEvent() — функция вызывается автоматически, когда поступают данные.

    Serial.setTimeout() — задает максимальное время (в мс) для работы Serial.readBytesUntil();

    Возможные проблемы

    1) auto-reboot DTR : возможна автоперезагрузка МК при передаче на него данных по serial-пору. Чтобы отключить это, надо поставить конденсатор 10мкФ между RES и GND. Я ставил электролитический кондер (естественно, + на RES).

    Как соединить две ардуины по UART (serial) порту

    Схема соединения двух ардуин:

    Длина провода и скорость: RS-232 (англ. Recommended Standard 232) — стандарт физического уровня для асинхронного интерфейса (UART).

    Расстояние связи по RS232 максимум 15 метров.

    Но всё зависит от скорости.

    Работа Arduino MEGA/DUE с несколькими последовательными serial портами

    Многопортовые ардуино.

    Как вы уже заметили, на ардуиновских платах Mega и Due имеется по 4 последовательных порта, а именно:

    Serial — выводы 0 (RX) and 1 (TX);

    Serial1 — выводы 19 (RX) and 18 (TX);

    Serial2 — выводы 17 (RX) and 16 (TX);

    Serial3 — выводы 15 (RX) and 14 (TX).

    Естественно, что на Due используется напряжение 3.3 В (на MEGA как обычно 5 В).

    Как с ними работать?

    Здесь синим цветом выделены собственно имена объектов ( Serial , Serial1 , Serial2 , Serial3 ), которые используются в коде программы для работы с их методами. Всё просто! Например,

    Пример вывода на дисплей LCD1602 через последовательный порт UART Arduino из-под Linux средствами языка Python

    Короче говоря, есть комп с линуксом, к которому подключена Arduino через USB, а к арудине подключен дисплей LCD1602, и мы хотим на него выводить инфу.

    Сначала привожу полный код программы для Arduino UNO, к которой подключен дисплей LCD1602:

    Я сделал здесь решетку ‘#’ в качестве символа завершения передачи пакета данных. Как только в потоке данных встречается символ #, данные выводятся на дисплей, и буфер обнуляется, при этом сам символ ‘#’ не выводится. Конечно, можно было бы использовать ‘n’ или др.

    Далее мы напишем скрипт на Python, который будет выводить инфу на дисплей. Я выбрал Python, потому что это прикладной язык и он лучше всего подходит для подобных задач. С такими языками как C++/C# и т.п. больше возни с библиотеками, а здесь всё просто, особенно если это дело происходит под линуксом.

    Первым делом устанавливаем необходимые библиотеки (для 2-ой или 3-ей версии python)

    $sudo apt-get install python-serial

    $sudo apt-get install python3-serial

    Далее в интерпретаторе python пишем:

    Здесь ардуина у меня подключена к порту /dev/ttyUSB0 — это я узнавал из Arduino IDE. Обычно она всегда на этом порту сидит, если других устройств на последовательный порт не подключено.

    Как вы уже догадались, и в первой, и во второй программы должна быть указано одинаковая скорость в бодах. В моем случае это 9600 — стандартная, хотя и маленькая скрость. Может быть и больше (читай выше).

    Объект string в arduino и команды через последовательный порт

    Дельта принтеры крайне требовательны к точности изготовления комплектующих (геометрия рамы, длины диагоналей, люфтам соединения диагоналей, эффектора и кареток) и всей геометрии принтера. Так же, если концевые выключатели (EndStop) расположены на разной высоте (или разный момент срабатывания в случае контактных концевиков), то высота по каждой из осей оказывается разная и мы получаем наклонную плоскость не совпадающая с плоскостью рабочего столика(стекла). Данные неточности могут быть исправлены либо механически (путем регулировки концевых выключателей по высоте), либо программно. Мы используем программный способ калибровки.
    Далее будут рассмотрены основные настройки дельта принтера.
    Для управления и настройки принтера мы используем программу Pronterface.
    Калибровка принтера делится на три этапа:

    1 Этап. Корректируем плоскость по трем точкам

    Выставление в одну плоскость трех точек — A, B, C (расположенных рядом с тремя направляющими). По сути необходимо уточнить высоту от плоскости до концевых выключателей для каждой из осей.
    Большинство (если не все) платы для управления трехмерным принтером (В нашем случае RAMPS 1.4) работают в декартовой системе координат, другими словами есть привод на оси: X, Y, Z.
    В дельта принтере необходимо перейти от декартовых координат к полярным. Поэтому условимся, что подключенные к двигателям X, Y, Z соответствует осям A, B, C.(Против часовой стрелки начиная с любого двигателя, в нашем случае смотря на логотип слева — X-A, справа Y-B, дальний Z-C) Далее при слайсинге, печати и управлении принтером в ручном режиме, мы будем оперировать классической декартовой системой координат, электроника принтера сама будет пересчитывать данные в нужную ей систему. Это условность нам необходима для понятия принципа работы и непосредственной калибровки принтера.

    Точки, по которым мы будем производить калибровку назовем аналогично (A, B, C) и позиция этих точек равна A= X-52 Y-30; B= X+52 Y-30; C= X0 Y60.

    Алгоритм настройки:

    1. Подключаемся к принтеру. (В случае “крагозяб” в командной строке, необходимо сменить скорость COM порта. В нашем случае с 115200 на 250000 и переподключится)

      После чего мы увидим все настройки принтера.
    2. Обнуляем высоты осей X, Y, Z командой M666 x0 y0 z0.
      И сохраняем изменения командой M500. После каждого изменения настроек необходимо нажать home (или команда g28), для того что бы принтер знал откуда брать отсчет.
    3. Калибровка принтера производится “на горячую”, то есть должен быть включен подогрев стола (если имеется) и нагрев печатающей головки (HotEnd’а) (Стол 60град., сопло 185 град.) Так же нам понадобится щуп, желательно металлический, известных размеров. Для этих задач вполне подойдет шестигранный ключ (самый большой, в нашем случае 8мм, он предоставляется в комплекте с принтерами Prizm Pro и Prizm Mini)
    4. Опускаем печатающую головку на высоту (условно) 9мм (от стола, так, что бы сопло еле касалось нашего щупа, т.к. высота пока что не точно выставлена.) Команда: G1 Z9.
    5. Теперь приступаем непосредственно к настройке наших трех точек.
      Для удобства можно вместо g- команд создать в Pronterface четыре кнопки, для перемещения печатающей головки в точки A, B, C, 0-ноль.

  • Последовательно перемещаясь между тремя точками (созданными ранее кнопками или командами) выясняем какая из них находится ниже всего (визуально) и принимает эту ось за нулевую, относительно нее мы будем менять высоту остальных двух точек.
  • Предположим, что точка A у нас ниже остальных. Перемещаем головку в точку B(Y) и клавишами управления высотой в Pronterface опускаем сопло до касания с нашим щупом, считая величину, на которую мы опустили сопло (в лоб считаем количество нажатий на кнопки +1 и +0.1)
    Далее командой меняем параметры высоты оси Y: M666 Y <посчитанная величина>
    M666 Y0.75
    M500
    G28
  • Ту же операцию проделываем с оставшимися осями. После чего следует опять проверить высоту всех точек, может получится, что разброс высот после первой калибровки уменьшится, но высота все равно будет отличатся, при этом самая низкая точка может изменится. В этом случае повторяем пункты 6-7.
  • 2 Этап. Исправляем линзу

    После того как мы выставили три точки в одну плоскость необходимо произвести коррекцию высоты центральной точки. Из за особенности механики дельты при перемещении печатающей головки между крайними точками в центре она может пройти либо ниже либо выше нашей плоскости, тем самым мы получаем не плоскость а линзу, либо вогнутую либо выпуклую.

    Корректируется этот параметр т.н. дельта радиусом, который подбирается экспериментально.

    Калибровка:

    1. Отправляем головку на высоту щупа в любую из трех точек стола. Например G1 Z9 X-52 Y-30
    2. Сравниваем высоту центральной точки и высоту точек A,B,C. (Если высота точек A, B, C разная, необходимо вернутся к предыдущей калибровки.)
    3. Если высота центральной точки больше остальных, то линза выпуклая и необходимо увеличить значение дельта радиуса. Увеличивать или уменьшать желательно с шагом +-0,2мм, при необходимости уменьшить или увеличить шаг в зависимости от характера и величины искривления (подбирается экспериментально)
    4. Команды:
      G666 R67,7
      M500
      G28
    5. Подгоняем дельта радиус пока наша плоскость не выровняется
    3 Этап. Находим истинную высоту от сопла до столика

    Третьим этапом мы подгоняем высоту печати (от сопла до нижней плоскости — столика) Так как мы считали, что общая высота заведомо не правильная, необходимо ее откорректировать, после всех настроек высот осей. Можно пойти двумя путями решения данной проблемы:
    1 Способ:
    Подогнав вручную наше сопло под щуп, так что бы оно свободно под ним проходило, но при этом не было ощутимого люфта,

    • Командой M114 выводим на экран значение фактической высоты нашего HotEnd’а
    • Командой M666 L получаем полное значение высоты (Параметр H)
    • После чего вычитаем из полной высоты фактическую высоту.
    • Получившееся значение вычитаем из высоты щупа.

    Таким образом мы получаем величину недохода сопла до нижней плоскости, которое необходимо прибавить к полному значению высоты и и записать в память принтера командами:
    G666 H 235.2
    M500
    G28

    2 Способ:
    Второй способ прост как валенок. С “потолка”, “на глаз” прибавляем значение высоты (после каждого изменение не забываем “уходить” в home), добиваясь необходимого значения высоты, но есть шанс переборщить со значениями и ваше сопло с хрустом шмякнется об стекло.

    Как сделать авто калибровку для вашего принтера и что при этом авто калибрует принтер вы узнаете из следующих статей.