Ардуино параллельные процессы. Arduino: Параллельное и последовательное подключение ведомых устройств к шине SPI. Управление светодиодом и пьезоизлучателем с помощью оператора delay()

Вообще говоря, Arduino не поддерживает настоящее распараллеливание задач, или мультипоточность. Но можно при каждом повторении цикла loop() указать микроконтроллеру проверять, не наступило ли время выполнить некую дополнительную, фоновую задачу. При этом пользователю будет казаться, что несколько задач выполняются одновременно.

Например, давайте будем мигать светодиодом с заданной частотой и параллельно этому издавать нарастающие и затихающие подобно сирене звуки из пьезоизлучателя. И светодиод, и пьезоизлучатель мы уже не раз подключали к Arduino. Соберём схему, как показано на рисунке.

Если вы подключаете светодиод к цифровому выводу, отличному от "13", не забывайте о токоограничивающем резисторе примерно на 220 Ом.

2 Управление светодиодом и пьезоизлучателем с помощью оператора delay()

Напишем вот такой скетч и загрузим его в Ардуино.

Const int soundPin = 3; /* объявляем переменную с номером пина, на который подключён пьезоэлемент */ const int ledPin = 13; // объявляем переменную с номером пина светодиода void setup() { pinMode(soundPin, OUTPUT); // объявляем пин 3 как выход. pinMode(ledPin, OUTPUT); // объявляем пин 13 как выход. } void loop() { // Управление звуком: tone(soundPin, 700); // издаём звук на частоте 700 Гц delay(200); tone(soundPin, 500); // на частоте 500 Гц delay(200); tone(soundPin, 300); // на частоте 300 Гц delay(200); tone(soundPin, 200); // на частоте 200 Гц delay(200); // Управление светодиодом: digitalWrite(ledPin, HIGH); // зажигаем delay(200); digitalWrite(ledPin, LOW); // гасим delay(200); }

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

Дело в том, что обычным образом эту задачу не решить. Задачи выполняются микроконтроллером строго последовательно. Оператор delay() задерживает выполнение программы на указанный промежуток времени, и пока это время не истечёт, следующие команды программы не будут выполняться. Из-за этого мы не можем задать разную длительность выполнения для каждой задачи в цикле loop() программы. Поэтому нужно как-то сымитировать многозадачность.

3 Параллельные процессы без оператора "delay()"

Вариант, при котором Arduino будет выполнять задачи псевдо-параллельно, предложен разработчиками Ардуино . Суть метода в том, что при каждом повторении цикла loop() мы проверяем, настало ли время мигать светодиодом (выполнять фоновую задачу) или нет. И если настало, то инвертируем состояние светодиода. Это своеобразный вариант обхода оператора delay() .

Const int soundPin = 3; // переменная с номером пина пьезоэлемента const int ledPin = 13; // переменная с номером пина светодиода const long ledInterval = 200; // интервал мигания светодиодом, мсек. int ledState = LOW; // начальное состояние светодиода unsigned long previousMillis = 0; // храним время предыдущего срабатывания светодиода void setup() { pinMode(soundPin, OUTPUT); // задаём пин 3 как выход. pinMode(ledPin, OUTPUT); // задаём пин 13 как выход. } void loop() { // Управление звуком: tone(soundPin, 700); delay(200); tone(soundPin, 500); delay(200); tone(soundPin, 300); delay(200); tone(soundPin, 200); delay(200); // Мигание светодиодом: // время с момента включения Arduino, мсек: unsigned long currentMillis = millis(); // Если время мигать пришло, if (currentMillis - previousMillis >= ledInterval) { previousMillis = currentMillis; // то запоминаем текущее время if (ledState == LOW) { // и инвертируем состояние светодиода ledState = HIGH; } else { ledState = LOW; } digitalWrite(ledPin, ledState); // переключаем состояние светодиода } }

Существенным недостатком данного метода является то, что участок кода перед блоком управления светодиодом должен выполняться быстрее, чем интервал времени мигания светодиода "ledInterval". В противном случае мигание будет происходить реже, чем нужно, и эффекта параллельного выполнения задач мы не получим. В частности, в нашем скетче длительность изменения звука сирены составляет 200+200+200+200 = 800 мсек, а интервал мигания светодиодом мы задали 200 мсек. Но светодиод будет мигать с периодом 800 мсек, что в 4 раза больше того, что мы задали.

