Как писать скетчи для ардуино
Перейти к содержимому

Как писать скетчи для ардуино

  • автор:

«Пишем свой первый скетч» Урок № 1

Урок № 1
Это продолжение цикла уроков про Ардуино для начинающих.

Предыдущие уроки можно посмотреть здесь.
Урок № 0 — «Введение в программирование Ардуино»
Урок № 0.5 — «Продолжение вводного урока.»Первое подключение Ардуино к компьютеру.»

В этом уроке, видео на который можно посмотреть на канале YOUTUBE
рассказано как подключить светодиод, рассчитать значение токоограничивающего резистора, про закон Ома, и многое другое.
Что такое функция void setup() и void loop(), вообщем посмотрите, не пожалеете.

Для этого урока, для основной его части потребуются.

Ардуино Нано v 3.0
Макетная плата (беспаечная) Breadboard
Провода 5 шт.
Резисторы 220 Ом 3 шт.
Светодиоды 3 шт.

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

Для дополнительного занятия потребуется ещё

Этот урок надо использовать вместе с видео.

Скетчи основной части урока.
Мигание 1 светодиодом. Скачать.
Мигание 3 светодиодами. Скачать.

Теперь, как и обещал некоторые интересности.

Вот наш код из первого скетча

void setup() pinMode(LED, OUTPUT); // УСТАНАВЛИВАЕМ ЦИФРОВОЙ ПИН 13 НА ВЫХОД
>

void loop() digitalWrite(LED, HIGH); // ЗАЖИГАЕМ СВЕТОДИОД
delay(1000); // ЖДЁМ 1 СЕКУНДУ
digitalWrite(LED, LOW); // ГАСИМ СВЕТОДИОД
delay(1000); // ЖДЁМ 1 СЕКУНДУ
>

Теперь как и обещал сократим код в функции loop()
И вместо 4 строчек написать всего 2
void loop() digitalWrite(LED, !digitalRead(LED)); // если светодиод был выключен – включаем и наоборот
delay(1000); // ждем 1 секунду
>
Что делают эти строчки.
Мы получаем текущее значение светодиода digitalRead(LED) и !digitalRead(LED) переворачиваем его(инвертируем).
Если светодиод горел, то гасим его
Если светодиод не горел, то зажигаем его.

с Логическими операторами мы познакомимся в следующих уроках

&& (логическое И) Истина, если оба операнда истина.

|| (логическое ИЛИ) Истина, если хотя бы один операнд истина.

! (логическое отрицание) Истина, если операнд Ложь, и наоборот.

Замена delay()

Эта функция служит для остановки микроконтроллера.
Как я и говорил в видео, функцию delay() надо применять очень осторожно.
Означает остановить выполнение всех программ (ну почти всех) и ждать пока не пройдёт время, указанное в скобках. Оно указывается в миллисекундах (1000 миллисекунд = 1 секунде) и пока это время не закончится ARDUINO будет бездействовать.
Есть ещё прерывания, но это мы будем изучать в следующих уроках.

Правильнее будет использовать функцию millis();
Она возвращает количество миллисекунд с момента начала выполнения программы.
Количество миллисекунд в millis() ограничено 50 днями. При наступлении этого времени счётчик сбросится 0.

Вот что у нас теперь получилось
#define LED 13
#define STOP 1000 // 1000 миллисекунд — это 1 секунды
unsigned long prev_ms = 0; // здесь будет храниться время последнего изменения состояния светодиода

void setup() pinMode(LED, OUTPUT); // УСТАНАВЛИВАЕМ ЦИФРОВОЙ ПИН 13 НА ВЫХОД
>

void loop()
/*
проверяем – настало ли время изменить состояние светодиода
для этого считываем значение светодиода digitalRead(LED),
а затем сверяем полученный интервал с нужным интервалом
*/
if (millis() — prev_ms > STOP) <
// текущее время –(минус) время последнего изменения. Если он больше 1000(1 сек.) то условие верно
digitalWrite(LED, !digitalRead(LED)); // если светодиод был выключен – включаем и наоборот
prev_ms = millis();
>
/*
Здесь могут быть другие команды, которые нужно выполнить и неважно, что будет делать микроконтроллер в данное время,Если настало время STOP, он прервётся, выполнит задачу, и начнёт с того же места, где прервался.
*/
>

Теперь переходим к самому интересному.
Дополнительной части урока.
Напишу её сегодня-завтра. Хочу, чтобы на выходные вам было чем заняться.

Удачи вам в освоении этого непростого, но интересного дела.
Изучив эти уроки, вы освоите новую, интересную профессию – Программист. Освоив её, вы можете быть спокойны за свою будущее, ведь по статистике, программист самый востребованный и высокооплачиваемый специалист в наше время.
Смотрите новые уроки. Подписывайтесь на канал. И удачи.

Многозадачность в Arduino

Вот и закончился базовый курс уроков программирования Arduino. Мы с вами изучили самые базовые понятия, вспомнили (или изучили) часть школьной программы по информатике, изучили большую часть синтаксиса и инструментов языка C++ и вроде бы весь набор Ардуино-функций, который предлагает нам платформа. Теперь перед нами чистый лист блокнота Arduino IDE и желание творить и программировать. Давайте попробуем!

