Домой / Игры / Ардуино параллельные процессы. Прерывания Arduino с помощью attachInterrupt. Аппаратное прерывание от таймера

Ардуино параллельные процессы. Прерывания Arduino с помощью attachInterrupt. Аппаратное прерывание от таймера

Вообще говоря, 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 IDE. Не удивляйтесь, обе программы сделаны на одном движке.

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

Запустим Arduino IDE и выберем простейший пример вывода данных на Serial Port :

Void setup() { Serial.begin(9600); } void loop() { Serial.println("Hello Kitty!"); // ждем 500 миллисекунд перед следующей отправкой delay(500); }

Запустим пример и убедимся, что код работает.

Получение данных

Теперь мы хотим получить этот же текст в . Запускаем новый проект и напишем код.

Первый шаг - импортировать библиотеку. Идем в Sketch | Import Library | Serial . В скетче появится строка:

Import processing.serial.*; Serial serial; // создаем объект последовательного порта String received; // данные, получаемые с последовательного порта void setup() { String port = Serial.list(); serial = new Serial(this, port, 9600); } void draw() { if (serial.available() > 0) { // если есть данные, received = serial.readStringUntil("\n"); // считываем данные } println(received); //отображаем данные в консоли }

Чтобы обеспечить прием данных с последовательного порта, нам нужен объект класса Serial . Так как с Arduino мы отправляем данные типа String, нам надо получить строку и в Processing.

В методе setup() нужно получить доступный последовательный порт. Как правило, это первый доступный порт из списка. После этого мы можем настроить объект Serial , указав порт и скорость передачи данных (желательно, чтобы скорости совпадали).

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

Processing позволяет работать не только с консолью, но и создавать стандартные окна. Перепишем код.

Import processing.serial.*; Serial serial; // создаем объект последовательного порта String received; // данные, получаемые с последовательного порта void setup() { size(320, 120); String port = Serial.list(); serial = new Serial(this, port, 9600); } void draw() { if (serial.available() > 0) { // если есть данные, // считываем их и записываем в переменную received received = serial.readStringUntil("\n"); } // Настройки для текста textSize(24); clear(); if (received != null) { text(received, 10, 30); } }

Запустим пример ещё раз и увидим окно с надписью, которое перерисовывается в одном месте.

Таким образом мы научились получать данные от Arduino. Это позволит нам рисовать красивые графики или создавать программы контроля за показаниями датчиков.

Отправка данных

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

Допустим, мы будем посылать символ "1" из Processing. Когда плата обнаружит присланный символ, включим светодиод на порту 13 (встроенный).

Скетч будет похож на предыдущий. Для примера создадим небольшое окно. При щелчке в области окна будем отсылать "1" и дублировать в консоли для проверки. Если щелчков не будет, то посылается команда "0".

Import processing.serial.*; Serial serial; // создаем объект последовательного порта String received; // данные, получаемые с последовательного порта void setup() { size(320, 120); String port = Serial.list(); serial = new Serial(this, port, 9600); } void draw() { if (mousePressed == true) { //если мы кликнули мышкой в пределах окна serial.write("1"); //отсылаем 1 println("1"); } else { //если щелчка не было serial.write("0"); //отсылаем 0 } }

Теперь напишем скетч для Arduino.

Char commandValue; // данные, поступаемые с последовательного порта int ledPin = 13; // встроенный светодиод void setup() { pinMode(ledPin, OUTPUT); // режим на вывод данных Serial.begin(9600); } void loop() { if (Serial.available()) { commandValue = Serial.read(); } if (commandValue == "1") { digitalWrite(ledPin, HIGH); // включаем светодиод } else { digitalWrite(ledPin, LOW); // в противном случае выключаем } delay(10); // задержка перед следующим чтением данных }

Запускаем оба скетча. Щёлкаем внутри окна и замечаем, что светодиод загорается. Можно даже не щёлкать, а удерживать кнопку мыши нажатой - светодиод будет гореть постоянно.

Обмен данными

Теперь попытаемся объединить оба подхода и обмениваться сообщениями между платой и приложением в двух направлениях.

Для максимальной эффективности добавим булеву переменную. В результате у нас отпадает необходимость постоянно отсылать 1 или 0 от Processing и последовательный порт разгружается и не передает лишнюю информацию.

Когда плата обнаружит присланную единицу, то меняем булевое значение на противоположное относительно текущего состояния (LOW на HIGH и наоборот). В else используем строку "Hello Kity", которую будем отправлять только в случае, когда не обнаружим "1".

Функция establishContact() отсылает строку, которую мы ожидаем получить в Processing. Если ответ приходит, значит Processing может получить данные.

Char commandValue; // данные, поступаемые с последовательного порта int ledPin = 13; boolean ledState = LOW; //управляем состоянием светодиода void setup() { pinMode(ledPin, OUTPUT); Serial.begin(9600); establishContact(); // отсылаем байт для контакта, пока ресивер отвечает } void loop() { // если можно прочитать данные if (Serial.available() > 0) { // считываем данные commandValue = Serial.read(); if (commandValue == "1") { ledState = !ledState; digitalWrite(ledPin, ledState); } delay(100); } else { // Отсылаем обратно Serial.println("Hello Kitty"); } delay(50); } void establishContact() { while (Serial.available() <= 0) { Serial.println("A"); // отсылает заглавную A delay(300); } }

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

Добавим новую булеву переменную firstContact , которая позволяет определить, есть ли соединение с Arduino.

В методе setup() добавляем строку serial.bufferUntil("\n"); . Это позволяет хранить поступающие данные в буфере, пока мы не обнаружим определённый символ. В этом случае возвращаем (\n), так как мы отправляем Serial.println() от Arduino. "\n" в конце значит, что мы активируем новую строку, то есть это будут последние данные, которые мы увидим.

Так как мы постоянно отсылаем данные, метод serialEvent() выполняет задачи цикла draw() , то можно его оставить пустым.

Теперь рассмотрим основной метод serialEvent() . Каждый раз, когда мы выходим на новую строку (\n), вызывается этот метод. И каждый раз проводится следующая последовательность действий:

  • Считываются поступающие данные;
  • Проверяется, содержат ли они какие-то значения (то есть, не передался ли нам пустой массив данных или "нуль");
  • Удаляем пробелы;
  • Если мы первый раз получили необходимые данные, изменяем значение булевой переменной firstContact и сообщаем Arduino, что мы готовы принимать новые данные;
  • Если это не первый приём необходимого типа данных, отображаем их в консоли и отсылаем микроконтроллеру данные о клике, который совершался;
  • Собщаем Arduino, что мы готовы принимать новый пакет данных.
import processing.serial.*; Serial serial; // создаем объект последовательного порта String received; // данные, получаемые с последовательного порта // Проверка на поступление данных от Arduino boolean firstContact = false; void setup() { size(320, 120); String port = Serial.list(); serial = new Serial(this, port, 9600); serial.bufferUntil("\n"); } void draw() { } void serialEvent(Serial myPort) { //формируем строку из данных, которые поступают // "\n" - разделитель - конец пакета данных received = myPort.readStringUntil("\n"); //убеждаемся, что наши данные не пустые перед тем, как продолжить if (received != null) { //удаляем пробелы received = trim(received); println(received); //ищем нашу строку "A" , чтобы начать рукопожатие //если находим, то очищаем буфер и отсылаем запрос на данные if (firstContact == false) { if (received.equals("A")) { serial.clear(); firstContact = true; myPort.write("A"); println("contact"); } } else { //если контакт установлен, получаем и парсим данные println(received); if (mousePressed == true) { //если мы кликнули мышкой по окну serial.write("1"); //отсылаем 1 println("1"); } // когда вы все данные, делаем запрос на новый пакет serial.write("A"); } } }

При подключении и запуске в консоли должна появится фраза "Hello Kitty". Когда вы будете щёлкать мышкой в окне Processing, светодиод на пине 13 будет включаться и выключаться.

Кроме Processing, вы можете использовать программы PuTTy или написать свою программу на C# использованием готовых классов для работы с портами.

04.Communication: Dimmer

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

Для примера понадобится стандартная схема с резистором и светодиодом на выводе 9.

Скетч для Arduino.

Const int ledPin = 9; // светодиод на выводе 9 void setup() { Serial.begin(9600); // устанавливаем режим на вывод pinMode(ledPin, OUTPUT); } void loop() { byte brightness; // проверяем, есть ли данные от компьютера if (Serial.available()) { // читаем последние полученные байты от 0 до 255 brightness = Serial.read(); // устанавливаем яркость светодиода analogWrite(ledPin, brightness); } }

Код для Processing

Import processing.serial.*; Serial port; void setup() { size(256, 150); println("Available serial ports:"); println(Serial.list()); // Uses the first port in this list (number 0). Change this to select the port // corresponding to your Arduino board. The last parameter (e.g. 9600) is the // speed of the communication. It has to correspond to the value passed to // Serial.begin() in your Arduino sketch. port = new Serial(this, Serial.list(), 9600); // Если вы знаете имя порта, используемой платой Arduino board, то явно укажите //port = new Serial(this, "COM1", 9600); } void draw() { // рисуем градиент от чёрного к белому for (int i = 0; i

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

04.Communication: PhysicalPixel (Зажигаем светодиод мышкой)

Немного изменим задачу. Будем проводить мышкой над квадратом и посылать символ "H" (High), чтобы зажечь светодиод на плате. Когда мышь покинет область квадрата, то пошлём символ "L" (Low), чтобы погасить светодиод.

Код для Arduino.

Const int ledPin = 13; // вывод 13 для светодиода int incomingByte; // переменная для получения данных void setup() { Serial.begin(9600); pinMode(ledPin, OUTPUT); } void loop() { // если есть данные if (Serial.available() > 0) { // считываем байт в буфере incomingByte = Serial.read(); // если это символ H (ASCII 72), то включаем светодиод if (incomingByte == "H") { digitalWrite(ledPin, HIGH); } // если это символ L (ASCII 76), то выключаем светодиод if (incomingByte == "L") { digitalWrite(ledPin, LOW); } } }

Код для Processing.

Import processing.serial.*; float boxX; float boxY; int boxSize = 20; boolean mouseOverBox = false; Serial port; void setup() { size(200, 200); boxX = width / 2.0; boxY = height / 2.0; rectMode(RADIUS); println(Serial.list()); // Open the port that the Arduino board is connected to (in this case #0) // Make sure to open the port at the same speed Arduino is using (9600bps) port = new Serial(this, Serial.list(), 9600); } void draw() { background(0); // Если курсор над квадратом if (mouseX > boxX - boxSize && mouseX boxY - boxSize && mouseY

04.Communication: Graph (Рисуем график)

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

Инструкция

Вообще говоря, Arduino не поддерживает настоящее распараллеливание задач, или мультипоточность.
Но можно при каждом повторении цикла "loop()" указать проверять, не наступило ли время выполнить некую дополнительную, фоновую задачу. При этом пользователю будет казаться, что несколько задач выполняются одновременно.
Например, давайте будем мигать с заданной частотой и параллельно этому издавать нарастающие и затихающие подобно сирене звуки из пьезоизлучателя.
И светодиод, и мы уже не раз подключали к Arduino. Соберём схему, как показано на рисунке. Если вы подключаете светодиод к цифровому выводу, отличному от "13", не забывайте о токоограничивающем резисторе примерно на 220 Ом.

Напишем вот такой скетч и загрузим его в Ардуино.
После платы видно, что скетч выполняется не совсем так как нам нужно: пока полностью не отработает сирена, светодиод не мигнёт, а мы бы хотели, чтобы светодиод ВО ВРЕМЯ звучания сирены. В чём же здесь проблема?
Дело в том, что обычным образом эту задачу не решить. Задачи выполняются микроконтроллером строго последовательно. Оператор "delay()" задерживает выполнение программы на указанный промежуток времени, и пока это время не истечёт, следующие команды программы не будут выполняться. Из-за этого мы не можем задать разную длительность выполнения для каждой задачи в цикле "loop()" программы.
Поэтому нужно как-то сымитировать многозадачность.

Вариант, при котором Arduino будет выполнять задачи псевдо-параллельно, предложен разработчиками Ардуино в статье https://www.arduino.cc/en/Tutorial/BlinkWithoutDelay.
Суть метода в том, что при каждом повторении цикла "loop()" мы проверяем, настало ли время мигать светодиодом (выполнять фоновую задачу) или нет. И если настало, то инвертируем состояние светодиода. Это своеобразный вариант обхода оператора "delay()".
Существенным недостатком данного метода является то, что участок кода перед блоком управления светодиодом должен выполняться быстрее, чем интервал времени мигания светодиода "ledInterval". В противном случае мигание будет происходить реже, чем нужно, и эффекта параллельного выполнения задач мы не получим. В частности, в нашем скетче длительность изменения звука сирены составляет 200+200+200+200 = 800 мсек, а интервал мигания светодиодом мы задали 200 мсек. Но светодиод будет мигать с периодом 800 мсек, что в 4 раза отличается от того, что мы задали. Вообще, если в коде используется оператор "delay()", в таком случае трудно сымитировать псевдо-параллельность, поэтому желательно его избегать.
В данном случае нужно было бы для блока управления звуком сирены также проверять, пришло время или нет, а не использовать "delay()". Но это бы увеличило количество кода и ухудшило читаемость программы.

Чтобы решить поставленную задачу, воспользуемся замечательной библиотекой ArduinoThread, которая позволяет с лёгкостью создавать псевдо-параллельные процессы. Она работает похожим образом, но позволяет не писать код по проверке времени - нужно выполнять задачу в этом цикле или не нужно. Благодаря этому сокращается объём кода и улучшается читаемость скетча. Давайте проверим библиотеку в действии.
Первым делом скачаем с официального сайта https://github.com/ivanseidel/ArduinoThread/archive/master.zip архив библиотеки и разархивируем его в директорию "libraries" среды разработки Arduino IDE. Затем переименуем папку "ArduinoThread-master" в "ArduinoThread".

Схема подключений останется прежней. Изменится лишь код программы. Теперь он будет такой, как на врезке.
В программе мы создаём два потока, каждый выполняет свою операцию: один мигает светодиодом, второй управляет звуком сирены. В каждой итерации цикла для каждого потока проверяем, пришло ли время его выполнения или нет. Если пришло - он запускается на исполнение с помощью метода "run()". Главное - не использовать оператор "delay()".
В коде даны более подробные пояснения.
Загрузим код в память Ардуино, запустим. Теперь всё работает в точности так, как надо!

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

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

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

Это позволит не пропустить все остальные запланированные прерывания. Во-вторых, при обработке прерывания программный код не должен требовать активности от других прерываний. Если этого не предотвратить, то программа просто зависнет.

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

Если вызов функции Update() все же необходим, то предварительно необходимо будет проверить переменную состояния. Это позволит выяснить, необходима ли последующая обработка.

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

Какими функциями оперирует тот или иной таймер?

Для микроконтроллера Arduino Uno у каждого из трех таймеров свои операции.

Так Timer0 отвечает за ШИМ на пятом и шестом пине, функции millis() , micros() , delay() .

Другой таймер – Timer1, используется с ШИМ на девятом и десятом пине, с библиотеками WaveHC и Servo.

Timer2 работает с ШИМ на 11 и 13 пинах, а также с Tone .

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

Результатом этого процесса будет сохранение в регистре копии основных переменных кода, что позволит обеспечить максимальную скорость доступа к ним.

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

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

Здравствуйте, Андрей. Весьма интересен ваш подход к передаче багажа знаний и опыта, вами накопленного. Очень помогает в начинаниях. Ну и я, начиная осваивать 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 // если измеренная дистанция меньше или равна минимальной — стоим