Вообще, если в коде используется оператор delay() , в таком случае трудно сымитировать псевдо-параллельность, поэтому желательно его избегать.

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

4 Использование библиотеки ArduinoThread для создания параллельных потоков

Чтобы решить поставленную задачу, воспользуемся замечательной библиотекой ArduinoThread , которая позволяет с лёгкостью создавать псевдо-параллельные процессы. Она работает похожим образом, но позволяет не писать код по проверке времени - нужно выполнять задачу в этом цикле или не нужно. Благодаря этому сокращается объём кода и улучшается читаемость скетча. Давайте проверим библиотеку в действии.


Первым делом скачаем с официального сайта архив библиотеки и разархивируем его в директорию libraries/ среды разработки Arduino IDE. Затем переименуем папку ArduinoThread-master в ArduinoThread .

Схема подключений останется прежней. Изменится лишь код программы.

#include // подключение библиотеки ArduinoThread const int soundPin = 3; // переменная с номером пина пьезоэлемента const int ledPin = 13; // переменная с номером пина светодиода Thread ledThread = Thread(); // создаём поток управления светодиодом Thread soundThread = Thread(); // создаём поток управления сиреной void setup() { pinMode(soundPin, OUTPUT); // объявляем пин 3 как выход. pinMode(ledPin, OUTPUT); // объявляем пин 13 как выход. ledThread.onRun(ledBlink); // назначаем потоку задачу ledThread.setInterval(1000); // задаём интервал срабатывания, мсек soundThread.onRun(sound); // назначаем потоку задачу soundThread.setInterval(20); // задаём интервал срабатывания, мсек } void loop() { // Проверим, пришло ли время переключиться светодиоду: if (ledThread.shouldRun()) ledThread.run(); // запускаем поток // Проверим, пришло ли время сменить тональность сирены: if (soundThread.shouldRun()) soundThread.run(); // запускаем поток } // Поток светодиода: void ledBlink() { static bool ledStatus = false; // состояние светодиода Вкл/Выкл ledStatus = !ledStatus; // инвертируем состояние digitalWrite(ledPin, ledStatus); // включаем/выключаем светодиод } // Поток сирены: void sound() { static int ton = 100; // тональность звука, Гц tone(soundPin, ton); // включаем сирену на "ton" Гц if (ton }

В программе мы создаём два потока - ledThread и soundThread , каждый выполняет свою операцию: один мигает светодиодом, второй управляет звуком сирены. В каждой итерации цикла для каждого потока проверяем, пришло ли время его выполнения или нет. Если пришло - он запускается на исполнение с помощью метода run() . Главное - не использовать оператор delay() . В коде даны более подробные пояснения.


Загрузим код в память Ардуино, запустим. Теперь всё работает в точности так, как надо!

И приведем пример использования функции Arduino attachInterrupt() .

Прерывание – это сигнал, который сообщает процессору о наступлении какого-либо события, которое требует незамедлительного внимания. Процессор должен отреагировать на этот сигнал, прервав выполнение текущих инструкций и передав управление обработчику прерывания (ISR, Interrupt Service Routine). Обработчик – это обычная функция, которую мы пишем сами и помещаем туда тот код, который должен отреагировать на событие.

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

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

Прерывания в Ардуино можно разделить на несколько видов:

  • Аппаратные прерывания . Прерывание на уровне микропроцессорной архитектуры. Самое событие может произойти в производительный момент от внешнего устройства – например, нажатие кнопки на клавиатуре, движение компьютерной мыши и т.п.
  • Программные прерывания . Запускаются внутри программы с помощью специальной инструкции. Используются для того, чтобы вызвать обработчик прерываний.
  • Внутренние (синхронные) прерывания . Внутреннее прерывание возникает в результате изменения или нарушения в исполнении программы (например, при обращении к недопустимому адресу, недопустимый код операции и другие).

Зачем нужны аппаратные прерывания

Аппаратные прерывания возникают в ответ на внешнее событие и исходят от внешнего аппаратного устройства. В Ардуино представлены 4 типа аппаратных прерываний. Все они различаются сигналом на контакте прерывания:

  • Контакт притянут к земле. Обработчик прерывания исполняется до тех пор, пока на пине прерывания будет сигнал LOW.
  • Изменение сигнала на контакте. В таком случае Ардуино выполняет обработчик прерывания, когда на пине прерывания происходит изменение сигнала.
  • Изменение сигнала от LOW к HIGH на контакте – при изменении с низкого сигнала на высокий будет исполняться обработчик прерывания.
  • Изменение сигнала от HIGH к LOW на контакте – при изменении с высокого сигнала на низкий будет исполняться обработчик прерывания.