Многозадачность

Микроконтроллер может выполнять только одну задачу в один момент времени, так как у него одно вычислительное ядро (у некоторых больше, например ESP32), поэтому реальной “многозадачности” нет и быть не может. Но за счёт большой скорости выполнения ядро может выполнять задачи по очереди, и для человека это будет казаться многозадачностью: ведь что для нас “раз Миссисипи” (одна секунда), для микроконтроллера – десятки миллионов операций!

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

Суперцикл с костылями

Суперцикл – главный цикл программы, который выполняется сверху вниз и начинает с самого начала, когда доходит до конца. В Arduino IDE нашим суперциклом является loop() . В главном цикле мы можем опрашивать датчики, управлять внешними устройствами, выводить данные на дисплеи, производить вычисления и всё такое, но в любом случае эти действия будут происходить друг за другом, последовательно. Большинство действий не требуют постоянного выполнения: например нам не нужно миллион раз в секунду обновлять дисплей, опрашивать кнопки или датчики – достаточно делать это несколько раз в секунду при помощи программного таймера (об этом ниже). Некоторые действия требуют относительно большого времени работы процессора, например сложные вычисления или отправка изображения на дисплей, и за это время мы можем пропустить какое-то важное событие (импульс с датчика оборотов, поворот энкодера, входящие данные по интерфейсу связи). Как успеть их обработать?

Помимо основного цикла у нас есть прерывания, которые при наступлении определённого события позволяют остановить выполнение кода в основном цикле и перейти к выполнению кода в обработчике прерывания, а после его завершения вернуться к основному циклу и продолжить работу. Некоторые задачи можно решить на одних лишь прерываниях, не написав ни одной строчки в цикл loop() !

  • Мы с вами изучали внешние аппаратные прерывания, позволяющие прерваться по внешнему сигналу (нажатие кнопки, поворот энкодера, импульс с тахометра).
  • Также у микроконтроллера есть внутренние прерывания, которые вызываются его периферией. Таких прерываний может быть несколько десятков! Одним из таких прерываний является прерывание таймера: по настроенному периоду программа будет прерываться и выполнять указанный код.
  • В реализации Arduino один из таймеров настраивается на счёт реального времени, благодаря чему у нас работают функции millis() и micros() (читай урок про функции времени). Именно эти функции являются готовым инструментом для тайм-менеджмента нашего кода и позволяют создавать работу “по расписанию” (об этом ниже).
  • Ещё один пример – прерывания UART. Вы не задумывались, где и как микроконтроллер принимает данные из монитора порта? Мы ведь не опрашиваем его вручную. В реализации Arduino входящие по UART данные вызывают прерывание, в котором складываются в буфер, и уже из этого буфера мы читаем их в программе в любой удобный нам момент.

Рассмотрим некоторые варианты реализации многозадачности в Arduino.

Многозадачность с yield()

В уроке про функции времени мы коснулись функции yield() , которая позволяет выполнять свой код внутри задержек delay() . Данный костыль позволяет очень быстро реализовать “параллельное” выполнение двух задач: одной по задержке, а второй – постоянно. В том уроке мы рассмотрели пример, в котором мигает светодиод и опрашивается кнопка:

void setup() < pinMode(13, OUTPUT); >void loop() < digitalWrite(13, 1); delay(1000); digitalWrite(13, 0); delay(1000); >void yield() < // а тут можно опрашивать кнопку // и не пропустить нажатия из за delay! >

Таким же образом можно опрашивать энкодер или другие железки, которые требуют максимально частого опроса. Не менее жизненным будет пример со сценарием движения шагового мотора или плавного движения сервопривода, которые требуют частого вызова “функций движения”.

Рассмотрим абстрактный пример движения мотора по нескольким заданным точкам, функция вращения мотора должна вызываться как можно чаще (так сделано почти во всех библиотеках для шаговых моторов):

void setup() <> void loop() < // задать целевой угол №1 delay(1000); // задать целевой угол №2 delay(120); // задать целевой угол №3 delay(2000); // задать целевой угол №4 delay(250); // задать целевой угол №5 delay(600); >void yield() < // вращать мотор >

Таким образом мы быстро и просто расписали “траекторию” движения для шагового мотора по времени, не используя какие-то таймеры и библиотеки. Для более сложных программ, например с движением двух моторов, такой фокус уже может не пройти и проще работать с таймером.

Многозадачность с millis()

Большинство примеров к различным модулям/датчикам используют задержку delay() в качестве “торможения” программы, например для вывода данных с датчика в последовательный порт. Именно такие примеры портят восприятие новичка и он тоже начинает использовать задержки. А на задержках далеко не уедешь!

При помощи функций времени millis() или micros() можно организовать программный таймер, по которому и выполнять нужные действия. С хема такая:

  • Заводим переменную для таймера типа unsigned long ( uint32_t ) – именно этот тип возвращает millis() .
  • Ищем разницу между текущим временем работы программы и переменной таймера.
  • Если разница больше необходимого периода – выполняем нужный код и сбрасываем таймер.

Все рассмотренные ниже конструкции могут работать также с micros() для создания микросекундных таймеров

Реализация классического “таймера на millis()” выглядит так:

#define MY_PERIOD 500 // период в мс uint32_t tmr1; // переменная таймера void setup() <> void loop() < if (millis() - tmr1 >= MY_PERIOD) < // ищем разницу tmr1 = millis(); // сброс таймера // выполнить действие >>
  • Данная конструкция “уходит” с периода, если в коде есть задержки и прочие блокирующие участки, во время выполнения которых millis() успевает увеличиться на время, большее чем период таймера. Это может быть критично например для счёта времени и других похожих ситуаций, когда период срабатывания таймера не должен смещаться.
  • В то же время, если заблокировать выполнение кода на время, большее чем один период – алгоритм просто скорректирует эту разницу, так как мы сбрасываем его актуальным значением millis() .

Несколько таймеров

Вернёмся к вопросу многозадачности: хотим выполнять одно действие два раза в секунду, второе – три, и третье – 10. Нам понадобится 3 переменные таймера и 3 конструкции с условием:

// переменные таймеров uint32_t myTimer1, myTimer2, myTimer3; void setup() <> void loop() < if (millis() - myTimer1 >= 500) < // таймер на 500 мс (2 раза в сек) myTimer1 = millis(); // сброс таймера // выполнить действие 1 >if (millis() - myTimer2 >= 333) < // таймер на 333 мс (3 раза в сек) myTimer2 = millis(); // сброс таймера // выполнить действие 2 >if (millis() - myTimer3 >= 100) < // таймер на 100 мс (10 раз в сек) myTimer3 = millis(); // сброс таймера // выполнить действие 3 >>

И вот так мы можем например 10 раз в секунду опросить датчик, фильтровать значения, и два раза в секунду выводить показания на дисплей. И три раза в секунду мигать лампочкой. Красота!

Другие варианты реализации

Рассмотрим ещё несколько вариантов реализации и сброса таймера.

Облегчённый таймер

В классическом варианте таймера мы создавали отдельную 4-х байтную переменную под каждый таймер. Весьма расточительно! Не всегда нужен таймер на период в несколько миллиардов миллисекунд, чаще всего это несколько секунд.

Можно использовать более лёгкий тип данных (2 и 1 байта) и взять из результата millis() только нужную “младшую” часть, присвоив к соответствующему типу данных. “Лишняя” старшая часть числа автоматически отсечётся.

Реализация 2-х байтного таймера на максимальный период 65’535 мс (чуть больше минуты):

uint16_t tmr; void loop() < uint16_t ms = millis(); if (ms - tmr >1000) < tmr = ms; // ваш код >>

Аналогично можно сделать для 1-байтной переменной, максимальный период 255 мс:

uint8_t tmr; void loop() < uint8_t ms = millis(); if ((uint8_t)(ms - tmr) >100) < tmr = ms; // ваш код >>

Примечание: несмотря на явно указанный тип данных uint8_t , компилятор считает разность в скобках как у знаковых чисел ( int8_t ), что приведёт к неправильной работе всей конструкции. Поэтому результат разности принудительно преобразуем к uint8_t .

Второй вариант сброса таймера

Второй вариант сброса таймера выглядит так:

#define MY_PERIOD 500 // период в мс uint32_t tmr1; // переменная таймера void setup() <> void loop() < if (millis() - tmr1 >= MY_PERIOD) < // ищем разницу tmr1 += MY_PERIOD; // сброс таймера // выполнить действие >>
  • Эта конструкция жёстко отрабатывает период, то есть не “уходит” со временем, если в коде присутствует задержка, потому что время следующего срабатывания всегда кратно периоду.
  • Минусом здесь является то, что если таймер пропустит период – он “сработает” несколько раз подряд при следующей проверке!

Третий вариант сброса таймера

Доработаем второй тип: можно посчитать, на сколько периодов нужно “обновить” переменную таймера. Например так:

#define MY_PERIOD 500 // период в мс uint32_t tmr1; // переменная таймера void setup() <> void loop() < uint32_t timeLeft = millis() - tmr1; if (timeLeft >= MY_PERIOD) < tmr1 += MY_PERIOD * (timeLeft / MY_PERIOD); // выполнить действие >>
  • Здесь я заранее считаю время, прошедшее после последнего срабатывания, и записываю в переменную timeLeft , потому что оно нам ещё понадобится. К переменной таймера мы прибавляем период, умноженный на количество переполнений. Если вызов таймера не был пропущен – произойдёт умножение на 1.
  • Целочисленное деление (timeLeft / period) позволяет получить целое количество переполнений, поэтому скобки стоят именно так.
  • Данная конструкция таймера позволяет жёстко соблюдать период выполнения и не боится пропущенных вызовов, но требует дополнительных вычислений, в том числе “дорогое” деление. Деление будет выполняться в разы быстрее, если период таймера кратен степени двойки (2, 4, 16, 32, 64…). Почему? Читай урок по оптимизации кода.

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

#define MY_PERIOD 500 // период в мс uint32_t tmr1; // переменная таймера void setup() <> void loop() < uint32_t timeLeft = millis() - tmr1; if (timeLeft >= MY_PERIOD) < tmr1 += ((timeLeft >= 2 * MY_PERIOD) ? (timeLeft / MY_PERIOD) : 1) * MY_PERIOD; // выполнить действие > >