Прерывания полезны в программах Ардуино, так как помогают решать проблемы синхронизации. Например, при работе с UART прерывания позволяют не отслеживать поступление каждого символа. Внешнее аппаратное устройство подает сигнал прерывания, процессор сразу же вызывает обработчик прерывания, который вовремя захватывает символ. Это позволяет экономить процессорное время, которое без прерываний тратилось бы на проверку статуса UART, вместо этого все необходимые действия выполняются обработчиком прерывания, не затрагивая главную программу. Особых возможностей от аппаратного устройства не требуется.

Основными причинами, по которым необходимо вызвать прерывание, являются:

  • Определение изменения состояния вывода;
  • Прерывание по таймеру;
  • Прерывания данных по SPI, I2C, USART;
  • Аналогово-цифровое преобразование;
  • Готовность использовать EEPROM, флеш-память.

Как реализуются прерывания в Ардуино

При поступлении сигнала прерывания работа приостанавливается. Начинается выполнение функции, которая объявляется на выполнение при прерывании. Объявленная функция не может принимать входные значения и возвращать значения при завершении работы. На сам код в основном цикле программы прерывание не влияет. Для работы с прерываниями в Ардуино используется стандартная функция attachInterrupt() .

Отличие реализации прерываний в разных платах Ардуино

В зависимости от аппаратной реализации конкретной модели микроконтроллера есть несколько прерываний. Плата Arduino Uno имеет 2 прерывания на втором и третьем пине, но если требуется более двух выходов, плата поддерживает специальный режим «pin-change». Этот режим работает по изменению входа для всех пинов. Отличие режима прерывания по изменению входа заключается в том, что прерывания могут генерироваться на любом из восьми контактов. Обработка в таком случае будет сложнее и дольше, так как придется отслеживать последнее состояние на каждом из контактов.

На других платах число прерываний выше. Например, плата имеет 6 пинов, которые могут обрабатывать внешние прерывания. Для всех плат Ардуино при работе с функцией attachInterrupt (interrupt, function, mode) аргумент Inerrupt 0 связан с цифровым пином 2.

Прерывания в языке Arduino

Теперь давайте перейдем к практике и поговорим о том, как использовать прерывания в своих проектах.

Синтаксис attachInterrupt()

Функция attachInterrupt используется для работы с прерываниями. Она служит для соединения внешнего прерывания с обработчиком.

Синтаксис вызова: attachInterrupt(interrupt, function, mode)

Аргументы функции:

  • interrupt – номер вызываемого прерывания (стандартно 0 – для 2-го пина, для платы Ардуино Уно 1 – для 3-го пина),
  • function – название вызываемой функции при прерывании(важно – функция не должна ни принимать, ни возвращать какие-либо значения),
  • mode – условие срабатывания прерывания.

Возможна установка следующих вариантов условий срабатывания:

  • LOW – выполняется по низкому уровню сигнала, когда на контакте нулевое значение. Прерывание может циклично повторяться – например, при нажатой кнопке.
  • CHANGE – по фронту, прерывание происходит при изменении сигнала с высокого на низкий или наоборот. Выполняется один раз при любой смене сигнала.
  • RISING – выполнение прерывания один раз при изменении сигнала от LOW к HIGH.
  • FALLING – выполнение прерывания один раз при изменении сигнала от HIGH к LOW.4

Важные замечания