Таким образом “дорогое” деление заменилось умножением на 2, которое компилятор ещё и сам оптимизирует. Если остаток времени больше двух периодов – то прибавка будет посчитана как количество пропущенных периодов. Возможно это и будет самой лучшей конструкцией “не уходящего” таймера.

Четвёртый вариант сброса таймера

Если операция деления во третьем типе сброса таймера (см. выше) для вас критична – можно избежать пропуска таймера и лишних вызовов следующим образом:

#define PERIOD 500 uint32_t timer = 0; void loop() < if (millis() - timer >= PERIOD) < // ваше действие do < timer += PERIOD; if (timer < PERIOD) break; // переполнение uint32_t >while (timer < millis() - PERIOD); // защита от пропуска шага >>

Возможно не стоит пользоваться таким нагромождением, но я просто оставлю это здесь =)

Таймер на остатке от деления

В интернете часто можно встретить вот такую конструкцию: условие выполняется, когда остаток от деления millis() на период равен нулю.

if (millis() % period == 0) < // ваше действие >

Казалось бы, очень крутой и простой алгоритм… но использовать его нельзя по целому ряду причин:

  • Операция “остаток от деления” довольно тяжёлая и медленная, размещение нескольких таких “таймеров” в основном цикле программы сильно увеличит время его выполнения и замедлит работу всей программы в целом!
  • В реальной программе может создаться задержка продолжительностью дольше 1 мс и существует довольно высокий риск пропуска срабатывания такого “таймера”!
  • В то же время условие срабатывания таймера будет верно целую миллисекунду и действие может выполниться несколько раз подряд, что недопустимо в большинстве случаев!

Класс таймера

Можно обернуть конструкцию таймера в класс (урок про классы) для более удобного использования:

class Timer < public: Timer () <>Timer (uint32_t nprd) < start(nprd); >void start(uint32_t nprd) < prd = nprd; start(); >void start() < tmr = millis(); if (!tmr) tmr = 1; >void stop() < tmr = 0; >bool ready() < if (tmr && millis() - tmr >= prd) < start(); return 1; >return 0; > private: uint32_t tmr = 0, prd = 0; >;

Добавляем класс в начало скетча или отдельный файл и можем создавать таймеры. Если при инициализации указать период – таймер сразу запустится. Период можно изменить во время работы при помощи start(период) , что также перезапустит таймер. Вызов start() без аргументов перезапустит таймер на тот же период. При вызове stop() таймер остановится. В основном цикле опрашиваем функцию ready() , когда таймер сработает – она вернёт true .

Timer tmr1(1000); Timer tmr2; void setup() < Serial.begin(9600); tmr2.start(2000); >void loop()

Здесь создано два таймера, первый на 1 секунду, второй на 2 секунды (для примера я задал период через setPeriod() ).

Ещё один вариант реализации таймера

Очень часто в Интернете можно встретить “таймер на миллис” такого вида:

if (millis() >= tmr) < tmr = millis() + prd; // действие по таймеру >

Визуально эта конструкция “легче” классической, так как экономит одно вычитание в цикле. Это действительно так, экономия составляет ровно 1 такт процессора (на AVR): классическая конструкция выполняется условно 18 тактов, а эта – 17. Один такт это 0.0625 микросекунды, что просто незначительно.

Также данная конструкция некорректно переходит через переполнение millis() : когда переменная таймера переполнит uint32_t , миллис будет больше неё вплоть до своего переполнения и условие будет верно всё это время. Не используйте данную конструкцию в проектах, которые могут работать больше 50 суток без перезагрузки.

Таймер с переключением периода

Таймер, который после срабатывания переключает период на другой, например включаем реле на 10 секунд каждые 60 минут

uint32_t tmr; bool flag; #define period1 10*1000L #define period2 60*60*1000L void setup() < >void loop() < if (millis() - tmr >= (flag ? period1 : period2)) < tmr = millis(); flag = !flag; // ваш код >>

Замкнутый цикл, выполняемый заданное время

По сути это аналог задержки, но мы можем выполнять свой код внутри неё:

uint32_t now = millis(); while (millis () - now < 5000) < // тут в течение 5000 миллисекунд вертится код // удобно использовать для всяких калибровок >

Однократное срабатывание

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

if (flag && millis() - timer >= 1000) < flag = 0; // остановить // выполнить действие >

Для перезапуска такого таймера нужно поднять флаг flag = 1 , а также сбросить таймер timer = millis() .

Для экономии памяти этим флагом может быть и переменная периода, например:

if (period && millis() - timer >= period) < period = 0; // остановить // выполнить действие >

Для перезапуска такого таймера нужно задать период period ненулевым числом и сбросить таймер timer = millis() .

Переполнение

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

Примеры

Начнём с простого: классический blink:

void setup() < pinMode(13, OUTPUT); // пин 13 как выход >void loop() < digitalWrite(13, HIGH); // включить delay(1000); // ждать digitalWrite(13, LOW); // выключить delay(1000); // ждать >

Программа полностью останавливается на команде delay() , ждёт указанное время, а затем продолжает выполнение. Чем это плохо? (А вы ещё спрашиваете?) Первым делом внесём такую оптимизацию: сократим код вдвое и избавимся от одной задержки, используя флаг:

boolean LEDflag = false; void setup() < pinMode(13, OUTPUT); >void loop() < digitalWrite(13, LEDflag); // вкл/выкл LEDflag = !LEDflag; // инвертировать флаг delay(1000); // ждать >

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

boolean LEDflag = false; uint32_t myTimer; // переменная времени void setup() < pinMode(13, OUTPUT); >void loop() < if (millis() - myTimer >= 1000) < myTimer = millis(); // сбросить таймер digitalWrite(13, LEDflag); // вкл/выкл LEDflag = !LEDflag; // инвертировать флаг >>

Многозадачность с прерываниями таймера (для AVR)

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

  • Динамическая индикация
  • Генерация определённого сигнала/протокола связи
  • Программный ШИМ
  • “Тактирование” шаговых моторов
  • Любой другой пример выполнения через указанное время или просто периодическое выполнение по строгому периоду

Настройка таймера на нужную частоту и режим работы – непосильная для новичка задача, хоть и решается в 2-3 строчки кода, поэтому предлагаю использовать библиотеки. Для настройки прерываний по таймерам 1 и 2 есть библиотеки TimerOne и TimerTwo. Мы сделали свою библиотеку, GyverTimers, в которой есть таймер 0 (для программирования без использования Arduino.h), а также все таймеры на Arduino MEGA, а их там целых 6 штук. Ознакомиться с документацией и примерами можно на странице библиотеки.

Сейчас рассмотрим простой пример, в котором “параллельно” выполняющемуся Blink будут отправляться данные в порт. Пример оторван от реальности, так делать нельзя, но он важен для понимания самой сути: код в прерывании выполнится в любом случае, ему безразличны задержки и бесконечные циклы в основном коде.

#include «GyverTimers.h» void setup() < Serial.begin(9600); // Устанавливаем период таймера 333000 мкс ->0.333 c (3 раза в секунду) Timer2.setPeriod(300000); Timer2.enableISR(); // запускаем прерывание на канале А таймера 2 pinMode(13, OUTPUT); // будем мигать > void loop() < // "блинк" digitalWrite(13, 1); delay(1000); digitalWrite(13, 0); delay(1000); >// Прерывание А таймера 2 ISR(TIMER2_A)

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

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

Библиотеки

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

TimerMs

Это “Таймер на миллис” с дополнительными фишками и настройками:

  • Режим таймера и периодичного выполнения
  • Подключение функции-обработчика
  • Сброс/запуск/перезапуск/остановка/пауза/продолжение отсчёта
  • Возможность форсировать переполнение таймера
  • Возврат оставшегося времени в мс, а также условных единицах 8 и 16 бит
  • Несколько функций получения текущего статуса таймера
  • Алгоритм держит стабильный период и не боится переполнения millis()

Скачать и изучить документацию – ссылка на GitHub.

GyverOS

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

  • Лёгкий вес
  • Статическое выбираемое количество задач
  • Возможность остановки, отключения и прямого вызова задач
  • Вычисление времени до ближайшей задачи (для сна на этот период)
  • Встроенный бенчмарк: время выполнения задачи и загруженность процессора
  • Алгоритм работает на системном таймере millis()

Скачать и изучить документацию – ссылка на GitHub.

Минутка юмора

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

Как-то раз я запостил в нашей группе картинку и очень многие её не поняли. Надеюсь после изучения данного урока всё станет понятно =)

Видео

Полезные страницы

  • Набор GyverKIT – большой стартовый набор Arduino моей разработки, продаётся в России
  • Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
  • Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
  • Полная документация по языку Ардуино, все встроенные функции и макросы, все доступные типы данных
  • Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
  • Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
  • Поддержать автора за работу над уроками
  • Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])

Как писать скетчи для ардуино

Среда разработки Ардуино

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

Написание программ

Программы, создаваемые в среде разработки Ардуино, иногда еще называют скетчами. Скетчи пишутся в текстовом редакторе и сохраняются в файлах с расширением .ino. Встроенный текстовый редактор имеет стандартные инструменты копирования, вставки, поиска и замены текста. Область сообщений в окне программы является, своего рода, обратной связью для пользователя и информирует его о событиях (в том числе и об ошибках), возникающих в процессе записи или экспорта написанного кода. Консоль отображает в виде текста поток выходных данных среды Ардуино, включая все сообщения об ошибках и пр. генерируемую ею информацию. В нижнем правом углу окна программы показывается модель текущей платы и последовательный порт, к которому она подключена. Кнопки на панели инструментов предназначены для создания, открытия, сохранения и прошивки программ в устройство. Отдельная кнопка запускает программу SerialMonitor.

Примечание: В старых версиях IDE (до версии 1.0) скетчи сохраняются с расширением .pde. Такие файлы без проблем открываются в версии 1.0, но при их пересохранении появится сообщение с предложением изменить формат файла на .ino.

Verify (Проверить)
Проверить код на ошибки.

Upload (Прошить)
Скомпилировать программу и «зашить» ее в микроконтроллер Ардуино. Подробнее о прошивке — см. ниже.