При работе с прерываниями нужно обязательно учитывать следующие важные ограничения:

  • Функция – обработчик не должна выполняться слишком долго. Все дело в том, что Ардуино не может обрабатывать несколько прерываний одновременно. Пока выполняется ваша функция-обработчик, все остальные прерывания останутся без внимания и вы можете пропустить важные события. Если надо делать что-то большое – просто передавайте обработку событий в основном цикле loop(). В обработчике вы можете лишь устанавливать флаг события, а в loop – проверять флаг и обрабатывать его.
  • Нужно быть очень аккуратными с переменными. Интеллектуальный компилятор C++ может “пере оптимизировать” вашу программу – убрать не нужные, на его взгляд, переменные. Компилятор просто не увидит, что вы устанавливаете какие-то переменные в одной части, а используете – в другой. Для устранения такой вероятности в случае с базовыми типами данных можно использовать ключевое слово volatile, например так: volatile boolean state = 0. Но этот метод не сработает со сложными структурами данных. Так что надо быть всегда на чеку.
  • Не рекомендуется использовать большое количество прерываний (старайтесь не использовать более 6-8). Большое количество разнообразных событий требует серьезного усложнения кода, а, значит, ведет к ошибкам. К тому же надо понимать, что ни о какой временной точности исполнения в системах с большим количеством прерываний речи быть не может – вы никогда точно не поймете, каков промежуток между вызовами важных для вас команд.
  • В обработчиках категорически нельзя использовать delay(). Механизм определения интервала задержки использует таймеры, а они тоже работают на прерываниях, которые заблокирует ваш обработчик. В итоге все будут ждать всех и программа зависнет. По этой же причине нельзя использовать протоколы связи, основанные на прерываниях (например, i2c).

Примеры использования attachInterrupt

Давайте приступим к практике и рассмотрим простейший пример использования прерываний. В примере мы определяем функцию-обработчик, которая при изменении сигнала на 2 пине Arduino Uno переключит состояние пина 13, к которому мы традиционно подключим светодиод.

#define PIN_LED 13 volatile boolean actionState = LOW; void setup() { pinMode(PIN_LED, OUTPUT); // Устанавливаем прерывание // Функция myEventListener вызовется тогда, когда // на 2 пине (прерываниие 0 связано с пином 2) // изменится сигнал (не важно, в какую сторону) attachInterrupt(0, myEventListener, CHANGE); } void loop() { // В функции loop мы ничего не делаем, т.к. весь код обработки событий будет в функции myEventListener } void myEventListener() { actionState != actionState; // // Выполняем другие действия, например, включаем или выключаем светодиод digitalWrite(PIN_LED, actionState); }

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

Прерывания по нажатию кнопки с антидребезгом

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

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