Примечание: чтобы прошить микроконтроллер через внешний программатор — нужно зажать клавишу «shift» перед нажатием на эту иконку. При этом текст возле кнопки изменится на «Upload using Programmer».

New (Создать)
Создать новую программу.

Open (Открыть)
Команда открывает меню со списком всех скетчей, доступных в вашей рабочей папке. После щелчка по файлу его содержимое откроется в текущем окне.

Примечание: к сожалению, из-за бага Java’ы, в этом меню не работает скроллинг; поэтому если вам нужно открыть программу из самого конца списка — лучше используйте меню File | Sketchbook.

Save (Сохранить)
Сохранить программу

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

Дополнительные команды находятся в меню: File, Edit, Sketch, Tools и Help. В этих меню всегда активны только те пункты, которые можно применить к текущему элементу или фрагменту кода.

Меню «Edit (Правка)«
  • Copy for Forum (Скопировать для форума)
    Скопировать код программы в буфер обмена в специальном формате, удобном для постинга на форум (с подсветкой синтаксиса).
  • Copy as HTML (Скопировать как HTML)
    Скопировать код программы в буфер обмена в виде HTML-кода, удобного для встраивания в веб-страницы.
Меню «Sketch (Программа)«
  • Verify/Compile (Проверить/Компилировать)
    Проверить код программы на ошибки.
  • Show Sketch Folder (Показать папку программы)
    Открыть папку с файлом текущего скетча.
  • Add File. (Добавить файл. )
    Добавляет исходный файл к текущей программе (выбранный файл будет скопирован из исходной папки). Добавленный файл появится в новой вкладке главного окна. Удалить файлы можно с помощью таб-меню.
  • Import Library (Импортировать библиотеку)
    Добавляет библиотеку к вашей программе путем вставки оператора #include вначале кода. Подробнее о библиотеках см. ниже. Кроме того, в версиях IDE 1.0.5 и выше реализована возможность импортирования библиотек из .zip-архивов.
Меню «Tools (Инструменты)«
  • Auto Format (Автоформат)
    Эта команда наводит красоту в вашем коде, а именно: делает одинаковые отступы соответствующих открывающих и закрывающих фигурных скобок, дополнительные отступы кода внутри логических блоков.
  • Archive Sketch (Заархивировать скетч)
    Создать .zip-архив текущего скетча. Результирующий архив помещается в папку с программой.
  • Board (Плата)
    Выбрать модель используемого Ардуино. Описание различных плат Ардуино см. ниже.
  • Serial Port (Последовательный порт)
    Это меню содержит список всех последовательных устройств, присутствующих в системе (как физических, так и виртуальных). Их список должен обновляться автоматически при каждом открытии главного меню.
  • Programmer (Программатор)
    Позволяет выбрать внешний программатор для прошивки микроконтроллера без использования USB-соединения. Обычно эта функция требуется редко — например, для прошивки загрузчика в новый микроконтроллер.
  • Burn Bootloader (Прошить загрузчик)
    Это меню позволяет прошить загрузчик в контроллер Ардуино. При нормальной работе Ардуино эта функция обычно не требуется, но она может быть полезна, если вам вдруг потребуется заменить микроконтроллер ATmega на новый (который с магазина идет без загрузчика). Перед прошивкой убедитесь, что в меню Boards выбрана именно ваша плата.

Скетчбук (рабочая папка)

В среде разработки Ардуино используется принцип организации скетчбука: все ваши программы (или скетчи) хранятся в одном месте. Чтобы просмотреть их, необходимо выбрать меню File > Sketchbook или щелкнуть по кнопке Open на панели инструментов. Директория для хранения ваших программ будет автоматически создана при первом запуске среды Ардуино. Ее месторасположение всегда можно изменить в окне настроек программы.

Начиная с версии 1.0, файлы скетчей имеют расширение .ino. В предыдущих версиях использовалось расширение .pde. В новых версиях программы (1.0 и старше) файлы .pde по-прежнему можно открыть, только их расширение будет автоматически переименовано на .ino.

Вкладки, компиляция и работа с несколькими файлами

Среда Ардуино позволяет работать с программами, состоящими из нескольких файлов (каждый из которых открывается в отдельной вкладке). Например, это могут быть файлы Ардуино (без расширения), C-файлы (с расширением .c), файлы C++ (с расширением .cpp) или заголовочные файлы (.h).

Прошивка

Перед тем, как прошивать программу в контроллер, необходимо правильно выбрать плату и последовательный порт в меню Tools > Board и Tools > Serial Port соответственно. Разновидности плат Ардуино перечислены ниже. Последовательный порт на Mac-системах будет выглядеть примерно так: /dev/tty.usbmodem241 (для Arduino Uno, Mega2560 или Leonardo) или /dev/tty.usbserial-1B1 (для Duemilanove или более старых версий Ардуино с USB), или /dev/tty.USA19QW1b1P1.1 (для USB-UART преобразователей Keyspan). На Windows-системах, необходимый порт скорее всего будет COM1 или COM2 (для устройств с последовательным интерфейсом), либо COM4, COM5, COM6 и выше (для Ардуино с USB) — определить необходимый номер порта можно в Диспетчере устройств, отыскав строку «USB serial device» в разделе Порты. На Linux-системах последовательный порт будет выглядеть как /dev/ttyUSB0, /dev/ttyUSB1 или что-то наподобие.

После выбора используемого порта и платы, необходимо нажать кнопку Upload на панели инструментов или выбрать пункт Upload из меню File. После этого произойдет сброс Ардуино и начнется процесс загрузки программы в память контроллера. В старых моделях (до Arduino Diecimila) функция авто-сброса отсутствует, поэтому перед прошивкой таких устройств необходимо вручную нажать кнопку сброса на плате. В процессе загрузки на большинстве моделей Ардуино будут мигать светодиоды RX и TX. По завершению процесса прошивки, программа выдаст соответствующее сообщение или ошибку.

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

Библиотеки

Библиотеки расширяют функциональность программ и несут в себе дополнительные функции, например, для работы с аппаратными средствами, функции по обработке данных и т.д. Для подключения библиотеки необходимо выбрать ее из меню Sketch > Import Library. После этого библиотека будет скомпилирована, а в начало программы будет добавлен один или несколько операторов #include. Помните, что библиотеки загружаются в контроллер вместе со скетчем, поэтому каждая подключенная библиотека треубет дополнительного места в памяти микроконтроллера. Соответственно, если та или иная библиотека больше не используется в вашей программе — просто удалите оператор #include из программы.

Список основных библиотек, описанных на сайте, приведен здесь. Некоторые из них устанавливаются вместе со средой разработки Ардуино, остальные можно скачать из разных источников. В версиях IDE 1.0.5 и старше реализована возможность импорта библиотек прямо из zip-архива и подключения их к текущему скетчу. См. инструкции по установке сторонних библиотек.

Инструкции по написанию собственных библиотек см. здесь.

Оборудование сторонних производителей

Для работы с «железом» сторонних производителей необходимо добавить ряд файлов в директорию hardware, расположенную внутри рабочей папки. Среди этих файлов должны быть файлы с информацией о добавляемой плате (эта информацию будет отображаться в меню Board), библиотеки, загрузчики и параметры для программатора. Для установки нового оборудования необходимо создать папку внутри директории hardware и распаковать в нее содержимое архива с данными о добавляемом «железе». (Не следует называть создаваемую папку «arduino», иначе будет затерта информация о самой платформе Ардуино). Чтобы удалить добавленное оборудование — достаточно просто удалить его папку.

Дополнительную информацию о создании пакетов для стороннего оборудования можно найти в соответствующем разделе на сайте Arduino Google Code.

Программа «Serial Monitor»

Отображает данные, поступающие от Ардуино на компьютер по последовательному интерфейсу (поддерживается работа как с USB-, так и с обычными версиями Ардуино). Чтобы отправить данные внешнему устройству, достаточно просто ввести текст в окне программы и щелкнуть по кнопке «Отправить» (либо нажать Enter). Из выпадающего списка необходимо выбрать только скорость передачи данных, соответствующую той скорости, которую вы указали в функции Serial.begin() в вашем скетче. Помните, что на Mac- и Linux-системах, Ардуино будет сбрасываться при каждом подключении программы к устройству (соответственно, после сброса скетч будет выполняться заново).

Общаться с Ардуино можно также через Processing, Flash, MaxMSP и пр. (подробнее об этом читайте здесь).

Настройки

Некоторые параметры можно задать непосредственно в окне настроек программы (на Mac-системах это окно вызывается из меню Arduino, на Windows и Linux-системах — из меню File). Остальные параметры находятся в конфиг-файле, местонахождение которого также указано в окне настроек.

Поддерживаемые языки

Среда разработки Ардуино 1.0.1 переведена более чем на 30 различных языков. По умолчанию, язык IDE выбирается исходя из языковых настроек вашей операционной системы. (Обратите внимание: на Windows и Linux-системах язык IDE определяется по региональным настройкам, отвечающим за формат даты и валюты, а не по языкоу самой операционной системы).

Если вы хотите вручную изменить текущий язык программы, запустите среду Ардуино и откройте окно настроек. В поле Editor Language будет выпадающий список поддерживаемых языков. Выберите из списка предпочитаемый язык и перезапустите программу, чтобы изменения вступили в силу. Если выбранный вами язык не поддерживается, то по умолчанию IDE подгрузит английскую локализацию.

Чтобы сбросить языковые настройки среды и вернуть автоматический выбор ее языка по региональным настройкам операционной системы, в выпадающем списке необходимо выбрать пункт System Default. Изменения вступят в силу после перезапуска IDE Arduino. И наоборот, чтобы изменение языковых настроек операционной системы повлияли на текущий язык программы, необходимо просто перезапустить среду Ардуино.

Разновидности плат

Выбирать модель используемой платы в среде Ардуино необходимо по двум причинам:

  1. Чтобы задать параметры, используемые во время компиляции и прошивки скетчей (такие, как тактовая частота, бодрейт и др.);
  2. Чтобы задать настройки фьюз-битов, используемые во время прошивки загрузчика в контроллер платы.