If(digitalRead(2)==HIGH) { //при нажатии кнопки //Если от предыдущего нажатия прошло больше 100 миллисекунд if (millis() - previousMillis >= 100) { //Запоминается время первого срабатывания previousMillis = millis(); if (led==oldled) { //происходит проверка того, что состояние кнопки не изменилось led=!led; }

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

Прерывания по таймеру

Таймером называется счетчик, который производит счет с некоторой частотой, получаемой из процессорных 16 МГц. Можно произвести конфигурацию делителя частоты для получения нужного режима счета. Также можно настроить счетчик для генерации прерываний при достижении заданного значения.

И прерывание по таймеру позволяет выполнять прерывание один раз в миллисекунду. В Ардуино имеется 3 таймера – Timer0, Timer1 и Timer2. Timer0 используется для генерации прерываний один раз в миллисекунду, при этом происходит обновление счетчика, который передается в функцию millis (). Этот таймер является восьмибитным и считает от 0 до 255. Прерывание генерируется при достижении значения 255. По умолчанию используется тактовый делитель на 65, чтобы получить частоту, близкую к 1 кГц.

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

TIMSK0 |= _BV(OCIE0A);

Требуется определить обработчик прерывания для вектора прерывания по таймеру. Вектором прерывания называется указатель на адрес расположения команды, которая будет выполняться при вызове прерывания. Несколько векторов прерывания объединяются в таблицу векторов прерываний. Таймер в данном случае будет иметь название TIMER0_COMPA_vect. В этом обработчике будут производиться те же действия, что и в loop ().

SIGNAL(TIMER0_COMPA_vect) { unsigned long currentMillis = millis(); sweeper1.Update(currentMillis); if(digitalRead(2) == HIGH) { sweeper2.Update(currentMillis); led1.Update(currentMillis); } led2.Update(currentMillis); led3.Update(currentMillis); } //Функция loop () останется пустой. void loop() { }

Подведение итогов

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

Здравствуйте, Андрей. Весьма интересен ваш подход к передаче багажа знаний и опыта, вами накопленного. Очень помогает в начинаниях. Ну и я, начиная осваивать arduino имею желание прогрессировать. Тем более, что с посторонней помощью у меня это получается быстрее. Итак: сперва моя задача была сделать робота, едущего по линии. Сделал — все отлично. Но дальше, снабжая его дополнительными опциями, не понимал — почему он перестал корректно реагировать на линию. Набрел на эту статью и понял причину.

Теперь у меня к вам вопрос: в ниже указанном и готовом скетче, учитывая проблемы с delay, нужно ли мне везде, где присутствует эта функция перейти на millis? Если так, то я понимаю, что скетч придется переделывать почти весь? И не совсем понятно, как использовать millis в замере расстояния? Спасибо.

//Робот с функцией следования по белой полосе

// **********************Установка выводов моторов ************************

int MotorLeftSpeed = 5; // Левый (А) мотор СКОРОСТЬ - ENA

int MotorLeftForward = 4; // Левый (А) мотор ВПЕРЕД - IN1

int MotorLeftBack = 3; // Левый (А) мотор НАЗАД - IN2

int MotorRightForward = 8; // Правый (В) мотор ВПЕРЕД - IN3

int MotorRightBack = 7; // Правый (В) мотор НАЗАД - IN4

int MotorRightSpeed = 9; // Правый (В) мотор СКОРОСТЬ - ENB

// **********************Установка выводов УЗ датчиков***********************

int trigPinL = 14; // задание номера вывода левого trig УЗ датчика

int echoPinL = 15; // задание номера вывода левого echo УЗ датчика

int trigPinC = 10; // задание номера вывода центрального trig УЗ датчика

int echoPinC = 11; // задание номера вывода центрального echo УЗ датчика

int trigPinR = 12; // задание номера вывода правого trig УЗ датчика

int echoPinR = 13; // задание номера вывода правого echo УЗ датчика

// ********************* Установка выводов датчиков линии *******************

const int LineSensorLeft = 19; // вход левого датчика линии

const int LineSensorRight = 18; // вход правого датчика линии

int SL; // статус левого сенсора

int SR; // статус правого сенсора

// *********************Установка вывода световой и звуковой сигнализации**************

int Light = 2; // задание номера вывода световой сигнализации

int Zumm = 6; // задание номера вывода зуммера

int ledState = LOW; // этой переменной устанавливаем состояние светодиода

long previousMillis = 0; // храним время последнего переключения светодиода

long interval = 300; // интервал между включение/выключением светодиода (0,3 секунды)

// *********************Переменная измерение дистанции датчиками*************

unsigned int impulseTimeL=0;

unsigned int impulseTimeC=0;

unsigned int impulseTimeR=0;

long distL=0; // дистанция, измеренная левым УЗ датчиком

long distC=0; // дистанция, измеренная центральным УЗ датчиком

long distR=0; // дистанция, измеренная правым Уз датчиком

// *********************************** SETUP ********************************

Serial.begin (9600); // запускаем серийный порт (скорость 9600)

//*************** Задаем контакты моторов****************

pinMode (MotorRightBack, OUTPUT); // Правый (В) мотор НАЗАД

pinMode (MotorRightForward, OUTPUT); // Правый (В) мотор ВПЕРЕД

pinMode (MotorLeftBack, OUTPUT); // Левый (А) мотор НАЗАД

pinMode (MotorLeftForward, OUTPUT); // Левый (А) мотор ВПЕРЕД

delay (duration);

//*************** Задаем контакты датчиков полосы**************

pinMode (LineSensorLeft, INPUT); // определением pin левого датчика линии

pinMode (LineSensorRight, INPUT); // определением pin правого датчика линии

// ***************Задание режимов выводов УЗ датчиков**********************

pinMode (trigPinL, OUTPUT); // задание режима работы вывода левого trig УЗ датчика

pinMode (echoPinL, INPUT); // задание режима работы вывода левого echo УЗ датчика

pinMode (trigPinC, OUTPUT); // задание режима работы вывода центрального trig УЗ датчика

pinMode (echoPinC, INPUT); // задание режима работы вывода центрального echo УЗ датчика

pinMode (trigPinR, OUTPUT); // задание режима работы вывода правого trig УЗ датчика

pinMode (echoPinR, INPUT); // задание режима работы вывода правого echo УЗ датчика

// ***************Задаем контакты световой и звуковой сигнализации********************************

pinMode (Zumm,OUTPUT); // задание режима работы вывода зуммера

pinMode (Light,OUTPUT); // задание режима работы вывода световой сигнализации

// ****************** Основные команды движения ******************

void forward (int a, int sa) // ВПЕРЕД

analogWrite (MotorRightSpeed, sa);

analogWrite (MotorLeftSpeed, sa);

void right (int b, int sb) // ПОВОРОТ ВПРАВО (одна сторона)

digitalWrite (MotorRightBack, LOW);

digitalWrite (MotorLeftBack, LOW);

digitalWrite (MotorLeftForward, HIGH);

analogWrite (MotorLeftSpeed, sb);

void left (int k, int sk) // ПОВОРОТ ВЛЕВО (одна сторона)

digitalWrite (MotorRightBack, LOW);

digitalWrite (MotorRightForward, HIGH);

analogWrite (MotorRightSpeed, sk);

digitalWrite (MotorLeftBack, LOW);

void stopp (int f) // СТОП

digitalWrite (MotorRightBack, LOW);

digitalWrite (MotorRightForward, LOW);

digitalWrite (MotorLeftBack, LOW);

digitalWrite (MotorLeftForward, LOW);

// **************************Измерение дистанции*********************

void izmdistL () // измерение дистанции левым УЗ датчиком

digitalWrite (trigPinL, HIGH);

digitalWrite (trigPinL, LOW); // импульс 10мС на вывод trig УЗ датчика для измерения расстояния

impulseTimeL = pulseIn (echoPinL, HIGH); // считывание расстояния с УЗ датчика

distL=impulseTimeL/58; // Пересчитываем в сантиметры

void izmdistC () // измерение дистанции центральным УЗ датчиком

digitalWrite (trigPinC, HIGH);

digitalWrite (trigPinC, LOW); // импульс 10мС на вывод trig УЗ датчика для измерения расстояния

impulseTimeC = pulseIn (echoPinC, HIGH); // считывание расстояния с УЗ датчика

distC=impulseTimeC/58; // Пересчитываем в сантиметры

void izmdistR () // измерение дистанции центральным УЗ датчиком

digitalWrite (trigPinR, HIGH);

digitalWrite (trigPinR, LOW); // импульс 10мС на вывод trig УЗ датчика для измерения расстояния

impulseTimeR = pulseIn (echoPinR, HIGH); // считывание расстояния с УЗ датчика

distR=impulseTimeR/58; // Пересчитываем в сантиметры

// *********************************** LOOP *********************************

// ********************** Режим следования по ЛИНИИ *************************

// *********************световая и звуковая сигнализация**************

tone (Zumm,900); // включаем звук на 900 Гц

tone (Zumm,900); // включаем звук на 800 Гц

unsigned long currentMillis = millis ();

if (currentMillis — previousMillis > interval) //проверяем не прошел ли нужный интервал, если прошел то

previousMillis = currentMillis; // сохраняем время последнего переключения

if (ledState == LOW) // если светодиод не горит, то зажигаем, и наоборот

ledState = HIGH;

digitalWrite (Light, ledState); // устанавливаем состояния выхода, чтобы включить или выключить светодиод

// ************************ Измерение дистанции************************

Serial.println (distL);

Serial.println (distC);

Serial.println (distR);

if (distL>50 && distC>50 && distR>50) // если измеренная дистанция больше 50 сантиметров — едем

SL = digitalRead (LineSensorLeft); // считываем сигнал с левого датчика полосы

SR = digitalRead (LineSensorRight); // считываем сигнал с правого датчика полосы

// ************************* Следование по черной линии ***********************

// РОБОТ на полосе - едем прямо

if (SL == LOW & SR == LOW) // БЕЛЫЙ - БЕЛЫЙ - едем ПРЯМО

forward (10, 100);// ПРЯМО (время, скорость)

// РОБОТ начинает смещаться с полосы - подруливаем

else if (SL == LOW & SR == HIGH) // ЧЕРНЫЙ — БЕЛЫЙ - поворот ВЛЕВО

left (10, 100);// поворот ВЛЕВО (время, скорость)

else if (SL == HIGH & SR == LOW) // БЕЛЫЙ — ЧЕРНЫЙ - поворот ВПРАВО

right (10, 100);// поворот ВПРАВО (время, скорость)

// ФИНИШ — РОБОТ видит обоими датчиками полосу

else if (SL == HIGH & SR == HIGH) // ЧЕРНЫЙ — ЧЕРНЫЙ — СТОП

stopp (50);// СТОП

else // если измеренная дистанция меньше или равна минимальной — стоим