Ниже перечислены основные пункты меню Boards и дана расшифровка соответствующих им настроек:

  • Arduino Uno
    ATmega328, работающий на частоте 16 МГц с авто-сбросом, используемый загрузчик — optiboot (115200 бод, 0.5 КБ).
  • Arduino Duemilanove w/ ATmega328
    ATmega328, работающий на частоте 16 МГц с авто-сбросом.
  • Arduino Diecimila or Duemilanove w/ ATmega168
    ATmega168, работающий на частоте 16 МГц с авто-сбросом. Настройки компиляции и прошивки эквивалентны пункту Arduino NG or older w/ ATmega168, за исключением загрузчика с уменьшенным таймаутом (после сброса платы светодиод на 13-м выводе мигает только один раз).
  • Arduino Nano w/ ATmega328
    ATmega328, работающий на частоте 16 МГц с авто-сбросом. Имеет восемь аналоговых входов.
  • Arduino Nano w/ ATmega168
    ATmega168, работающий на частоте 16 МГц с авто-сбросом. Настройки компиляции и прошивки эквивалентны пункту Arduino NG or older w/ ATmega168, за исключением загрузчика с уменьшенным таймаутом (после сброса платы светодиод на 13-м выводе мигает только один раз). Имеет восемь аналоговых входов.
  • Arduino Mega 2560 or Mega ADK
    ATmega2560, работающий на частоте 16 МГц с авто-сбросом, используемый загрузчик — stk500v2.
  • Arduino Mega (ATmega1280)
    ATmega1280, работающий на частоте 16 МГц с авто-сбросом.
  • Arduino Leonardo
    ATmega32u4, работающий на частоте 16 МГц с авто-сбросом.
  • Arduino Mini w/ ATmega328
    ATmega328, работающий на частоте 16 МГц с авто-сбросом, используемый загрузчик — optiboot(115200 бод, 0.5 КБ). Имеет восемь аналоговых входов.
  • Arduino Mini w/ ATmega168
    Настройки эквивалентны пункту Arduino NG or older w/ ATmega168 (т.е. ATmega168, работающий на частоте 16 МГц без авто-сброса).
  • Arduino Ethernet
    Настройки эквивалентны Arduino UNO с установленной Ethernet-платой расширения.
  • Arduino Fio
    ATmega328, работающий на частоте 8 МГц с авто-сбросом. Настройки эквивалентны пункту Arduino Pro or Pro Mini (3.3V, 8 MHz) w/ATmega328.
  • Arduino BT w/ ATmega328
    ATmega328, работающий на частоте 16 МГц. Прошитый загрузчик (4 КБ) содержит коды для инициализации встроенного bluetooth-модуля.
  • Arduino BT w/ ATmega168
    ATmega168, работающий на частоте 16 МГц. Прошитый загрузчик (4 КБ) содержит коды для инициализации встроенного bluetooth-модуля.
  • LilyPad Arduino w/ ATmega328
    ATmega328, работающий на частоте 8 МГц (3.3В) с авто-сбросом. Настройки эквивалентны пункту Arduino Pro or Pro Mini (3.3V, 8MHz) w/ ATmega328.
  • LilyPad Arduino w/ ATmega168
    ATmega168, работающий на частоте 8 МГц. Настройки компиляции и прошивки эквивалентны пункту Arduino Pro or Pro Mini (8MHz) w/ ATmega168. Используется загрузчик с уменьшенным таймаутом (при сбросе платы светодиод на 13-м выводе мигает три раза), поскольку базовые версии LilyPad не поддерживают функцию авто-сброса. В LilyPad также нет внешнего тактового генератора, поэтому при прошивке загрузчика фьюз-биты ATmega168 устанавливаются таким образом, чтобы контроллер тактировался от внутреннего генератора на 8 МГц.

Инструкции по добавлению в программу других устройств описаны выше в разделе «Оборудование сторонних производителей». Текст данного руководства опубликован под лицензией Creative Commons Attribution-ShareAlike 3.0. Примеры кода, встречающиеся в руководстве, являются свободным контентом.

Скетчи для Arduino

Скетчи для Arduino – важный элемент программирования, без которого невозможно функционирование и запуск цифровых процессов в среде разработки Arduino IDE. Новичкам и начинающим пользователям приложения бывает сложно разобраться с прошивкой, хотя создатели ПО вложили в нее много готовых примеров и подсказок.

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

  • применение интегрированных, уже имеющихся примеров;
  • написание отдельных кодов самостоятельно.

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

применение интегрированных примеров

Запускаем его и работаем.

Панель управления

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

Напомним их предназначение:

Кнопки

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

Ну и поговорим немного о сторонних скетчах, которые вы наверняка заметили в каждом нашем материале и схеме подключения на конкретных проектах. Если описывать наглядно, как выглядит методология их применения, то получится так. Сначала вы копируете скетч, вставляете его в соответствующее окно в утилите, кликаете на кнопку Компиляция, дожидаетесь ее окончания. Просто, не так ли?

Добавление скетча

Мы часто предлагали и будем это делать далее готовые скетчи для Arduino Uno. Так вот — многие из них подойдут и под другие контроллеры: Arduino Nano или Arduino Mega, об этом ищите указания в статьях. Это важно!

Фраза «Залить скетч» означает то, что его следует скопировать и вставить в форму для компиляции. Она часто встречается – потому будьте внимательны!

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *