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

Мультизадачность на Arduino

Если вам уже удалось сделать первые шаги с Arduino, помигав светодиодом, считать информацию на аналоговых входах Arduino с простейших датчиков и повращать сервоприводом, то, наверняка появилось желание сделать что-нибудь посложнее и поинтереснее.  Реализация более сложных проектов обычно включает в себя объединение частей кода и простых скетчей, пытаясь заставить все это работать вместе. И тут вас ожидает сюрприз — некоторые скетчи, великолепно работающие по-отдельности, отказываются работать совместно с другими.

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

 

Отказываемся от delay ()

Возможно, использование функции delay () для управления временем является одной из первых вещей, о которых вы слышали, когда начали экспериментировать c Arduino. Пользоваться этой функции очень просто и является решением «в лоб», однако данный подход является источником проблем, возникающих на пути, когда вы пытаетесь добавить дополнительную функциональность в свой простой проект. Проблема в том, что функция delay () заставляет работать микроконтроллер только «на себя», она как бы «монополизирует» его, заставляя ожидать заданный временной интервал.

Во время выполнения delay () вы не можете опрашивать входы, обрабатывать данные и менять состояние выходов. Эта функция связывает процессор на все 100%.

Давайте вспомним «Hello, world» и вновь помигаем светодиодом на плате Arduino, используя delay (). Я использую Arduino Mega 2560 и также, как и на Arduino UNO, встроенный в плату светодиод подключен к 13 пину. Простенький код включает светодиод на 1 секунду (1000 мс), затем выключает его на этот же временной интервал.

Показать/скрыть код
// номер пина с подключенным светодиодом
int led = 13;

void setup() {
  // устанавливаем цифровой пин в режим вывода
  pinMode(led, OUTPUT);
}
void loop() {
  digitalWrite(led, HIGH); // включаем светодиод, установив высокий уровень
  delay(1000); // ждем 1 секунду
  digitalWrite(led, LOW); // выключаем светодиод, установив низкий уровень
  delay(1000); // ждем 1 секунду
}

Напомню, что функция setup () выполняется единожды при подаче напряжения питания на Arduino, или же после нажатия кнопки reset на плате. Затем функция loop () и, соответственно, и все что в ней находится, выполняется бесконечно, снова и снова, пока на микроконтроллер подано напряжение питания. Поэтому процессор не может выполнить ничего, кроме как мигать светодиодом — все время тратится на выполнение функции delay.

А что если нам нужно одновременно с морганием светодиодом вращать сервой? Давайте просто добавим к скетчу со светодиодом код, управляющий сервоприводом, в котором опять используем функцию delay () для управления скоростью вращения.

Показать/скрыть код
// Подключаем библиотеку для управления сервоприводами
#include<Servo.h>

// номер пина с подключенным светодиодом
int led = 13;

// создаем объект для управления сервами
// возможно создать до 12 объектов
Servo my_servo;

// переменная для хранения положения сервы
int pos = 0;

void setup() {
  // устанавливаем цифровой пин в режим вывода
  pinMode(led, OUTPUT);
  // присоединяем сервопривод, подключенный к 9 пину к объекту servo
  my_servo.attach(9);
}

void loop() {
  digitalWrite(led, HIGH); // включаем светодиод, установив высокий уровень
  delay(1000); // ждем 1 секунду
  digitalWrite(led, LOW); // выключаем светодиод, установив низкий уровень
  delay(1000); // ждем 1 секунду

  // пробегаем все положения от 0 до 180 градусов с шагом в 1 градус
  for(pos=0; pos <= 180; pos += 1) {
    my_servo.write(pos); // перемещаем серву в положение, заданное в pos
    delay(15); // ждем 15мс, чтобы серва переместилась в требуемое положение
  }

  // вращаем серву в обратную сторону
  // угол поворота изменяется со 180 до 1, уменьшаясь с шагом 1
  for(pos=180; pos >= 0; pos -= 1) {
    my_servo.write(pos);
    delay(15);
  }
}

Для подключения множества различных сенсоров, индикаторов и исполнительных устройств к Arduino Mega2560 я использую MEGA Sensor Shield v1.2 от DFROBOT. На этой плате к каждому пину Arduino, использующемуся в качестве входа или выхода, добавлены выводы питания и земли, что очень удобно (многие датчики являются активными и требуют внешнего питания) и позволяет подключать внешние устройства, используя трехвыводной разъем. Все светодиоды продублированы на этом шилде.

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

DFROBOT MEGA Sensor Shield v1.2

Подключим сервопривод к 9 пину, загрузим скетч в Arduino и посмотрим что из этого выйдет.

Мы видим, что мигание светодиода чередуется с полным циклом вращения сервы — мигает светодиод, сервопривод делает оборот с 0 до 180 градусов, затем обратно, только после этого вновь мигает светодиод. То есть действия происходят последовательно. Но нам хотелось бы, чтобы это происходило параллельно и светодиод выполнял свой цикл мигания вне зависимости от местоположения сервы.

Каким же образом мы можем управлять временными интервалами не используя функцию delay ()?

 

Используем millis ()

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

Вместе с Arduino IDE в качестве простого примера идет скетч под названием BlinkWithoutDelay

Пример скетча BlinkWithoutDelay

Соберем простенькую схемку, как показано ниже.

Светодиод, подключенный к Arduino

Конечно, к 13 пину и так подключен светодиод, расположенный на плате Arduino, но нам для дальнейшего эксперимента потребуется именно отдельный светодиод.

Для расчета номинала токоограничивающего резистора воспользуемся законом Ома и параметрами имеющегося светодиода. У меня под рукой оказались 45º красные матовые светодиоды NationStar BT-204SED диаметром 5 мм. Прямое напряжение для них составляет 2.0 В и номинальный рабочий ток 20 мА. Считаем, что высокий уровень напряжения на выходе нашей платы составляет 5 В. Чтобы задать необходимый ток в 20 мА, производим простейшие вычисления сопротивления:

    \[R = \frac{U_R}{I_R} = \frac{5 B - 2 B}{0.02 А} = 150 \Omega \]

Значит, наш резистор должен быть примерно такого или большего номинального сопротивления. Очень часто в  различных наборах с Arduino продаются резисторы 220 Ом и в этом есть определенный смысл, ведь зачастую очень важно - помигать светодиодом  🙂

Показать/скрыть код
// номер пина с подключенным светодиодом
const int ledPin = 13;

int ledState = LOW; // состояние светодиода: ВКЛ/ВЫКЛ
long previousMillis = 0; // время, когда состояние светодиода обновлялось

long interval = 1000; // половина периода мигания (в миллисекундах)

void setup() {
  // устанавливаем цифровой пин со светодиодом в режим вывода
  pinMode(ledPin, OUTPUT);
}

void loop() {
  // выясняем не настал ли момент сменить состояние светодиода

  unsigned long currentMillis = millis(); // текущее время в миллисекундах

  if(currentMillis - previousMillis > interval) {
    // сохраняем последний момент, когда менялось состояние светодиода
    previousMillis = currentMillis;

    // изменяем состояние светодиода на противоположное
    if(ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;
    // устанавливаем высокий или низкий уровень напряжения на выходе
    // основываясь на значении переменной ledState
    digitalWrite(ledPin, ledState);
  }
}

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

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

Вместо использования delay () для задания времени мигания, в этом примере запоминается текущее состояние светодиода и момент, когда оно в последний раз изменялось. На каждой итерации цикла, используется функция millis () для определения времени, когда необходимо вновь изменить состояние светодиода.

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

Показать/скрыть код
// Эти переменные хранят временной шаблон для интервалов мигания
// и текущее состояние светодиода
int ledPin = 13; // номер пина со светодиодом
int ledState = LOW; // состояние светодиода
// последний момент времени, когда состояние светодиода изменялось
unsigned long previousMillis = 0;
long OnTime = 250; // длительность свечения светодиода (в миллисекундах)
long OffTime = 750; // светодиод не горит (в миллисекундах)

void setup() {
  // устанавливаем цифровой пин со светодиодом как ВЫХОД
  pinMode(ledPin, OUTPUT);
}

void loop() {
  // выясняем не настал ли момент сменить состояние светодиода

  unsigned long currentMillis = millis(); // текущее время в миллисекундах
  // если светодиод включен и светится больше чем надо
  if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
  {
    ledState = LOW; // выключаем
    previousMillis = currentMillis; // запоминаем момент времени
    digitalWrite(ledPin, ledState); // реализуем новое состояние
  }
  else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
  {
    ledState = HIGH; // выключаем
    previousMillis = currentMillis ; // запоминаем момент времени
    digitalWrite(ledPin, ledState); // реализуем новое состояние
  }
}

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

У нас также есть код, который следит за состоянием и решает, когда и как нужно что-то изменить. Это и есть автомат. Каждый раз, когда в цикле мы «выполняем автомат», автомат заботится об обновлении состояния.

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

Подключаем второй светодиод к Arduino

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

Показать/скрыть код
// Эти переменные хранят временной шаблон для интервалов мигания
// и текущее состояние светодиодов

int ledPin1 = 12; // номер пина со светодиодом
int ledState1 = LOW; // состояние светодиода
// последний момент времени, когда состояние светодиода изменялось
unsigned long previousMillis1 = 0;
long OnTime1 = 250; // длительность свечения светодиода (в миллисекундах)
long OffTime1 = 750; // светодиод не горит (в миллисекундах)

int ledPin2 = 13; // номер пина со светодиодом
int ledState2 = LOW; // состояние светодиода
// последний момент времени, когда состояние светодиода изменялось
unsigned long previousMillis2 = 0;
long OnTime2 = 330; // длительность свечения светодиода (в миллисекундах)
long OffTime2 = 400; // светодиод не горит (в миллисекундах)

void setup() {
 // устанавливаем цифровой пин со светодиодом как ВЫХОД
 pinMode(ledPin1, OUTPUT);
 pinMode(ledPin2, OUTPUT);
}

void loop() {
 // выясняем не настал ли момент сменить состояние светодиода

 unsigned long currentMillis = millis(); // текущее время в миллисекундах

 // конечный автомат для 1-го светодиода
 if((ledState1 == HIGH) && (currentMillis - previousMillis1 >= OnTime1))
 {
   ledState1 = LOW; // выключаем
   previousMillis1 = currentMillis; // запоминаем момент времени
   digitalWrite(ledPin1, ledState1); // реализуем новое состояние
 }
 else if ((ledState1 == LOW) && (currentMillis - previousMillis1 >= OffTime1))
 {
   ledState1 = HIGH; // выключаем
   previousMillis1 = currentMillis ; // запоминаем момент времени
   digitalWrite(ledPin1, ledState1); // реализуем новое состояние
 }
 // конечный автомат для 2-го светодиода
 if((ledState2 == HIGH) && (currentMillis - previousMillis2 >= OnTime2))
 {
   ledState2 = LOW; // выключаем
   previousMillis2 = currentMillis; // запоминаем момент времени
   digitalWrite(ledPin2, ledState2); // реализуем новое состояние
 }
 else if ((ledState2 == LOW) && (currentMillis - previousMillis2 >= OffTime2))
 {
   ledState2 = HIGH; // выключаем
   previousMillis2 = currentMillis ; // запоминаем момент времени
   digitalWrite(ledPin2, ledState2); // реализуем новое состояние
 }
}

Мы можем добавлять светодиоды в виде конечных автоматов до того момента, пока не закончится память или пины GPIO.

Все что нужно для этого — подключить новый светодиод, скопировать часть кода, реализующую конечный автомат и внести совсем уж небольшие корректировки. Но писать один и тот же код снова и снова — достаточно занудливое занятие. Должен же быть какой-то способ сделать это проще?

Конечно, существует! Есть методы программирования, которые и проще и эффективнее.

 

Используем классы

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

Реализуем тот же самый код методом объектно-ориентированного программирования (ООП).

Язык программирования Arduino — Wiring, является разновидностью C++, который поддерживает объектно-ориентированное программирование. Использовав объектно-ориентированные свойства языка, мы можем собрать вместе все переменные состояния и функциональность для мигающего светодиода в класс C++.
Это совсем не сложно сделать. Весь код уже есть. Нам просто нужно переупаковать его в класс.

Начинаем с объявления класса Flasher:

Показать/скрыть код
class Flasher
{
  // Переменные - члены класса
  // Инициализируются при запуске
  int ledPin; // номер пина со светодиодом
  long OnTime; // время включения в миллисекундах
  long OffTime; // время, когда светодиод выключен
  // Текущее состояние
  int ledState; // состояние ВКЛ/ВЫКЛ
  unsigned long previousMillis; // последний момент смены состояния
};

Далее добавим конструктор класса. Конструктор имеет тоже самое имя что и класс и предназначен для инициализации переменных.

Показать/скрыть код
// Конструктор создает экземпляр класса Flasher
// и инициализирует переменные-члены класса и состояние
public:
Flasher(int pin, long on, long off)
{
  ledPin = pin;
  pinMode(ledPin, OUTPUT);

  OnTime = on;
  OffTime = off;
  ledState = LOW;
  previousMillis = 0;
}

Наконец, мы возьмем наш цикл и преобразуем его в функцию-член класса, называемую Update (). Она иденична оригинальному void loop () — изменено только название.

Показать/скрыть код
void Update()
{
  // выясняем не настал ли момент сменить состояние светодиода

 unsigned long currentMillis = millis(); // текущее время в миллисекундах
 if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
 {
   ledState = LOW; // выключаем
   previousMillis = currentMillis; // запоминаем момент времени
   digitalWrite(ledPin, ledState); // реализуем новое состояние
 }
 else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
 {
   ledState = HIGH; // выключаем
   previousMillis = currentMillis ; // запоминаем момент времени
   digitalWrite(ledPin, ledState); // реализуем новое состояние
 }
}

Повторно скомпоновав существующий код в класс Flasher, мы инкапсулировали все переменные (состояние) и функциональность (автомат) для мигания светодиодом.

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

Больше нет необходимости повторять код конечного автомата. Нам просто нужно запросить другой экземпляр класса Flasher.

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

Показать/скрыть код
class Flasher
{
  // Переменные - члены класса
  // Инициализируются при запуске
  int ledPin; // номер пина со светодиодом
  long OnTime; // время включения в миллисекундах
  long OffTime; // время, когда светодиод выключен

  // Текущее состояние
  int ledState; // состояние ВКЛ/ВЫКЛ
  unsigned long previousMillis; // последний момент смены состояния

  // Конструктор создает экземпляр Flasher и инициализирует
  // переменные-члены класса и состояние
  public:
  Flasher(int pin, long on, long off)
  {
   ledPin = pin;
   pinMode(ledPin, OUTPUT);

   OnTime = on;
   OffTime = off;

   ledState = LOW;
   previousMillis = 0;
  }

  void Update()
  {
   // выясняем не настал ли момент сменить состояние светодиода

   unsigned long currentMillis = millis(); // текущее время в миллисекундах

   if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
   {
     ledState = LOW; // выключаем
     previousMillis = currentMillis; // запоминаем момент времени
     digitalWrite(ledPin, ledState); // реализуем новое состояние
   }
   else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
   {
     ledState = HIGH; // выключаем
     previousMillis = currentMillis ; // запоминаем момент времени
     digitalWrite(ledPin, ledState); // реализуем новое состояние
   }
  }
};

Flasher led1(11, 100, 400);
Flasher led2(12, 350, 350);
Flasher led3(13, 300, 700);

void setup()
{
}
void loop()
{
  led1.Update();
  led2.Update();
  led3.Update();
}

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

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

Теперь применим те же принципы к коду для сервоприводов и реализуем некоторое действие.

Добавим на нашу макетную плату пару сервомоторов, подключив их к пинам 9 и 10 и третий светодиод на пин 11.

Две сервы и три светодиода на Arduino

Реализуем класс Sweeper, который инкапсулирует действие вращения сервы, но использует функцию millis () для управления временем, аналогично тому, как класс Flasher делает это для светодиодов.

Также добавим функции Attach () и Detach () сопоставления сервопривода с конкретным выводом микроконтроллера.

Показать/скрыть код
class Sweeper
{
  Servo servo; // сервопривод
  int pos; // текущее положение сервы
  int increment; // увеличиваем перемещение на каждом шаге
  int updateInterval; // промежуток времени между обновлениями
  unsigned long lastUpdate; // последнее обновление положения

  public:
  Sweeper(int interval)
  {
    updateInterval = interval;
    increment = 1;
  }

  void Attach(int pin)
  {
    servo.attach(pin);
  }

  void Detach()
  {
    servo.detach();
  }
  void Update()
  {
    if((millis() - lastUpdate) > updateInterval) // время обновлять
    {
      lastUpdate = millis();
      pos += increment;
      servo.write(pos);
      Serial.println(pos);
      if ((pos >= 180) || (pos <= 0)) // конец вращения
      {
        // обратное направление
        increment = -increment;
      }
    }
  }
};

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

Каждый экземпляр  Flasher требует две строки кода:

  • Одну для объявления экземпляра
  • Одну для вызова обновления в цикле

Каждый экземпляр Sweeper требует только три строки кода:

  • Одну для объявления экземпляра
  • Одну для сопоставления с выводом при установке
  • Одну для вызова обновления в цикле

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

Показать/скрыть код
#include <Servo.h>

class Flasher
{
  // Переменные - члены класса
  // Инициализируются при запуске
  int ledPin; // номер пина со светодиодом
  long OnTime; // время включения в миллисекундах
  long OffTime; // время, когда светодиод выключен

  // Текущее состояние
  int ledState; // состояние ВКЛ/ВЫКЛ
  unsigned long previousMillis; // последний момент смены состояния

  // Конструктор создает экземпляр Flasher и инициализирует
  // переменные-члены класса и состояние
  public:
  Flasher(int pin, long on, long off)
  {
    ledPin = pin;
    pinMode(ledPin, OUTPUT);

    OnTime = on;
    OffTime = off;

    ledState = LOW;
    previousMillis = 0;
  }

  void Update()
  {
    // выясняем не настал ли момент сменить состояние светодиода

    unsigned long currentMillis = millis(); // текущее время в миллисекундах

    if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
    {
      ledState = LOW; // выключаем
      previousMillis = currentMillis; // запоминаем момент времени
      digitalWrite(ledPin, ledState); // реализуем новое состояние
    }
    else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
    {
      ledState = HIGH; // выключаем
      previousMillis = currentMillis ; // запоминаем момент времени
      digitalWrite(ledPin, ledState); // реализуем новое состояние
    }
  }
};

class Sweeper
{
  Servo servo; // сервопривод
  int pos; // текущее положение сервы
  int increment; // увеличиваем перемещение на каждом шаге
  int updateInterval; // промежуток времени между обновлениями
  unsigned long lastUpdate; // последнее обновление положения 

  public:
  Sweeper(int interval)
  {
    updateInterval = interval;
    increment = 1;
  }

  void Attach(int pin)
  {
    servo.attach(pin);
  }

  void Detach()
  {
    servo.detach();
  }

  void Update()
  {
    if((millis() - lastUpdate) > updateInterval) // время обновлять
    {
      lastUpdate = millis();
      pos += increment;
      servo.write(pos);
      Serial.println(pos);
      if ((pos >= 180) || (pos <= 0)) // конец вращения
      {
        // обратное направление
        increment = -increment;
      }
    }
  }
};

Flasher led1(11, 123, 400);
Flasher led2(12, 350, 350);
Flasher led3(13, 200, 222);

Sweeper sweeper1(15);
Sweeper sweeper2(25);

void setup()
{
  Serial.begin(9600);
  sweeper1.Attach(9);
  sweeper2.Attach(10);
} 

void loop()
{
  sweeper1.Update();
  sweeper2.Update();
  led1.Update();
  led2.Update();
  led3.Update();
}

Теперь у нас есть 5 независимых задач, работающих в режиме нон-стоп и в циклически выполняющейся функции loop () всего 5 строк кода!

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

multitasking_scheme04

В коде, представленном ниже проверяется состояние кнопки на каждом шаге цикла. led1 и sweeper2 не обновляются, если кнопка нажата.

Показать/скрыть код
#include <Servo.h>

class Flasher
{
  // Переменные - члены класса
  // Инициализируются при запуске
  int ledPin; // номер пина со светодиодом
  long OnTime; // время включения в миллисекундах
  long OffTime; // время, когда светодиод выключен

  // Текущее состояние
  int ledState; // состояние ВКЛ/ВЫКЛ
  unsigned long previousMillis; // последний момент смены состояния

  // Конструктор создает экземпляр Flasher и инициализирует
  // переменные-члены класса и состояние
  public:
  Flasher(int pin, long on, long off)
  {
    ledPin = pin;
    pinMode(ledPin, OUTPUT);

    OnTime = on;
    OffTime = off;

    ledState = LOW;
    previousMillis = 0;
  }

  void Update()
  {
    // выясняем не настал ли момент сменить состояние светодиода

    unsigned long currentMillis = millis(); // текущее время в миллисекундах

    if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
    {
      ledState = LOW; // выключаем
      previousMillis = currentMillis; // запоминаем момент времени
      digitalWrite(ledPin, ledState); // реализуем новое состояние
    }
    else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
    {
      ledState = HIGH; // выключаем
      previousMillis = currentMillis ; // запоминаем момент времени
      digitalWrite(ledPin, ledState); // реализуем новое состояние
    }
  }
};

class Sweeper
{
  Servo servo; // сервопривод
  int pos; // текущее положение сервы
  int increment; // увеличиваем перемещение на каждом шаге
  int updateInterval; // промежуток времени между обновлениями
  unsigned long lastUpdate; // последнее обновление положения 

  public:
  Sweeper(int interval)
  {
    updateInterval = interval;
    increment = 1;
  }

  void Attach(int pin)
  {
    servo.attach(pin);
  }

  void Detach()
  {
    servo.detach();
  }

  void Update()
  {
    if((millis() - lastUpdate) > updateInterval) // время обновлять
    {
      lastUpdate = millis();
      pos += increment;
      servo.write(pos);
      Serial.println(pos);
      if ((pos >= 180) || (pos <= 0)) // конец вращения
      {
        // обратное направление
        increment = -increment;
      }
   }
 }
};

Flasher led1(11, 123, 400);
Flasher led2(12, 350, 350);
Flasher led3(13, 200, 222);

Sweeper sweeper1(15);
Sweeper sweeper2(25);

void setup()
{
  Serial.begin(9600);
  sweeper1.Attach(9);
  sweeper2.Attach(10);
} 

void loop()
{
  sweeper1.Update();
  if (digitalRead(2) == HIGH)
  {
    sweeper2.Update();
    led1.Update();
  }
  led2.Update();
  led3.Update();
}

Теперь у нас три светодиода мигают в своих собственных режимах, 2 сервы вращаются независимо друг от друга. При нажатии кнопки, sweeper2 и led1 останавливаются до той поры, пока мы удерживаем кнопку нажатой.

Вот как все это выглядит у меня, что называется, «вживую».

Так как у нас нет никаких задержек в цикле, наша схема реагирует на нажатие кнопки практически мгновенно.

У нас получилось 5 независимо выполняющихся задач, управляемых при помощи простого пользовательского интерфейса (кнопка). Мы смогли избавиться от задержки, фактически блокирующей работу процессора. И наш эффективный объектно-ориентированный код оставляет много места для расширения функциональности!

 
Как вы оцениваете эту публикацию? 1 звезда2 звезды3 звезды4 звезды5 звезд (321 голосов, средняя оценка: 4,91 из 5)
Загрузка...

Вы можете пропустить чтение записи и оставить комментарий. Размещение ссылок запрещено.

159 комментариев к записи “Многозадачность на Arduino”

  1. Алексей:

    Подскажите как решить проблему. На мини про 328 сделан стробоскоп, светодиод 2мс горит, 8мс не горит. При использовании delay () все ок, строб работает как часы, но естественно нет возможности делать что-то еще. А с вашим строб выдает паузу которую видно на глаз.

    • Вся проблема в реализации функции digitalWrite (). Вот здесь я об этом писал robotosha.ru/arduino/digi...ing-arduino.html

      • Сергей:

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

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

        То есть если разделить от 0 до 100, если промежуток от 0 до 50 работает одно реле, то то от 50 до 100 другое.За ранее благодарен, ну или подскажите в каком направлении искать

    • Попробуйте вот этот код:

      int ledState = LOW; unsigned long previousMillis = 0; long OnTime = 2; long OffTime = 8; void setup() { DDRB=B10000000; } void loop() { while(1) { unsigned long currentMillis = millis(); if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime)) { ledState = LOW; previousMillis = currentMillis; PORTB=B10000000; } else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime)) { ledState = HIGH; previousMillis = currentMillis ; PORTB=B00000000; } } }

  2. АлексейМ:

    подскажите как подобно реализовать код, IR дальномер прикреплённый к серво, что бы получать с него данные к примеру на позиции 180,90,0, 2 серво работает отталкиваясь от полученных данных с IR

    • Можно сделать примерно так:

      #include <Servo.h> class Sweeper { Servo servo; // сервопривод public: int pos; // текущее положение сервы int increment; // увеличиваем перемещение на каждом шаге int updateInterval; // промежуток времени между обновлениями unsigned long lastUpdate; // последнее обновление положения public: Sweeper(int interval) { updateInterval = interval; increment = 1; } void Attach(int pin) { servo.attach(pin); } void setPos(int posn) { servo.write(posn); } void Detach() { servo.detach(); } void Update() { if((millis() - lastUpdate) > updateInterval) // время обновлять { lastUpdate = millis(); pos += increment; servo.write(pos); if ((pos >= 180) || (pos <= 0)) // конец вращения { // обратное направление increment = -increment; } } } }; int DistanceToPos (int posn) { int resultPos; // Здесь нужно вставить код для // чтения данных с дальномера и // преобразования их в положение // сервы resultPos return(resultPos); } Sweeper sweeper1(50); Sweeper sweeper2(0); void setup() { sweeper1.Attach(9); sweeper2.Attach(10); } void loop() { while(1) { sweeper1.Update(); if(sweeper1.pos==0 || sweeper1.pos==90 || sweeper1.pos==180) sweeper2.setPos(DistanceToPos(sweeper1.pos)); } }

  3. Александер:

    Добрый вечер! Подскажите пожалуйста, как реализировать код без delay (), если я нажал кнопку, то светодиод должен загорется и через 30 секунд потухнуть, пока я снова не нажал на кнопку?

    • Можно сделать так:

      int ledPin = 13; // пин со светодиодом long OnTime = 30000; // время свечения светодиода, мс int ledState = LOW; int pinButton = 2; // пин, к которому подключена кнопка unsigned long previousMillis = 0; void setup() { pinMode(ledPin, OUTPUT); } void loop() { unsigned long currentMillis; // если светодиод светится if (ledState == HIGH) { currentMillis = millis(); // проверяем сколько прошло времени, // если больше заданного - выключаем if (currentMillis-previousMillis >= OnTime) { ledState = LOW; previousMillis = 0; digitalWrite(ledPin,ledState); } } // если кнопка нажата - включаем светодиод if (digitalRead(pinButton) == HIGH) { previousMillis = currentMillis; // запоминаем время ledState = HIGH; digitalWrite(ledPin,ledState); } }

  4. Александер:

    Большое Вам спасибо за вашу помощь и за очень интересную страницу!

  5. Дмитрий:

    Здравствуйте!

    Подскажите пожалуйста, как реализовать вращение сервоприводом от 0 до 180 градусов без delay при условии, когда на аналоговом входе будет превышен некоторый порог? (в виде примера).

    • Давайте уточним. Вам нужно, чтобы при превышении некоторого порога серва производила вращение от 0 до 180 градусов даже если напряжение опять упало ниже порога или что-то нужно делать в этом случае? То есть, если порог превышен, мы перестаем отслеживать этот аналоговый вход и вращаем сервой от 0 до 180 и после того как вращение произведено, опять начинаем отслеживать вход. Или нужна какая-то более сложная логика работы?

      • Дмитрий:

        Добрый день!

        Прошу прощения за неточное формулирование.

        Необходимо следующее.

        Если на аналоговом входе не превышен некоторый порог, то сервопривод должен поворачиваться от 0 до 180 градусов до остановки. При превышении некоторого порога на этом же аналоговом входе, сервопривод должен начать обратное вращение от 180 до 0 градусов до остановки. Каким параметром можно регулировать скорость вращения сервопривода, не применяя delay.

        • Дмитрий, воспользуйтесь кодом к комментарию вопроса АлексеяМ, приведенного чуть выше. Вместо числа 50 в 59-й строке

          Sweeper sweeper1 (50);

          установите требуемую задержку в миллисекундах (время через которое будет изменяться положение сервопривода, фактически определяет скорость вращения сервопривода). Также для отслеживания порогового уровня threshold (которое необходимо предварительно задать) на аналоговом входе (например, с номером 3) нужно изменить функцию Update класса Sweeper:

          void Update() { if((millis() - lastUpdate) > updateInterval) // время обновлять { lastUpdate = millis(); // считываем значение с аналогового порта 3 и если оно // меньше некоторого порогового значения threshold // вращаем серву от 0 до 180 if ((analogRead(3) < threshold) && (pos<180)) { pos += increment; servo.write(pos); } // если же значение превышено - вращаемся до 0 // в противоположную сторону if ((analogRead(3) >= threshold) && (pos>0)) { pos -= increment; servo.write(pos); } } }

          • Дмитрий:

            Андрей спасибо большое за разъяснение! Всё понятно!

  6. BiHiTRiLL:

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

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

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

      class Flasher { // Переменные - члены класса // Инициализируются при запуске int ledPin; // номер пина со светодиодом long StartDelay; // задержка при первом запуске long OnTime; // время включения в миллисекундах long OffTime; // время, когда светодиод выключен // Текущее состояние boolean FlagStart; // флаг первого запуска int ledState; // состояние ВКЛ/ВЫКЛ unsigned long previousMillis; // последний момент смены состояния // Конструктор создает экземпляр Flasher и инициализирует // переменные-члены класса и состояние public: Flasher(int pin, long pause, long on, long off) { ledPin = pin; pinMode(ledPin, OUTPUT); StartDelay = pause; OnTime = on; OffTime = off; FlagStart = true; ledState = LOW; previousMillis = 0; } void Update() { unsigned long currentMillis = millis(); // текущее время в миллисекундах // для первого запуска заданное время ничего не делаем // если же запуск не первый, то мигаем светодиодом if (FlagStart) { if(currentMillis - previousMillis >= StartDelay) FlagStart = false; } else { // выясняем не настал ли момент сменить состояние светодиода if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime)) { ledState = LOW; // выключаем previousMillis = currentMillis; // запоминаем момент времени digitalWrite(ledPin, ledState); // реализуем новое состояние } else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime)) { ledState = HIGH; // включаем previousMillis = currentMillis ; // запоминаем момент времени digitalWrite(ledPin, ledState); // реализуем новое состояние } } } }; // светодиод на 13 пине (встроенный) // задержка при запуске - 10с, // время включения - 0.3с, выключения - 0.7с Flasher led1(13, 10000, 300, 700); void setup() { } void loop() { led1.Update(); }

  7. BiHiTRiLL:

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

    Например:

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

    Flasher led1(11, 1000, 1000, 2000); Flasher led2(12, 2000, 1000, 2000); Flasher led3(13, 3000, 1000, 2000);

    но она не работает. Первая и вторая группы включаются и выключаются синхронно.

    если задержка 0, то вообще получается бред:

    Flasher led1(11, 0, 1000, 2000); Flasher led2(12, 1000, 1000, 2000); Flasher led3(13, 2000, 1000, 2000);

    в этом случае одинаково мигают все три группы.

    Эмпирическим путём нашел как последовательно включать три группы:

    Flasher led1(11, 1000, 1000, 2000); Flasher led2(12, 3000, 1000, 2000); Flasher led3(13, 4000, 1000, 2000);

    Но логики работы, хоть убейте, не вижу.

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

    • Накосячил я чуток 🙂 Забыл после стартовой паузы зажечь светодиод. Нужно код в строке 40 заменить на:

      if(currentMillis - previousMillis >= StartDelay) { FlagStart = false; ledState = HIGH; // включаем светодиод digitalWrite(ledPin, ledState); // реализуем новое состояние previousMillis = currentMillis ; // запоминаем момент времени }

      Должно заработать нормально и с первым вашим вариантом.

  8. BiHiTRiLL:

    Спасибо большое! Всё отлично работает! Ща буду адаптировать для своего проекта.

  9. Петр_К:

    Андрей, подскажите, пожалуйста...

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

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

    Заранее спасибо.

    • Петр, у Дмитрия точно такая же задача — постоянное вращение от 0 до 180 и обратно. Воспользуйтесь его кодом. С порогами только разберитесь 🙂 — а то вы хотите при превышении порога вращаться и влево и вправо, получается некоторая неопределенность. Или я что-то не так понял.

      • Петр_К:

        серво движется из 0 в 180 и обратно и т.д. (0-180-0-180-0-180 до бесконечности)

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

        • Пример: серва вращается направо, напряжение превысило некоторый порог, мы меняем направление вращения, но напряжение на входе не падает. Что делать в этом случае? Опять менять направление вращения? Если напряжение будет опять превышать уровень, то мы увидим серву, которая дергается на месте, постоянно меняя направление вращения. Лучше сделать два условия: первое — если напряжение ВЫШЕ некоторого уровня вращаемся в одну сторону, если напряжение НИЖЕ некоторого уровня вращаемся в другую сторону. По сути это будут два состояния в зависимости от которых будет меняться направление вращения.

          • Петр_К:

            теоретически, ситуации когда «мы меняем направление вращения, но напряжение на входе не падает» быть не должно... )))

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

    void Update() { if((millis() - lastUpdate) > updateInterval) // время обновлять { lastUpdate = millis(); // считываем значение с аналогового порта 3 и если оно // превышает порог threshold - изменяем направление вращения if((analogRead(3)>threshold)) increment = -increment; // для крайних положений if(pos==180) increment = - abs(increment); // уменьшаем if(pos==0) increment = abs(increment); // увеличиваем pos += increment; servo.write(pos); } }

  11. Петр_К:

    Спасибо.

  12. Николай:

    Здравствуйте.

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

    Когда значения несколько миллсекунд-не страшно, а мне надо, чтоб,например диод работал 3 секунды и был выключен 15 секунд. А тут сначала идет пауза в 15 сек, и только потом срабатывает.

    Спасибо заранее.

    • Николай, здравствуйте

      Нужно внести две правки в код:

      1. Изменить в конструкторе класса Flasher значение переменной ledState на HIGH:

      public: Flasher(int pin, long pause, long on, long off) { ledPin = pin; pinMode(ledPin, OUTPUT); StartDelay = pause; OnTime = on; OffTime = off; FlagStart = true; ledState = HIGH; // сначала включаем светодиод previousMillis = 0; }

      2. В условии, которое выполняется при первом запуске нужно перевести светодиод в это состояние:

      if (FlagStart) { digitalWrite(ledPin, ledState); // переводим светодиод в начальное состояние (HIGH) if(currentMillis - previousMillis >= StartDelay) FlagStart = false; } else { ... }

      Экземпляр класса в вашем случае создается, используя такие параметры:

      Flasher led1(13, 0, 3000, 15000);

      стартовая пауза — 0 мс, время свечения — 3 c, в выключенном состоянии — 15 с

  13. denn:

    Андрей, подскажите, пожалуйста

    возможно ли сделать что бы время между вспышками не расползалось?

    взял ваш пример для трех светодиодов упакованный в класс.

    подставил в свои вводные

    Flasher led1(12, 10, 1490); Flasher led2(13, 10, 990);

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

    led1 отстает

    пробовал вместо >= поставить ==

    не помогает.

    так же заключил в цикл while () как из примера про быстродействие. то же результата не принесло.

    как победить?

    • В текущем коде погрешность определения времени ± 1 мс. Поэтому, постепенно происходит накопление ошибки. Можно увеличить точность, используя вместо функции millis () функцию micros (), которая является счетчиком микросекунд. Рассинхронизация сохранится, но сократится в сотни раз. Для этого, в коде нужно заменить millis () на micros () и создавать экземпляры класса используя микросекунды:

      Flasher led1(12, 10000, 1490000); Flasher led2(13, 10000, 990000);

      Нужно помнить, что счетчики millis () и micros () имеют тип unsigned long (целые значения от 0 до 2^32-1) и поэтому периодически происходит их переполнение (обнуление счетчика). Для millis () этот период составляет 50 дней, для micros () — около 70 минут.

      Для микроконтроллеров, работающих на частоте 16 МГц разрешение функции micros () составляет 4 мкс, для 8 МГц микроконтроллеров — 8 мкс. Это означает, что результат, возвращаемый функцией micros () всегда будет кратен 4 (8).

      В программе дополнительно нужно будет отследить момент обнуления счетчика.

      • denn:

        а не подскажете как отследить переполнение?

        я про переполнение читал, вот только как построить код пока не понял.

        • Приведу пример метода Update с использованием функции millis, в котором отслеживается переполнение счетчика. Для micros — делается аналогично.

          class Flasher { const unsigned long maxValue = 4294967295UL; // максимальное значение счетчика ... void Update() { // выясняем не настал ли момент сменить состояние светодиода unsigned long currentMillis = millis(); // текущее время в миллисекундах unsigned long difMillis=0; // если счетчик не сбросился (обычная ситуация) if (currentMillis >= previousMillis) { if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime)) { ledState = LOW; // выключаем previousMillis = currentMillis; // запоминаем момент времени digitalWrite(ledPin, ledState); // реализуем новое состояние } else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime)) { ledState = HIGH; // включаем previousMillis = currentMillis ; // запоминаем момент времени digitalWrite(ledPin, ledState); // реализуем новое состояние } } else { // нам нужно добавить к текущему моменту остаток, который // счетчик "натикал" до сброса difMillis = maxValue - previousMillis; // вычисляем остаток if((ledState == HIGH) && (currentMillis + difMillis >= OnTime)) { ledState = LOW; // выключаем previousMillis = currentMillis; // запоминаем момент времени digitalWrite(ledPin, ledState); // реализуем новое состояние } else if ((ledState == LOW) && (currentMillis + difMillis >= OffTime)) { ledState = HIGH; // включаем previousMillis = currentMillis ; // запоминаем момент времени digitalWrite(ledPin, ledState); // реализуем новое состояние } } } };

  14. Дмитрий:

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

    Помогите, пожалуйста.

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

      #include <Servo.h> class Sweeper { Servo servo; // сервопривод int pos; // текущее положение сервы int increment; // увеличиваем перемещение на каждом шаге int updateInterval; // промежуток времени между обновлениями unsigned long lastUpdate; // последнее обновление положения long OffTime; // время, когда сервопривод выключен boolean servoState; // состояние ВКЛ (true)/ВЫКЛ (false) public: Sweeper(int interval, long off) { updateInterval = interval; increment = 1; // шаг вращения OffTime = off; //задаем время паузы между вращением servoState = true; // серво в режиме вращения } void Attach(int pin) { servo.attach(pin); } void Detach() { servo.detach(); } void Update() { unsigned long currentMillis = millis(); // текущее время в миллисекундах if(servoState && ((currentMillis - lastUpdate) > updateInterval)) // время обновлять { lastUpdate = currentMillis; pos += increment; servo.write(pos); Serial.println(pos); if ((pos >= 180) || (pos <= 0)) // конец вращения { increment = -increment; // обратное направление servoState=false; // выключаем режим вращения } } else if (!servoState && (currentMillis - lastUpdate >= OffTime)) { lastUpdate = millis(); // запоминаем момент времени servoState = true; // включаем режим вращения } } }; Sweeper sweeper1(15, 10000); // время обновления - 15мс, пауза - 10 секунд Sweeper sweeper2(25, 5000); // время обновления - 25мс, пауза - 5 секунд void setup() { Serial.begin(9600); sweeper1.Attach(9); // первая серва - 9 вывод Arduino sweeper2.Attach(10); // первая серва - 10 вывод Arduino } void loop() { sweeper1.Update(); sweeper2.Update(); }

      • denn:

        так быстро отвечаете, класс!

      • Дмитрий:

        Спасибо огромное! Я еще сделал вставку в строке 53, чтобы серва не жужжала во время паузы.

        53 else if ((pos >= 180) || (pos <= 0)) { 54 servo.detach(); 55 }

        Еще раз спасибо!

        • Полезное вы дополнение сделали.

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

          • Дмитрий:

            Спасибо за совет с кодом. Пытаюсь дальше связать свой скетч.

          • Дмитрий:

            Здравствуйте. Обращаюсь к вам еще раз. Задача: Нужно, чтобы серва работала по вашему коду с паузой, а параллельно с ней должен включаться светодиод при достижении определенной температуры (датчик DHT 22).

            class Flasher { // Переменные - члены класса // Инициализируются при запуске int ledPin; // номер пина со светодиодом long OnTemp; // время включения в миллисекундах long OffTemp; // время, когда светодиод выключен // Текущее состояние int ledState; // состояние ВКЛ/ВЫКЛ unsigned long previousMillis; // последний момент смены состояния // Конструктор создает экземпляр Flasher и инициализирует // переменные-члены класса и состояние public: Flasher(int pin, long on, long off) { ledPin = pin; pinMode(ledPin, OUTPUT); OnTemp = on; OffTemp = off; ledState = LOW; previousMillis = 0; } void Update() { // выясняем не настал ли момент сменить состояние светодиода float h = dht.readHumidity(); float t = dht.readTemperature(); unsigned long currentMillis = millis(); // текущее время в миллисекундах if((ledState == HIGH) && (currentMillis - previousMillis >= OnTemp)) { if (t >= 24) { ledState = LOW; // выключаем } else { ledState = HIGH; } previousMillis = currentMillis; // запоминаем момент времени digitalWrite(ledPin, ledState); // реализуем новое состояние } else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTemp)) { if (t < 24) { ledState = HIGH; // выключаем } else { ledState = LOW; } previousMillis = currentMillis ; // запоминаем момент времени digitalWrite(ledPin, ledState); // реализуем новое состояние } } };

            Когда связываю два кода и светодиод и серва ведут себя как попало. Пытался вписать код для светодиода в ваш Flasher — ничего не получилось. Скажите, пожалуйста, что нужно сделать?

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

    void loop() { sweeper1.Update(); sweeper2.Update(); float t = dht.readTemperature(); // считываем значение температуры if (t<24) digitalWrite(13, HIGH); // если значение < 24 включаем светодиод на 13 пине else digitalWrite(13, LOW); // если значение >=24 - выключаем светодиод на 13 пине }

    • Дмитрий:

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

  16. Дмитрий, что-то где-то у вас не так. У меня нет датчика температуры — я его заменил на переменный резистор. Светодиод включается при достижении заданного значения, ниже этого значения — выключается. Серва вращается с паузой независимо от светодиода. Полный вариант кода:

    #include <Servo.h> class Sweeper { Servo servo; // сервопривод int pos; // текущее положение сервы int increment; // увеличиваем перемещение на каждом шаге int updateInterval; // промежуток времени между обновлениями unsigned long lastUpdate; // последнее обновление положения long OffTime; // время, когда сервопривод выключен boolean servoState; // состояние ВКЛ (true)/ВЫКЛ (false) public: Sweeper(int interval, long off) { updateInterval = interval; increment = 1; // шаг вращения OffTime = off; //задаем время паузы между вращением servoState = true; // серво в режиме вращения } void Attach(int pin) { servo.attach(pin); } void Detach() { servo.detach(); } void Update() { unsigned long currentMillis = millis(); // текущее время в миллисекундах if(servoState && ((currentMillis - lastUpdate) > updateInterval)) // время обновлять { lastUpdate = currentMillis; pos += increment; servo.write(pos); Serial.println(pos); if ((pos >= 180) || (pos <= 0)) // конец вращения { increment = -increment; // обратное направление servoState=false; // выключаем режим вращения } } else if (!servoState && (currentMillis - lastUpdate >= OffTime)) { lastUpdate = millis(); // запоминаем момент времени servoState = true; // включаем режим вращения } } }; Sweeper sweeper1(15, 1000); // время обновления - 15мс, пауза - 1 секунда void setup() { Serial.begin(9600); // устанавливаем скорость передачи данных sweeper1.Attach(9); // серва - 9 вывод Arduino pinMode(13, OUTPUT); // устанавливаем 13 вывод в режиме выхода } void loop() { int temp = analogRead(1); // читаем значение на аналоговом пине 1 if (temp>=700) digitalWrite(13, HIGH); // при превышении порога включаем светодиод else digitalWrite(13,LOW); // иначе - выключаем светодиод Serial.println(temp); // выводим значение в порт - для контроля sweeper1.Update(); }

  17. Видео с результатом работы этого скетча:

    • Дмитрий:

      Здравствуйте. С потенциометром и датчиком дождя все работает. Подключенный датчик температуры сам по себе ничем не мешает. Проблема возникает (серва просто крутится) при добавлении строчки в void loop

      float t = dht.readTemperature();

      Может быть проблема с переменной float. Все дело в том, что нужно включение и выключение в диапазоне температур 38,2-38,8.

      Может быть есть какое-то решение данной проблемы.

      Подключаю считывание данный датчика к порту D3.

      • Дмитрий, проблема, по всей видимости, в библиотеке DHT. Там много использований функции delay, во время выполнения которой микроконтроллер перестает обрабатывать другие события (я вначале статьи про это писал). Нужно править библиотеку. Я датчик DHT11 себе купил, пока жду поступления. Как придет, попробую разобраться с проблемой и вам помочь.

        • Дмитрий:

          Андрей, действительно в библиотеке есть четыре delay. Пользуюсь этой библиотекой

          /* DHT library MIT license written by Adafruit Industries */ #include "DHT.h" DHT::DHT(uint8_t pin, uint8_t type) { _pin = pin; _type = type; firstreading = true; } void DHT::begin(void) { // set up the pins! pinMode(_pin, INPUT); digitalWrite(_pin, HIGH); _lastreadtime = 0; } float DHT::readTemperature(void) { float f; if (read()) { switch (_type) { case DHT11: f = data[2]; return f; case DHT22: case DHT21: f = data[2] & 0x7F; f *= 256; f += data[3]; f /= 10; if (data[2] & 0x80) f *= -1; return f; } } Serial.print("Read fail"); return NAN; } float DHT::readHumidity(void) { float f; if (read()) { switch (_type) { case DHT11: f = data[0]; return f; case DHT22: case DHT21: f = data[0]; f *= 256; f += data[1]; f /= 10; return f; } } Serial.print("Read fail"); return NAN; } boolean DHT::read(void) { uint8_t laststate = HIGH; uint8_t counter = 0; uint8_t j = 0, i; unsigned long currenttime; // pull the pin high and wait 250 milliseconds digitalWrite(_pin, HIGH); delay(250); currenttime = millis(); if (currenttime < _lastreadtime) { // ie there was a rollover _lastreadtime = 0; } if (!firstreading && ((currenttime - _lastreadtime) < 2000)) { return true; // return last correct measurement //delay(2000 - (currenttime - _lastreadtime)); } firstreading = false; /* Serial.print("Currtime: "); Serial.print(currenttime); Serial.print(" Lasttime: "); Serial.print(_lastreadtime); */ _lastreadtime = millis(); data[0] = data[1] = data[2] = data[3] = data[4] = 0; // now pull it low for ~20 milliseconds pinMode(_pin, OUTPUT); digitalWrite(_pin, LOW); delay(20); cli(); digitalWrite(_pin, HIGH); delayMicroseconds(40); pinMode(_pin, INPUT); // read in timings for ( i=0; i= 4) && (i%2 == 0)) { // shove each bit into the storage bytes data[j/8] < 6) data[j/8] |= 1; j++; } } sei(); /* Serial.println(j, DEC); Serial.print(data[0], HEX); Serial.print(", "); Serial.print(data[1], HEX); Serial.print(", "); Serial.print(data[2], HEX); Serial.print(", "); Serial.print(data[3], HEX); Serial.print(", "); Serial.print(data[4], HEX); Serial.print(" =? "); Serial.println(data[0] + data[1] + data[2] + data[3], HEX); */ // check we read 40 bits and that the checksum matches if ((j >= 40) && (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) ) { return true; } return false; }

          • Дмитрий:

            Нашел вот эту библиотеку

            /****************************************************************** DHT Temperature & Humidity Sensor library for Arduino. Features: - Support for DHT11 and DHT22/AM2302/RHT03 - Auto detect sensor model - Very low memory footprint - Very small code <a href="http://www.github.com/markruys/arduino-DHT">www.github.com/markruys/arduino-DHT</a> Written by Mark Ruys, mark@paracas.nl. BSD license, check license.txt for more information. All text above must be included in any redistribution. Datasheets: - <a href="http://www.micro4you.com/files/sensor/DHT11.pdf">www.micro4you.com/files/sensor/DHT11.pdf</a> - <a href="http://www.adafruit.com/datasheets/DHT22.pdf">www.adafruit.com/datasheets/DHT22.pdf</a> - <a href="http://dlnmh9ip6v2uc.cloudfront.net/datasheets/Sensors/Weather/RHT03.pdf">dlnmh9ip6v2uc.cloudfront....eather/RHT03.pdf</a> - <a href="http://meteobox.tk/files/AM2302.pdf">meteobox.tk/files/AM2302.pdf</a> Changelog: 2013-06-10: Initial version 2013-06-12: Refactored code 2013-07-01: Add a resetTimer method ******************************************************************/ #include "DHT.h" void DHT::setup(uint8_t pin, DHT_MODEL_t model) { DHT::pin = pin; DHT::model = model; DHT::resetTimer(); // Make sure we do read the sensor in the next readSensor() if ( model == AUTO_DETECT) { DHT::model = DHT22; readSensor(); if ( error == ERROR_TIMEOUT ) { DHT::model = DHT11; // Warning: in case we auto detect a DHT11, you should wait at least 1000 msec // before your first read request. Otherwise you will get a time out error. } } } void DHT::resetTimer() { DHT::lastReadTime = millis() - 3000; } float DHT::getHumidity() { readSensor(); return humidity; } float DHT::getTemperature() { readSensor(); return temperature; } #ifndef OPTIMIZE_SRAM_SIZE const char* DHT::getStatusString() { switch ( error ) { case DHT::ERROR_TIMEOUT: return "TIMEOUT"; case DHT::ERROR_CHECKSUM: return "CHECKSUM"; default: return "OK"; } } #else // At the expense of 26 bytes of extra PROGMEM, we save 11 bytes of // SRAM by using the following method: prog_char P_OK[] PROGMEM = "OK"; prog_char P_TIMEOUT[] PROGMEM = "TIMEOUT"; prog_char P_CHECKSUM[] PROGMEM = "CHECKSUM"; const char *DHT::getStatusString() { prog_char *c; switch ( error ) { case DHT::ERROR_CHECKSUM: c = P_CHECKSUM; break; case DHT::ERROR_TIMEOUT: c = P_TIMEOUT; break; default: c = P_OK; break; } static char buffer[9]; strcpy_P(buffer, c); return buffer; } #endif void DHT::readSensor() { // Make sure we don't poll the sensor too often // - Max sample rate DHT11 is 1 Hz (duty cicle 1000 ms) // - Max sample rate DHT22 is 0.5 Hz (duty cicle 2000 ms) unsigned long startTime = millis(); if ( (unsigned long)(startTime - lastReadTime) < (model == DHT11 ? 999L : 1999L) ) { return; } lastReadTime = startTime; temperature = NAN; humidity = NAN; // Request sample digitalWrite(pin, LOW); // Send start signal pinMode(pin, OUTPUT); if ( model == DHT11 ) { delay(18); } else { // This will fail for a DHT11 - that's how we can detect such a device delayMicroseconds(800); } pinMode(pin, INPUT); digitalWrite(pin, HIGH); // Switch bus to receive data // We're going to read 83 edges: // - First a FALLING, RISING, and FALLING edge for the start bit // - Then 40 bits: RISING and then a FALLING edge per bit // To keep our code simple, we accept any HIGH or LOW reading if it's max 85 usecs long word rawHumidity; word rawTemperature; word data; for ( int8_t i = -3 ; i 90 ) { error = ERROR_TIMEOUT; return; } } while ( digitalRead(pin) == (i & 1) ? HIGH : LOW ); if ( i >= 0 && (i & 1) ) { // Now we are being fed our 40 bits data < 30 ) { data |= 1; // we got a one } } switch ( i ) { case 31: rawHumidity = data; break; case 63: rawTemperature = data; data = 0; break; } } // Verify checksum if ( (byte)(((byte)rawHumidity) + (rawHumidity >> 8) + ((byte)rawTemperature) + (rawTemperature >> 8)) != data ) { error = ERROR_CHECKSUM; return; } // Store readings if ( model == DHT11 ) { humidity = rawHumidity >> 8; temperature = rawTemperature >> 8; } else { humidity = rawHumidity * 0.1; if ( rawTemperature & 0x8000 ) { rawTemperature = -(int16_t)(rawTemperature & 0x7FFF); } temperature = ((int16_t)rawTemperature) * 0.1; } error = ERROR_NONE; }

            Все заработало.

            Спасибо большое за помощь и за направление моих мыслей в нужную сторону!

  18. Дмитрий, это хорошо, что заработало. Рад помочь.

  19. Геннадий:

    Здравствуйте Андрей. Очень понравилась ваша статья. Загрузил скетч с тремя светодиодами. Всё работает отлично. Но не смог добавить в код возможность одному из светодиодов моргать двумя вспышками. Думал так получится:

    Flasher led1(11, 50, 500); Flasher led1(11, 50, 500); Flasher led2(12, 50, 1000); Flasher led3(13, 50, 2000); void setup() {} void loop() { led1.Update(); led1.Update(); led2.Update(); led3.Update(); }

    но, не работает. Не подскажите как можно реализовать?

  20. Геннадий, ваша задумка реализуется чуть сложнее 🙂 . Для дважды мигающего светодиода нам нужно отслеживать уже четыре события: длительность первой и второй вспышек и промежутки времени между ними. Функция Update в классе Flasher отслеживала только одну вспышку и паузу после нее, поэтому, можно модифицировать класс Flasher, добавив в него обработку еще двух событий. Для этого реализуем новый класс DoubleFlasher. Пример скетча с двумя различными вспышками одного светодиода:

    class DoubleFlasher { // Переменные - члены класса // Инициализируются при запуске int ledPin; // номер пина со светодиодом long OnTime1; // время 1-го включения светодиода в миллисекундах long OffTime1; // время первой паузы между включениями long OnTime2; // время 2-го включения светодиода в миллисекундах long OffTime2; // время второй пацзы между включениями // Текущее состояние int ledState; // состояние 1,2,3,4 int state; // светодиод ВКЛ/ВЫКЛ unsigned long previousMillis; // последний момент смены состояния // Конструктор создает экземпляр DoubleFlasher и инициализирует // переменные-члены класса и состояние public: DoubleFlasher(int pin, long on1, long off1, long on2, long off2) { ledPin = pin; pinMode(ledPin, OUTPUT); OnTime1 = on1; OffTime1 = off1; OnTime2 = on2; OffTime2 = off2; ledState = 1; state = HIGH; previousMillis = 0; } void Update() { // выясняем не настал ли момент сменить состояние светодиода unsigned long currentMillis = millis(); // текущее время в миллисекундах if((ledState == 1) && (currentMillis - previousMillis >= OnTime1)) { ledState = 2; state = LOW; //выключаем previousMillis = currentMillis; // запоминаем момент времени digitalWrite(ledPin, state); // реализуем новое состояние Serial.println(ledState); } else if ((ledState == 2) && ((currentMillis - previousMillis) >= OffTime1)) { ledState = 3; state = HIGH; //включаем previousMillis = currentMillis; // запоминаем момент времени digitalWrite(ledPin, state); // реализуем новое состояние Serial.println(ledState); } else if ((ledState == 3) && ((currentMillis - previousMillis) >= OnTime2)) { ledState = 4; state = LOW; //выключаем previousMillis = currentMillis; // запоминаем момент времени digitalWrite(ledPin, state); // реализуем новое состояние Serial.println(ledState); } else if ((ledState == 4) && ((currentMillis - previousMillis) >= OffTime2)) { ledState = 1; state = HIGH; //включаем previousMillis = currentMillis; // запоминаем момент времени digitalWrite(ledPin, state); // реализуем новое состояние Serial.println(ledState); } } }; // светодиод на 13 пине, 100 и 120 мс - длительность вспышек, 150 мс и 1 с - паузы DoubleFlasher led1(13, 100, 150, 120, 1000); void setup() {} void loop() { led1.Update(); }

  21. Дмитрий:

    Здравствуйте Андрей .Очень понравилась ваша структура кода,если вас не затруднит подскажите пожалуйста как можно реализовать управление сервоприводом двумя кнопками в шаблоне вашего кода. Т.е пока нажата одна кнопка сервопривод двигается в одно направление,кнопку отпустили сервопривод остановился и инверсия.С delay это делается просто, а вот с millis «танцую с бубном» и пока не особо получается.

    • Реализуем новый класс SweeperKeys для управления серво при помощи двух кнопок:

      #include <Servo.h> class SweeperKeys { Servo servo; // сервопривод int pos; // текущее положение сервы int increment; // увеличиваем перемещение на каждом шаге int updateInterval; // промежуток времени между обновлениями unsigned long lastUpdate; // последнее обновление положения public: SweeperKeys(int interval) { updateInterval = interval; increment = 1; // шаг приращения положения сервы pos=90; // устанавливаем серву в среднее положение servo.write(pos); } void Attach(int pin) { servo.attach(pin); } void Detach() { servo.detach(); } void Update() { if(((millis() - lastUpdate) > updateInterval) && (digitalRead(2)==HIGH)) { // если время обновлять и нажата кнопка на пине 2 - увеличиваем положение сервы lastUpdate = millis(); if (pos<180) {pos += increment; servo.write(pos);} } else if(((millis() - lastUpdate) > updateInterval) && (digitalRead(3)==HIGH)) { // если время обновлять и нажата кнопка на пине 3 - уменьшаем положение сервы lastUpdate = millis(); if (pos>0) {pos -= increment; servo.write(pos);} } } }; SweeperKeys sweeper1(15); void setup() { sweeper1.Attach(9); } void loop() { sweeper1.Update(); }

      • Дмитрий:

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

  22. Дмитрий:

    Доброй ночи, Андрей.

    Помогите решить следующую задачу:

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

    Спасибо!

    • Дмитрий, здравствуйте! Для решения этой задачи нам нужно отслеживать два состояния: один для режима мигания и второй для режима паузы. Если мы находимся в режиме мигания, то тут еще два состояния: когда светодиод горит и не горит. В режиме паузы нам нужно отслеживать нажатие кнопки и после каждого нажатия изменять ее состояние (нажатие кнопки включает светодиод, повторное нажатие — выключает его). С кнопкой есть проблема, связанная с, так называемым, дребезгом контактов. Здесь я боролся с дребезгом, используя программный способ. Кнопка подключена ко второму цифровому пину. Также я здесь отслеживаю переполнение счетчика миллисекунд (я не проверяю условие переполнения счетчика для кнопки, но при желании, по аналогии, вы самостоятельно сможете добавить пару строк кода). Мой вариант скетча:

      class PauseFlasher { const unsigned long maxValue = 4294967295UL; // максимальное значение счетчика // Переменные - члены класса // Инициализируются при запуске int ledPin; // номер пина со светодиодом long OnTime; // время включения в миллисекундах long OffTime; // время, когда светодиод выключен long PeriodFlashing; // длительность мигания long PeriodPause; // длительность паузы // Текущее состояние int ledState; // состояние ВКЛ/ВЫКЛ int ledFlashing; // состояние мигания/паузы int keyState, oldKeyState; // состояние, изменяемое кнопкой unsigned long previousMillis; // последний момент смены состояния unsigned long previousFlashingMillis; // последний момент смены режима unsigned long previousKeyMillis; // последний момент смены состояния кнопки // Конструктор создает экземпляр Flasher и инициализирует // переменные-члены класса и состояние public: PauseFlasher(int pin, long on, long off, long flashing, long pause) { ledPin = pin; pinMode(ledPin, OUTPUT); OnTime = on; OffTime = off; PeriodFlashing = flashing; PeriodPause = pause; ledState = HIGH; ledFlashing = HIGH; // режим мигания/паузы; keyState = LOW; oldKeyState = HIGH; previousMillis = 0; // время смены режима при мигании previousFlashingMillis = 0; // время последней паузы/включения previousKeyMillis = 0; } void Update() { // выясняем не настал ли момент сменить состояние светодиода unsigned long currentMillis = millis(); // текущее время в миллисекундах unsigned long temp_time=0, temp_time1=0; // вспомогательные переменные // рассчитываем промежуток времени между прошлой сменой состояния и текущей // в зависимости от того, произошло переполнение счетчика или нет if (currentMillis >= previousFlashingMillis) temp_time1 = currentMillis - previousFlashingMillis; else temp_time1 = maxValue - previousFlashingMillis + currentMillis; // переключаемся между режимами мигания и паузой if ((ledFlashing==HIGH) && (temp_time1 >= PeriodFlashing)) { ledFlashing = LOW; // выключаем previousFlashingMillis = currentMillis; // запоминаем момент времени digitalWrite(ledPin, ledFlashing); // реализуем новое состояние keyState = LOW; oldKeyState= HIGH; } if ((ledFlashing==LOW) && (temp_time1 >= PeriodPause)) { ledFlashing = HIGH; // включаем previousFlashingMillis = currentMillis ; // запоминаем момент времени digitalWrite(ledPin, ledFlashing); // реализуем новое состояние keyState = LOW; oldKeyState= HIGH; } // для режима мигания if(ledFlashing==HIGH) { // рассчитываем промежуток времени между прошлой сменой состояния и текущей // в зависимости от того, произошло переполнение счетчика или нет if (currentMillis >= previousMillis) temp_time = currentMillis - previousMillis; else temp_time = maxValue - previousMillis + currentMillis; if((ledState == HIGH) && (temp_time >= OnTime)) { ledState = LOW; // выключаем previousMillis = currentMillis; // запоминаем момент времени digitalWrite(ledPin, ledState); // реализуем новое состояние } else if ((ledState == LOW) && (temp_time >= OffTime)) { ledState = HIGH; // включаем previousMillis = currentMillis ; // запоминаем момент времени digitalWrite(ledPin, ledState); // реализуем новое состояние } } if(ledFlashing==LOW) { // для режима паузы // если нажата кнопка - меняем состояние светодиода (ВКЛ/ВЫКЛ) // программно боремся с дребезгом контактов if (digitalRead(2)==HIGH) { if(millis()-previousKeyMillis>100) { previousKeyMillis = millis(); if(keyState==oldKeyState) { if(keyState==LOW) keyState=HIGH; else if(keyState==HIGH) keyState=LOW; digitalWrite(ledPin, keyState); } } } else oldKeyState=keyState; } } }; // светодиод - на 13 пине // режим мигания - 5000мс, режим паузы - 15000мс // в режиме мигания - 100мс светодиод горит, 200мс - не горит PauseFlasher led1(13, 100, 200, 5000, 15000); void setup(){} void loop() { led1.Update(); }

      • Дмитрий:

        Добрый день, Андрей.

        Спасибо за ответ.

        Как вы считаете, если использовать библиотеку TimerOne — которая будет управлять таймером включения/выключения светодиода раз в 30 минут, и в loop писать остальной код, это упростит или усложнит работу процессора, по сравнению с тем вариантом, который вы предложили?

        Спасибо!

        • Использование аппаратного прерывания по таймеру (как в TimerOne) будет более предпочтительно. Нужно учесть только, что функция millis использует Timer0 (подробнее). Если вы начнете использовать timer0, изменяя период его прерываний, у вас millis перестанет работать как надо.

          • Дмитрий:

            Андрей, спасибо за ответ.

            В процессе работы с библиотекой Timer0, появилась необходимость, внести в следующий код правки:

            void timerIsr() { digitalWrite( 13, digitalRead( 13 ) ^ 1 ); }

            Правки связаны со следующим. Есть подключаемая библиотека, которая пиликает тональными сигналами разной длины. Эти сигналы я описываю в основной программе. При этом эти сигналы должны пропиликать каждые 30 минут. Тональники выходят на 6 пин и пока пиликают они горит светодиод на 13 пине.

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

            Спасибо!

  23. Дмитрий:

    Андрей, уже сам придумал ))))

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

  24. unsigned long previousMillis = 0; unsigned long period = 1800000; // 30 минут в миллисекундах ... if(millis()-previousMillis >= period) { previousMillis = millis(); //запоминаем текущий момент ... здесь ваш код ... }

    Как вы предлагаете — еще лучше))

    • Дмитрий:

      Андрей,как показала практика не совсем лучше (((

      Связано с тем, что максимальный интервал срабатывания таймера при использовании библиотеки Timer0 составляет 8388480 мкс (около 8.4 с).

      Время пиликанья тональником у меня, намного больше, соответственно Ваш код лучше!

      Спасибо!

  25. Рашит:

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

    заранее спасибо!

    • Рашит, здравствуйте. Алгоритм должен выглядеть примерно следующим образом:

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

      2. Нужно завести две константы: время в течении которого мы считаем, что кнопка нажата долго pressed_long

      3. Определяем, что кнопка нажата и запоминаем этот момент времени pressed_moment

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

      5. Если время удержания < pressed_long, то переключаемся на следующий режим мигания, иначе - переходим в режим, когда светодиоды не горят

      6. Реализуем текущий режим и ожидаем следующего нажатия клавиши. Если клавиша нажата — опять переходим к п.3

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

      • Рашит:

        if(button_pin == 0 && flag == 1) {//если пошло нажатие currentMillis1 = millis();//заносим в переменную значение таймера if(currentMillis1 - previousMillis1 >= 10) {/*когда программа заходит сюда в первый раз получается, что значение бесконечно большое, поэтому счётчик в низу увеличится сразу */ count1++;//увеличим значение previousMillis1 = currentMillis1; //запоминаем последнее значение таймера } } if(flag1) { if(count1 == 2 && button_pin == 0 && flag == 1) {//если значение счётчика 20 миллисикунд regim++;//перейдём к следующему режиму if(regim > 3) regim = 1;//если пришли в конец режимов, возвращаем в начало flag1 = 0;//что бы после следующего прогона программа не заходила сюда } } if(count1 >= 100 && button_pin == 0 && flag == 1) {//если длительное нажатие regim = 0;//выключение диодов flag = 0;//ставим флаг в ноль для того, что-бы программа спокойно работала и не тратила время на захождение функцию } if(button_pin == 1 && flag1 == 0) flag = 0;//после отпускания, короткого нажатия if(button_pin == 1 && flag == 0)//после длительного нажатия, отпускание { previousMillis1 = 0; flag = 1; count1 = 0; flag1 = 1; }

  26. Рашит:

    это кусок обработки кнопки

  27. Рашит:

    кнопка не реагирует(

    • Рашит, я в конце статьи про Подключение кнопки к Arduino добавил еще один пример, в котором отслеживается продолжительность нажатия кнопки. При кратковременном нажатии — происходит циклическое переключение рабочих режимов, при длительном — режим сбрасывается в 0. Используя этот пример, вам останется только написать проверку того, какой режим выбран и, в зависимости от этого, по-разному мигать светодиодами. В режиме = 0 выключаете все светодиоды. Думаю, это вам поможет в решении вашей задачи.

  28. Игорь:

    Добрый день!

    Очень нужный код!

    А как менять интервалы уже созданному led? Мне нужно что бы отображать частой мигания уровни температуры: ниже порога светодиод не мигает, превышение на два градуса мигает редко, на три часто, на 5 градусов просто горит! И соответственно наоборот, если температура снизится то будет все реже, пока не перейдет порог и тогда вообще гасить светодиод.

    Видимо надо какой то метод добавить и его вызывать?

    • Да, Игорь, вы правы. Можно создать новый метод ChangeTimeParameters для изменения переменных OnTime и OffTime для объекта класса Flasher, а затем вызывать этот метод (что-то типа led1.ChangeTimeParameters (300, 2000)), в качестве параметров передавая новые значения.

      class Flasher { int ledPin; long OnTime; long OffTime; … void ChangeTimeParameters(long on, long off) { OnTime = on; OffTime = off; } };

  29. Руслан:

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

    Задача состоит вот в чём. Необходимо управлять скоростью сервопривода, который вращается от 0 до 180 градусов, в бесконечном цикле. Требуется регулировать его скорость в реальном времени с помощью переменного резистора, т.е. поворачиваю ручку переменного резистора и сервопривод тут же замедляется или ускоряется.

    Как я понял из вашего кода необходимо изменять параметр

    Sweeper sweeper1(15);. Выше вы подсказываете Игорю, как это реализовать для светодиода. Для сервопривода аналогично? А обработку преобразования с переменного резистора, тоже нужно в классе делать? Подскажите, пожалуйста.

  30. Руслан, здравствуйте. Да, изменять временной параметр для сервоприводов можно точно также как и для светодиодов — написав аналогичную функцию ChangeTimeParameter для изменения временного параметра внутри класса Sweeper. Считывать значение с переменного резистора, можно внутри loop, а затем уже, проведя анализ полученного значения, вызывать метод ChangeTimeParameter для изменения параметра:

    loop() { long time_param; int resistor_value; resistor_value = analogRead(...); // читаем значение напряжения на резисторе // здесь код, который вычисляет параметр time_param, используя resistor_value; sweeper1.ChangeTimeParameters(time_param); sweeper1.Update(); }

  31. АлексейМ:

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

    • Алексей, а какой именно у вас датчик? Приведенный ниже скетч считывает значения (0-300) с ультразвукового датчика URM37 v3.2 (работает на дистанции до 3 метров) и передает их в Serial-порт.

      Подключение

      5V (Arduino) -> Pin 1 VCC (URM V3.2)

      GND (Arduino) -> Pin 2 GND (URM V3.2)

      Pin 19 (RX1) (Arduino MEGA) -> Pin 9 (URM V3.2)

      Pin 18 (TX1) (Arduino MEGA) -> Pin 8 (URM V3.2)

      int USValue = 0; int ledPin = 13; // boolean flag=true; uint8_t DMcmd[4] = { 0x22, 0x00, 0x00, 0x22}; //команда измерения расстояния void setup() { Serial.begin(9600); // Скорость передачи данных - 9600 Serial1.begin(9600); pinMode(ledPin, OUTPUT); // устанавливаем цифровой пин в качестве выхода digitalWrite(ledPin, 0); delay(75); //Даем сенсору некоторое время для старта } void loop() { analogWrite(ledPin, 0); flag=true; for(int i=0;i<4;i++) Serial1.write(DMcmd[i]); while(flag) { analogWrite(ledPin, 255); if(Serial1.available()>0) { int header=Serial1.read(); //0x22 int highbyte=Serial1.read(); int lowbyte=Serial1.read(); int sum=Serial1.read(); if(highbyte==255) USValue=65525; //если highbyte =255, то ошибка чтения. else USValue = highbyte*255+lowbyte; Serial.println("Distance="); Serial.println(USValue); flag=false; } } analogWrite(ledPin, 0); delay(200); }

      • АлексейМ:

        СПАСИБО!!!, использую библиотеку ultrasonic.h

        датчик — ультразвуковой дальномер hc-sr04

  32. Денис:

    Здравствуйте! Возникла проблема с millis. Вот часть моей программы

    void ifn() { if (n == 0) { unsigned long timeafterMillis = 0; unsigned long timebeforeMillis = millis(); if (timeafterMillis - timebeforeMillis > (time/2)) { servo.write(90); gorighttank(); timeafterMillis = millis(); }

    это подпрограмма, gorighttank — тоже. Не знаю почему, но робот ничего не делает, не уверен на 100 %, но сервопривод на 90 все-таки идет, прошу помоши! time равен 1400. Заранее спасибо.

    • Денис, у вас значение timeafterMillis всегда меньше, чем timebeforeMillis (вы сначала присваиваете значение timeafterMillis, затем проходит какое-то время, а потом вы присваиваете значение timebeforeMillis). Поэтому условие

      if (timeafterMillis - timebeforeMillis > (time/2))

      никогда не выполняется.

      Второй момент — перенесите описание переменных timeafterMillis и timebeforeMillis за рамки функции ifn — чтобы они стали глобальными переменными.

      • Денис:

        Спасибо за ответ, но чем мне поможет вынесение этих переменных за ifn?

      • Денис:

        Помогите мне пожалуйста, не могли бы вы написать условие if, понимаю свою ошибку, но никак не могу её решить. Общая цель: в течение времени time/2 делать 2 вещи одновременно.

        • void ifn() { if (n==0) { unsigned long startTime = millis(); while(millis() - startTime < (time/2)) { servo.write(90); gorighttank(); } } }

          • Денис:

            Спасибо!

          • Денис:

            Есть еще одна проблемка. Как я понимаю (грубо говоря) данный элемент скетча постоянно с большой частотой посылает +5V на моторы(у меня это gorighttank делает), и из-за этого они постоянно «включаются» выдавая максимальный момент. Но на ковре трение очень большое и из-за этого мой робот еле поворачивает на нем. Я использую l293d драйвер моторов. Не могли бы вы помочь с решением данной проблемы? Есть подозрение, что на моторы надо припаять конденсатор большой емкости, прошу помощи, заранее спасибо

          • Денис:

            Или это можно решить программным путем?

  33. Rom327:

    Доброго времени суток! Собрал пропорциональное управление шаговыми двигателями (тремя). Все работает, но при включении двигатели вращаются в положение резисторов. Хотелось бы добавить некий таймер запрета на время включения, который выдает HIGH уровень на заданное время и подключить его ко входу Enable плат управления ШД(TB6560). Для управления использую джойстики с возвратом в среднее положение пружинами и при включении всегда находятся в среднем положении. Подскажите, как выполнить функцию задержки (сброса) на ~10 секунд при включении?

    • При подаче питания на микроконтроллер счетчик миллисекунд функции millis устанавливается в ноль. Просто проверяете не превысил ли он значение равное 10 секундам. Если превысил — выполняете ваш код.

      if (millis()>10000) { // ваш код }

      Если я,конечно, правильно вас понял...

  34. Rom327:

    Спасибо! Понятно, что ничего не понятно... Мне надо, чтобы при включении на 3 выходе появился высокий уровень на 10 секунд, а потом сменился на низкий. А основная программа выполняется без задержки...

    • Так и есть — я вас не совсем правильно понял 🙂 Значит, делаем так:

      1. Выставляете на третьем выходе высокий уровень сигнала

      2. Запоминаете текущий момент времени в переменную, например, назовем ее current_time

      current_time = millis();

      3. Проверяете не прошло ли 10 секунд с запомненного момента времени current_time и, если прошло — меняете уровень на низкий

      if(millis()-current_time>10000) { // здесь изменить уровень сигнала на 3 выходе на низкий }

  35. Rom327:

    Спасибо! Ввиду того, что я всего неделю, как открыл для себя Ардуино, прошу сразу не ругать...

    int EN = 3; void setup() { pinMode(EN, OUTPUT); } void loop() { digitalWrite(EN, HIGH); unsigned long current_time = millis(); if(millis()-current_time>10000) { digitalWrite(EN, LOW); } }

    Ругается компилятор...

    • У вас скобки фигурной закрывающей } в самом конце не хватает.

      Вы когда код пишите, отступы делайте — так проще увидеть сколько скобок открыто, столько же должно быть и закрыто.

      Я в вашем коде подредактировал — добавил отступы и скобку.

  36. Rom327:

    Спасибо! Но компилятор все равно ругается:

    'current_time' was not declared in this scope

  37. Rom327:

    ... а включаешь — не работает! С delay работает, с millis нет! Ничего не понимаю... Специально в одном скетче проверяю, ненужное // заремил...

    int EN = 3; void setup() { pinMode(EN, OUTPUT); } void loop() { digitalWrite(EN, HIGH); //delay(1000); unsigned long current_time = millis(); if(millis()-current_time>1000) digitalWrite(EN, LOW); //delay(1000); }

    • Rom327:

      Но, если нет второго delay диод не гаснет!

      • Работающий код

        int EN = 3; void setup() { pinMode(EN, OUTPUT); digitalWrite(EN, HIGH); } void loop() { if (millis()>1000) digitalWrite(EN, LOW); }

        Все что внутри функции loop выполняется бесконечно «по кругу», пока на микроконтроллер подано питание. Если что-то нужно выполнить только один раз, после включения — этот код размещать внутри функции setup.

        Предыдущий код как работает: вы выставляете высокий уровень на третьем пине и запоминаете момент времени в который это произошло. После этого проверяется условие и, если оно верно (прошло более 1 секунды), на 3 пине выставляется низкий уровень. После этого функция loop заканчивается и начинает выполняться вновь, а там опять выставляется высокий уровень. Это происходит очень быстро (пин переводится в низкий уровень и сразу в высокий), потом опять проверяется условие, пин переводится в низкий уровень, сразу в высокий. Вы глазом это не замечаете. Когда ставите delay просто после выключения появляется задержка.

  38. Rom327:

    Андрей! Большое спасибо! Ваш код заработал! Но, к сожалению, не совсем как я ожидал. Сигнал HIGH подается на вход Enable контроллера (TB6560) и не разрешает вращение двигателя во время загрузки системы, но когда этот сигнал меняется на LOW и разрешается вращение, двигатель делает +1 шаг вперед. Видимо особенность работы контроллера TB6560. Это не есть правильно, так как при 10 включениях (к примеру) двигатель сделает уже 10 шагов...

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

    С уважением, Роман

  39. Rom327:

    /* Include the library */ #include "HCMotor.h" #include /* Pins used to drive the motors */ #define DIR_PIN 10 //Connect to MotorX 'dir' input. #define CLK_PIN 11 //Connect to MotorX 'step' or 'CLK' input. #define DIR_PIN1 12 //Connect to MotorY 'dir' input. #define CLK_PIN1 13 //Connect to MotorY 'step' or 'CLK' input. AccelStepper stepper2(1, 5, 4); //Connect to MotorZ 'step' , 'dir' input AccelStepper stepper3(1, 7, 6); //Connect to MotorA 'step' , 'dir' input AccelStepper stepper4(1, 9, 8); //Connect to MotorB 'step' , 'dir' input /* Set the analogue pin the potentiometer will be connected to. */ #define POT_PIN A0 //Подключение потенциометра Х dir-10 step-11 #define POT_PIN1 A1 //Подключение потенциометра У dir-12 step-13 #define POT_PIN2 A2 //Подключение потенциометра Z dir-4 step-5 (пропорциональное управление) #define POT_PIN3 A3 //Подключение потенциометра A Вращение компонента dir-6 step-7 (пропорциональное управление) #define POT_PIN4 A4 //Подключение потенциометра B Подача компонентов dir-8 step-9 (пропорциональное управление) #define GISTEREZIS 10 /* DEADZONE Мертвая зона потенциометров Х У */ #define DEADZONE 10 /* Аналог PIN-код будет возвращать значения от 0 до 1024, чтобы разделить это между вперед и обратного */ #define POT_REV_MIN 0 #define POT_REV_MAX (512 - DEADZONE) #define POT_FWD_MIN (512 + DEADZONE) #define POT_FWD_MAX 1024 /* Create an instance of the library Создать экземпляр в библиотеке*/ HCMotor HCMotor; void setup() { //Serial.begin(9600); /* Initialise the library Инициализация библиотеки */ HCMotor.Init(); HCMotor.attach(0, STEPPER, CLK_PIN, DIR_PIN); HCMotor.attach(1, STEPPER, CLK_PIN1, DIR_PIN1); HCMotor.Steps(0,CONTINUOUS); HCMotor.Steps(1,CONTINUOUS); stepper2.setMaxSpeed(1000); stepper2.setAcceleration(200); stepper3.setMaxSpeed(1000); stepper3.setAcceleration(200); stepper4.setMaxSpeed(1000); stepper4.setAcceleration(200);} void loop() { int Speed, Pot; /* Read the analogue pin to determine the position of the pot. Прочитайте аналог PIN-кода для определения позиции потенциометра. */ Pot = analogRead(POT_PIN); /* Is the pot in the reverse position ? Это потенциометр в перевернутом положении? */ if (Pot >= POT_REV_MIN && Pot = POT_FWD_MIN && Pot = POT_REV_MIN && Pot1 = POT_FWD_MIN && Pot1 GISTEREZIS) stepper2.moveTo(val); stepper2.setSpeed(1000); stepper2.runSpeedToPosition(); int val1 = analogRead(POT_PIN3); if (abs(val1-stepper3.currentPosition()) > GISTEREZIS) stepper3.moveTo(val1); stepper3.setSpeed(1000); stepper3.runSpeedToPosition(); int val2 = analogRead(POT_PIN4); if (abs(val2-stepper4.currentPosition()) > GISTEREZIS) stepper4.moveTo(val2); stepper4.setSpeed(1000); stepper4.runSpeedToPosition(); }

    Два мотора Х и У работают, меняя скорость. Моторы Z, A, B работают пропорционально. Вот их и надо заставить не крутиться на момент включения ~ 1,5-2 секунды.

  40. АлексейМ:

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

    #include #include Ultrasonic ultrasonic(2,3);// (Trig PIN,Echo PIN) int distmin = 10; //4 int distmax = 40; //22 int distL;//дистанция серво 120 int distR;//дистанция серво 70 int distC;//дистанция серво 90 int distcm; #define rulservoPin 5 #define distservoPin 4 Servo distservo; int degree = 90; //начальный угол сервы unsigned int impulseTime=0; unsigned int distance_sm=0; boolean servoState; // состояние ВКЛ (true)/ВЫКЛ (false) int pos =5;//шаг long previousMillis = 0; long interval = 50; //time void setup() { Serial.begin(9600); rulservo.attach(rulservoPin); rulservo.write(0); distservo.attach(distservoPin); distservo.write(degree); } int distservoC() {//положение серво 90 и замер дистанции distservo.write(degree); float res = ultrasonic.Ranging(CM); Serial.println(res); return res; } int distservoR() {/*поворот сервы между 70-90 и обратно, замер дистанции на положение серво 70*/ servoState = true; unsigned long currentMillis = millis(); if(servoState &&((currentMillis - previousMillis) > interval)) { previousMillis = currentMillis; degree += pos; distservo.write(degree); if ((degree 90)) { pos = -pos; servoState = false; } Serial.println(degree); if (degree == 70) { float res = ultrasonic.Ranging(CM); Serial.println(res); return res; } } } int distservoL() {/*поворот сервы между 120-90 и обратно, замер дистанции на положение серво 120*/ servoState = true; unsigned long currentMillis = millis(); if(servoState &&((currentMillis - previousMillis) > interval)) { previousMillis = currentMillis; degree += pos; distservo.write(degree); if ((degree = 120)) { pos = -pos; servoState = false; } Serial.println(degree); if (degree == 120) { float res = ultrasonic.Ranging(CM); Serial.println(res); return res; } } } int go() { //условие движения if( (distC>distmin)||(distC>distmax)&&(distC>0) ) return 1;//go go if( (distL>distR)&&(distL>distmin)||(distR=0) ) return 2;//go left if( (distR>distL)&&(distR>distmin)||(distL=0) ) return 3;//go right else { if (((distL<distmin))||(distL=0)&&((distR<distmin) ||(distR=0))) return 4;//go stop else return 5;//go back } } void loop() { int dir; distcm=distservoC(); //серво прямо, замер дистанции if ( ( distcm0) ) { //motor forward; label1: //метка distL=distservoL(); distR=distservoR(); dir=go(); switch (dir) { case 1: //go go break; case 2: //left break; case 3: //right break; case 4: // motor stop; //delay(1000); case 5: //motor back; do { //условие проверяется в конце цикла //выполняет поворот серво и замер дистанции слева, потом с право distL=distservoL(); distR=distservoR(); } //while проверяет условие while ((distL<distmin)&&(distR=0)&&(distL>0)); //если нет то заново метка label1: goto label1;// переход к метке } } }

    • Алексей,

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

      2) если вы пишете условие if, то условие равно записывается с двумя значками ==. Если написать просто if (x=5), то это условие выполняется всегда и переменной x присваивается значение 5.

      3) у вас в условии

      if (((distL<distmin))||(distL=0)&&((distR<distmin) ||(distR=0)))

      слишком много скобок. Возможно, ошибка где-то и здесь.

      4) while — это цикл с проверкой условия перед выполнением. Строчка

      while ((distL<distmin)&&(distR0)&&(distL>0));

      выполняется бесконечно (а точнее, бесконечно выполняется пустой оператор «;») пока условие, записанное внутри while верно. Если условие выполняется в этом месте программа стопорится и дальше не идет.

  41. Павел:

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

    class Flasher { int ledPin; int analogPin; public: Flasher(int pin1, int pin2) { ledPin = pin1; pinMode(ledPin, OUTPUT); analogPin = pin2; pinMode(analogPin, INPUT); } void Update() { int val = analogRead(analogPin); val = constrain(val, 10, 100); int ledLevel = map(val, 10, 100, 0, 225); analogWrite(ledPin, ledLevel); } }; Flasher led1(11, 1); Flasher led2(12, 2); Flasher led3(13, 3); void setup(){} void loop() { led1.Update(); led2.Update(); led3.Update(); }

    • Если у вас плата Arduino с микроконтроллером ATmega168 или ATmega328, то ШИМ поддерживают порты 3, 5, 6, 9, 10 и 11. Если Mega, то тогда можно в качестве выхода ШИМ для управления яркостью светодиодов использовать порты со 2 по 13. Для функции analogWrite можно не указывать, что мы будем использовать вывод как выход при помощи pinMode (строчку 9 в коде можно убрать).

  42. Анатолий:

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

    типа 5 секунд задержка потом светится 1 сек, 2 сек выкл, 2 сек светится, 3 сек выкл все это в в 13 сек всунуть. заранее спасибо.

    • Анатолий, посмотрите мой комментарий от 14.05.2015. Там я показал пример реализации класса DoubleFalsher, где задается 4 параметра: длительность 1 -го включения, пауза после 1-го включения, длительность 2-го включения и пауза после 2-го включения. Вам нужно попытаться разобраться в коде и по аналогии добавить еще один параметр — паузу перед первым включением.

  43. Александр:

    Подскажите как написать класс шагового двигателя?

  44. Алексей:

    Здравствуйте Андрей, суть вопроса такова: есть скетч с двумя циклами и кнопками управляюшими их запуском, возможно ли с помощью прерываний реализовать включение по кнопке второго цикла при не законченном первом, и/или включении первого опять сначала?

  45. Николай:

    Добрый день.

    Подскажите а простенькое меню, несколько последовательных экранов, в котором будут задаваться параметры, используемые в основном цикле, можно в класс выделить?

    Нажал кнопку — перешел в класс, задал параметры и по таймауту вывалился обратно в основной цикл.

    Где можно почитать, примеры посмотреть?

    Заранее благодарен!

    • Николай, здравствуйте. Можно вашу задачу решить как с использованием методологии объектно-ориентированного программирования (ООП), так и без нее. Чтобы разобраться с тем как писать программки с использованием ООП, начните с чтения какой-нибудь книжки по языку С++ (не обязательно с привязкой к Arduino). В каких-то случаях ООП позволяет упростить написание программного кода и его читаемость и понимаемость, в каких-то случаях можно, наоборот, используя ООП задачу усложнить и запутать себя самого. Книжку могу посоветовать Стивен Прата «Язык программирования C++. Лекции и упражнения», 6-е изд, 2012.

  46. Алексей:

    Здравствуйте Андрей что надо добавить в код чтобы циклы работали только во время нажатых соответствующих кнопках

    #include "FastLED.h" #define NUM_LEDS_PER_STRIP 60 CRGB leds[NUM_LEDS_PER_STRIP]; int ButPinBR = 10; // Подключаем кнопку к выходу 10 int ButPinRE = 11; void setup() { pinMode(ButPinBR, INPUT); pinMode(ButPinRE, INPUT); // tell FastLED there's 60 NEOPIXEL leds on pin 4 FastLED.addLeds(leds, NUM_LEDS_PER_STRIP); } void loop(){ { if(digitalRead(ButPinBR) == HIGH) { for(int i = 20; i < 40; i++) { leds[i] = CRGB::Red; FastLED.show(); leds[i] = CRGB::Red;} delay(25); for(int i = 20; i < 40; i++) {leds[i] = CRGB::Black; FastLED.show(); leds[i] = CRGB::Black;} delay(25); for(int i = 20; i < 40; i++) {leds[i] = CRGB::Red; FastLED.show(); leds[i] = CRGB::Red;} {int j = 20; for (int i = 40; i 0) j-- ; leds[j] = CRGB::Red; FastLED.show(); leds[i] = CRGB::Red; leds[j] = CRGB::Red; delay(10);} } {int j = 20; for (int i = 40; i 0) j-- ; leds[j] = CRGB::Black; FastLED.show(); leds[i] = CRGB::Black; leds[j] = CRGB::Black; delay(10); } } {int j = 20; for (int i = 40; i 0) j-- ; leds[j] = CRGB::Red; FastLED.show(); leds[i] = CRGB::Red; leds[j] = CRGB::Red; delay(10);} } {int j = 60; for (int i = 0; i = 40) j-- ; leds[j] = CRGB::Black; FastLED.show(); leds[i] = CRGB::Black; leds[j] = CRGB::Black; delay(500);} } for(int i = 20; i < 40; i++) {leds[i] = CRGB::Red; FastLED.show(); leds[i] = CRGB::Red;} delay(50); for(int i = 20; i < 40; i++) {leds[i] = CRGB::Black; FastLED.show(); leds[i] = CRGB::Black;} } {for(int i = 0; i < 60; i++) {leds[i] = CRGB::Black; FastLED.show(); leds[i] = CRGB::Black;}} } if(digitalRead(ButPinRE) == HIGH) { {int j = 60; for (int i = 0; i = 31) j-- ; leds[j] = CRGB::White; FastLED.show(); leds[i] = CRGB::White; leds[j] = CRGB::White; delay(25);} } {int j = 60; for (int i = 0; i = 31) j-- ; leds[j] = CRGB::Black; FastLED.show(); leds[i] = CRGB::Black; leds[j] = CRGB::Black; delay(25);} } } }

  47. Михаил Ш.:

    Доброго времени суток. Прежде всего хотелось бы выразить благодарность за статью, что пролила немного света в темный мир программирования. Но вот у меня вопрос, что если мне не надо мигать светодиодом, а просто сделать паузу, скажем на одну или две секунды? Как будет выглядеть код в этой ситуации. Например что бы я мог использовать его в цикле for или в чем то подобном, да хотя бы просто моторчик две секунды вперед крутился? А то просто что касается millis, других примеров кроме как мигания диодом я не видел. Заранее спасибо за ответ

    • Михаил, здравствуйте. millis — это счетчик миллисекунд, который запускается при подаче питания на микроконтроллер. Чтобы использовать его в программке:

      1. Запоминаем текущий момент времени

      current_time = millis();

      2. Периодически (как часто проверять зависит от конкретного примера, но, очень часто, эта проверка производится где-то внутри цикла loop) проверяем не достиг ли счетчик значения current_time + target (target — требуемый интервал времени в миллисекундах) и, если достиг, то выполняем нужный нам код:

      if (millis() >= (current_time+target) { // Здесь совершаем какие-то действия }

      В вашем случае, последовательность шагов такая:

      1. Включили моторчик

      2. Запомнили момент времени current_time, когда включился моторчик

      current_time = millis();

      3. Внутри loop пишем условие для отслеживания протекания 2 секунд (2000 миллисекунд)

      if (millis()>=(current_time + 2000)) { // выключаем моторчик }

  48. Димитрий:

    Буду очень благодарен если поможете решить проблему!решил сделать датчик включения освещения который должен включать светодиод через 10сек после срабатывания цифрового датчика освещенности если в этот момент (10сек) датчик освещенности изменит своё состояние начать отсчет заново(10сек) и точно та как же организовать выключение светодиода по датчику(через 10сек) не используя delay!Заранее благодарен!

    • Дмитрий, приветствую!

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

      1. Заведем перемненную led_state в которой будем хранить состояние светодиода (ВКЛ/ВЫКЛ) и флаг flag_change, показывающий что нужно изменить его текущее состояние

      int led_state = TRUE; boolean flag_state = FALSE;

      2. В цикле loop считываем состояние датчика

      3. Проверяем условие на срабатывание датчика и если он сработал, то

      if (...) { // Запоминаем текущий момент времени (см. комментарий к вопросу Михаил Ш. выше) current_time = millis(); }

      4. Смотрим, не прошло ли с запомненного момента более 10 секунд и если прошло, изменяем состояние светодиода

      if (millis()>=(current_time+10000)) { // изменяем состояние светодиода на противоположное led_state = !led_state; // и устанавливаем флаг смены состояния flag_change = TRUE; }

      5. Проверяем флаг смены состояния и, при необходимости, переключаем светодиод

      if (flag_change) digitalWrite (ledPin, led_state);

  49. екшщлф:

    Здравствуйте Андрей, во первых шикарная статья.

    Искал про milis (), остался читать из за Class-ов.

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

    Для этого установлены 2 спаренных реле 5-и контактных.

    Прошу помощи собственно... 🙂

    int ANALOG0 = A0; int CloseButton = 15; int rele1 = 2; int rele2 = 3; int T = 50; int Temp; int button; void setup() { pinMode(ANALOG0, INPUT); pinMode(CloseButton, INPUT); pinMode(rele1, OUTPUT); pinMode(rele2, OUTPUT); digitalWrite(15, HIGH); //поддягиваем внутренний резистор на верх HIGH А1, НЕ ПОДАВАТЬ +5 digitalWrite(rele1, HIGH); digitalWrite(rele2, HIGH); Serial.begin(38400); // указываем скорость работы с COM-портом Serial.println("go"); } void loop() { int val0; val0 = analogRead(ANALOG0);// считываем данные Temp = val0; delay(100); val0 = analogRead(ANALOG0); // считываем данные повторно } if (val0 > 10) { Serial.println( val0); } // Работа щеколды контроллера от кода или ключа if (val0 > 600 && Temp > 600) { OpenDoor(); delay(4000); CloseDoor(); } // Конец работы от контроллера button = digitalRead(CloseButton); //Кнопка BELL на контроллере if (button == LOW) { //Инвертированна, подтягивающий внутренний CloseDoor(); } } void OpenDoor() { digitalWrite(rele2, LOW); delay(T); digitalWrite(rele2, HIGH); } void CloseDoor() { digitalWrite(rele1, LOW); delay(T); digitalWrite(rele1, HIGH); }

    и в двух словах разницу между Void CloseDoor () в виде подпрограммы и Классами. Или статью хорошую.

    Спасибо

    drive.google.com/file/d/0...view?usp=sharing

    ссылка на механизм

  50. trioka:

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

    Прошу помощи в переделке моего скетча на milis (), так как при подключении 28j60 и запуске через интернет? зависает в библиотеке blynk.cc, не любит она delay.

    int ANALOG0 = A0; int CloseButton = 15; int rele1 = 2; int rele2 = 3; int T = 50; int Temp; int button; void setup() { pinMode(ANALOG0, INPUT); pinMode(CloseButton, INPUT); pinMode(rele1, OUTPUT); pinMode(rele2, OUTPUT); digitalWrite(15, HIGH); //поддягиваем внутренний резистор на верх HIGH А1, НЕ ПОДАВАТЬ +5 digitalWrite(rele1, HIGH); digitalWrite(rele2, HIGH); Serial.begin(38400); // указываем скорость работы с COM-портом Serial.println("go"); } void loop() { int val0; val0 = analogRead(ANALOG0);// считываем данные Temp = val0; delay(100); val0 = analogRead(ANALOG0); // считываем данные повторно } if (val0 > 10) { Serial.println( val0); } // Работа щеколды контроллера от кода или ключа if (val0 > 600 && Temp > 600) { OpenDoor(); delay(4000); CloseDoor(); } // Конец работы от контроллера button = digitalRead(CloseButton); //Кнопка BELL на контроллере if (button == LOW) { //Инвертированна, подтягивающий внутренний CloseDoor(); } } void OpenDoor() { digitalWrite(rele2, LOW); delay(T); digitalWrite(rele2, HIGH); } void CloseDoor() { digitalWrite(rele1, LOW); delay(T); digitalWrite(rele1, HIGH); }

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

    Ссылка на механизм

    И в двух словах про отличие подпрограмм методом void CloseDoor () как у меня и class как у вас.

    Спасибо.

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

  51. Илья:

    Андрей благодаря вашим статьям научился таймингу без delay () применяя millis ().

    Например при нажатии на кнопку светодиод горит заданное время и отключается. А как теперь добавить вторую кнопку на этот же светодиод с таким же режимом?

    Я пробовал добавить переменную на вторую кнопку и прописать такое условие ниже...не работает:

    if (digitalRead(Button2) == HIGH)//если нажата кнопка 2 { zMillis = tMillis; // запоминаем время led1 = HIGH; digitalWrite(ledPin,led1); }

  52. Илья:

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

    • Заведите какую-нибудь переменную, в которой будет храниться 1, если кнопка была нажата впервые и 0 до этого момента и после 5 секунд свечения светодиода.

      boolean flagFirstKeyPressed = FALSE; unsigned long momentKeyPressed = 2^32 - 1; // максимальное значение счетчика ... // если кнопка нажата впервые if((digitalRead(Button1) == HIGH) && !flagFirstKeyPressed) { flagFirstKeyPressed = TRUE; // устанавливаем флаг при первом нажатии momentKeyPressed = millis(); // запоминаем момент времени digitalWrite(ledPin, HIGH); // включаем светодиод } // если прошло 5 секунд if (millis() - momentKeyPressed >= 5000) { flagFirstKeyPressed = FALSE; // сбрасываем флаг momentKeyPressed = 2^32 - 1; // устанавливаем максимальное значение digitalWrite(ledPin, LOW); // выключаем светодиод }

      • Илья:

        Спасибо! Все понятно. Однако в строках «flagFirstKeyPressed = FALSE» и «flagFirstKeyPressed = TRUE» ругается компилятор. Заменил «FALSE» и «TRUE» на «0» и «1» заработало.

  53. Андрей:

    Здравствуйте, Андрей!

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

    Но есть один Нюанс!!! Сервопривод1 вращается от 0 до 90 град и обратно (не выходит за эти рамки 0 и 90), Сервопривод2 вращается от 180 до 90 и обратно.

    Я использовал Ваш пример, первый сервопривод мне удалось одолеть, а вот второй проблема...

    может создать две servo1 и для servo2 с инкрементом pos -= increment;

    (180-1 и так до 90град и обратно 90+1 и до 180град) Я пробовал у меня не получилось...

    может одновременное использование одинаковых имён «increment» не даёт нормально функционировать?

    Помогите с кодом пожалуйста.

    #include class Flasher { // Переменные - члены класса // Инициализируются при запуске int ledPin; // номер пина со светодиодом long OnTime; // время включения в миллисекундах long OffTime; // время, когда светодиод выключен // Текущее состояние int ledState; // состояние ВКЛ/ВЫКЛ unsigned long previousMillis; // последний момент смены состояния // Конструктор создает экземпляр Flasher и инициализирует // переменные-члены класса и состояние public: Flasher(int pin, long on, long off) { ledPin = pin; pinMode(ledPin, OUTPUT); OnTime = on; OffTime = off; ledState = LOW; previousMillis = 0; } void Update() { // выясняем не настал ли момент сменить состояние светодиода unsigned long currentMillis = millis(); // текущее время в миллисекундах if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime)) { ledState = LOW; // выключаем previousMillis = currentMillis; // запоминаем момент времени digitalWrite(ledPin, ledState); // реализуем новое состояние } else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime)) { ledState = HIGH; // выключаем previousMillis = currentMillis ; // запоминаем момент времени digitalWrite(ledPin, ledState); // реализуем новое состояние } } }; class Sweeper { Servo servo; // сервопривод int pos; // текущее положение сервы int increment; // увеличиваем перемещение на каждом шаге int updateInterval; // промежуток времени между обновлениями unsigned long lastUpdate; // последнее обновление положения public: Sweeper(int interval) { updateInterval = interval; increment = 1; } void Attach(int pin) { servo.attach(pin); } void Detach() { servo.detach(); } void Update() { if((millis() - lastUpdate) > updateInterval) // время обновлять { lastUpdate = millis(); pos += increment; servo.write(pos); Serial.println(pos); if ((pos >= 90) || (pos <= 0)) // конец вращения { // обратное направление increment = -increment; } } } }; Flasher led1(11, 123, 400); Flasher led2(12, 350, 350); Flasher led3(13, 200, 222); Sweeper sweeper1(15); Sweeper sweeper2(15); void setup() { Serial.begin(9600); sweeper1.Attach(9); sweeper2.Attach(10); } void loop() { sweeper1.Update(); if (digitalRead(2) == HIGH) { sweeper2.Update(); led1.Update(); } led2.Update(); led3.Update(); }

    • Для второго сервопривода вам понадобится другая функция Update. Обратите внимание на строчку 84 в вашем коде. В ней стоит условие, которое позволяет вращаться только в диапазоне 0-90 градусов. Как вариант, написать функцию Update_2, которая обновляла бы положение сервопривода в диапазоне 90-180 градусов и для второго сервопривода для обновления позиции pos использовать уже функцию Update_2. Она будет выглядеть практически также как Update, измените условие на диапазон изменения угла поворота. Также обратите внимание на то, что при старте программы вторую серву нужно поставить в начальное положение 90 (или 180) градусов. Для этого используется переменная pos.

  54. Марат:

    Здравствуйте Андрей помогите пожалуйста загнать в класс.

    int gap1=2; //2 цифровой пин для первого датчика int gap2=3; //3 цифровой пин для второго датчика int initial=0; //первый запуск int n=1; //номер выстрела float velocity, energy; //переменная для хранения скорости float length=0.148; //расстояние между датчиками в метрах volatile int flag1,flag2; //флажки для хранения состояния пролетания пулей датчиков volatile unsigned long mark1, mark2; //отметки времени прохождения пулей датчиков void setup() { pinMode(gap1, INPUT); //Настраиваем контакт 2 pinMode(gap2, INPUT); //Настраиваем контакт 3 Serial.begin(9600); //открываем COM порт attachInterrupt(1,start,RISING); //прерывание при прохождении первого датчика attachInterrupt(0,finish,RISING); //прерывание при прохождении второго датчика } void start() { mark1=micros(); flag1=1; //прошли первый датчик, время запомнили } void finish() { mark2=micros(); flag2=1; //прошли второй датчик, время запомнили } void loop() { if (initial==0) { flag1=0; flag2=0; Serial.println("System is ready, just pull the f*ckin trigger!"); //уведомление о том, что хрон готов к работе. Вылезает при включении Serial.println(" "); initial=1; } if (micros()-mark1>1000000 && flag1==1) { // (если пуля прошла первый датчик) И (прошла уже 1 секунда, а второй датчик не тронут) Serial.println("FAIL"); //выдаёт FAIL через 1 секунду, если пуля прошла через первый датчик, а через второй нет flag1=0; flag2=0; } if (flag2==1 && flag1==1) { //(если пуля прошла оба датчика) velocity=(1000000*(length)/(mark2-mark1)); //вычисление скорости Serial.print("Shot "); Serial.println(n); //вывод номера выстрела Serial.print("Speed: "); Serial.println(velocity); //вывод скорости в COM Serial.println(" "); flag1=0; flag2=0; n++; //номер выстрела +1 } delayMicroseconds(10); //задержка для стабильности }

  55. Александр:

    Здравствуйте, Андрей!

    Помогите, пожалуйста «прикрутить» потенциометр для управления сервой в этой программе.

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

    #define PWMA 3 // Контроль скорости мотора каретки подключен к D3 #define AIN1 8 // Направление вращения мотора каретки подключено к D8 #define AIN2 9 // Направление вращения мотора каретки подключено к D9 #include Servo motor;// Создаём объект для контроля сервы int pos = 950; // Переменная для хранения позиции сервы const int GerkonVpered = 4; // Герконовый датчик остановки каретки передний const int GerkonNazad = 5; // Герконовый датчик остановки каретки задний const int GerkonMonitor = 6; // Герконовый датчик остановки монитора в нижнем положении const int buttonPin = 2; // Контакт, на котором находится кнопка int flag = 0; // флаг состояния int regim = 1; // Переключалка void setup() { pinMode(buttonPin, INPUT_PULLUP); // Подключаем контакт кнопки и подтягивающий резистор pinMode(GerkonNazad, INPUT_PULLUP); // Подключаем контакт, на котором находится датчик pinMode(GerkonVpered, INPUT_PULLUP); // Подключаем контакт, на котором находится датчик pinMode(GerkonMonitor, INPUT_PULLUP); // Подключаем контакт, на котором находится датчик pinMode(PWMA,OUTPUT); pinMode(AIN1,OUTPUT); pinMode(AIN2,OUTPUT); } void loop() { if(digitalRead(buttonPin) == LOW && flag == 0) { regim ++; flag = 1; if(regim > 2) // Если номер режима превышает требуемого { // то отсчет начинается с 1 regim = 1; } } delay(80); if(digitalRead(buttonPin) == HIGH && flag == 1) { flag = 0; } // ======= Вполняем задачу при выборе режима ======= // ЗАКРЫТЬ МОНИТОР if(regim == 1) // Кнопка нажата 1 раз { motor.attach(10);// Подключаем контакт на котором находится Сервомотор motor.write(950);// Опускаем монитор в горизонтальное роложение if(digitalRead(GerkonMonitor) == LOW) //Если сработал Геркон монитора, { digitalWrite(AIN1, LOW); // то включаем мотор движения каретка "назад" digitalWrite(AIN2, HIGH); analogWrite(PWMA, 255); } if(digitalRead(GerkonNazad) == LOW) // Если сработал Геркон задний, { digitalWrite(AIN1,HIGH); // то останавливаем мотор движения каретки "назад" digitalWrite(AIN2,HIGH); digitalWrite(PWMA,LOW); } } // ОТКРЫТЬ МОНИТОР if(regim == 2) // Кнопка нажата 2 раз { if(digitalRead(GerkonMonitor) == LOW) // Если геркон монитора заблокирован, { // то motor.attach(10); // подключаем контакт на котором находится Сервомотор, digitalWrite(AIN1, HIGH); // включаем мотор движения каретки "вперед" digitalWrite(AIN2, LOW); analogWrite(PWMA, 255); } if(digitalRead(GerkonVpered) == LOW) // Если сработал передний геркон , { // то digitalWrite(AIN1,HIGH); // останавливаем мотор движения каретки "вперед", digitalWrite(AIN2,HIGH); digitalWrite(PWMA,LOW); motor.write(1300); // поднимаем монитор вверх на заданный угол. } } }

  56. Михаил:

    Всем привет!

    Оформил Flasher в виде библиотеки : aerokino.ru/upload/flasher.zip

    Cодержимое из архива поместить в папку Arduino/libraries. В Образцах появится Flasher с примером использования.

  57. Максим:

    здравствуйте! Есть функция движения робота,нужно переписать через millis,можете помочь?

    пример кода движения

    void forward() { // поднять вверх переднее правое колено и левое нижнее колено for(i = 0 ; i<=25; i++) { servo2.write(90 - i); servo3.write(90 + i); delay(5); } // передвинуть переднее правое и левое заднее бедро вперед, переднее левое и заднее правое бедро назад for(i = 0 ; i<=44; i++){ servo11.write(90 + i + 7);//плб servo22.write(50 + i );//ппб servo33.write(93 - i - 4);//злб servo44.write(138 - i );//зпб delay(5); } // опустить преднее правое и заднее левое колено for(i = 0 ; i<=25; i++) { servo2.write(65 + i); servo3.write(115 - i); delay(5); } // поднять переднее левое колено и заднее правое for(i = 0 ; i<=25; i++) { servo1.write(90 + i); servo4.write(83 - i); delay(5); } // передвинуть вперед переденее левое и заднее правое бедро, переднее правое и заднее левое бедро назад for(i = 0 ; i<=44; i++) { servo11.write(141 - i - 7);//плб servo22.write(94 - i );//ппб servo33.write(45 + i + 4);//злб servo44.write(94 + i);//зпб delay(5); } // опустить вниз for(i = 0 ; i<=25; i++) { servo1.write(115 - i); servo4.write(58 + i); delay(5); } }

  58. Сергей:

    Как в 110 и 113 строчках программа знает какой именно «Update» применить? Ведь их в теле программы два?

    • У нас два объекта — sweeper1 и sweeper2, принадлежащих классу Sweeper. Класс Sweeper включает метод Update. В 110 строке функция Update оперирует с данными объекта sweeper1, в 113 — объекта sweeper2.

  59. Олег:

    Подскажите, плз. Как в скетч для трех мигающих диодов из Вашей статьи (с использованием классов), вводить дополнительные переменные. Чтоб мигание было не два цикла, а несколько с разной продолжительностью? Убил вечер, но так и не добился. Спасибо.

    • Олег, здравствуйте. Внимательно перечитайте в статье раздел под названием «Используем классы». Там я подробно с комментариями описал как вводить переменные и инициализировать их. Почитайте комментарии — там приводились примеры с бОльшим числом мигающих светодиодов, сравните разные варианты кода (для двух и большего числа светодиодов). Также, для более глубокого понимания темы, рекомендую почитать книжки по объектно-ориентированному программированию на языке С++.

  60. Михаил Ш.:

    Здравствуйте Андрей! Воспользовался вашим ответом к моему комментарию от 29.12.15 в целом принцип работы с millis () понял, но есть некоторая не ясность. Задача была следующая есть УЗ дальномер и сервопривод, когда показания больше 30 см, сервопривод находится в положении 178 градусов, как только показания становятся ниже 30, сервопривод меняет положение на 2 градуса и скажем через три секунды перемещается на 60 градусов. Вопрос возник со следующем моментом:

    if (DISTANCE>30) { current_time=millis(); POS=178; } else { POS=2; } if (millis()>=(current_time+10000)) { POS=60; }

    если я запоминаю время в момент когда POS=178, то все работает как надо привод перемещается на 2 затем ждет 3 секунды и перемещается на 60. Но если я запоминаю время в момент когда POS = 2, то происходит наоборот, пока значение УЗ меньше 30 серва в состоянии 2 и ничего, но как только POS снова равно 178 серва ждет 3 секунды и перемещается на 60. Почему так происходит можете объяснить? Я изначально думал что если запомнить время в момент POS=2 то серва будет ждать 3 секунды, почему получилось наоборот? Заранее благодарен за ответ!

    • Михаил, здравствуйте. Введите переменную flag_change, которая будет принимать значение true, как только расстояние станет <= 30 и значение false после паузы и поворота на 60 градусов.

      boolean flag_change=false; ... if (DISTANCE>30) { POS = 178; } else { POS = 2; current_time=millis(); flag_change=true; } if(flag_change && (millis()>=(current_time+3000))) { POS=60; flag_change=false; }

      Это не сильно меняет сути. Возможно, у вас что-то не так в остальной части кода.

      • Михаил Ш.:

        Спасибо за подсказку. С остальной частью кода вряд ли, это маленькая проверочная программка, так что представленный фрагмент это весь цикл loop. Ладно буду пробовать, еще раз спасибо.

  61. Николай:

    Здравствуйте! Делаю свой первый проект для реализации умного дома. Суть заключается в следующем: Имеется дисплей Nextion HMI, пытаюсь его приручить в качестве панели для управления освещением, а также сбора информации с датчиков температуры и влажности. Дисплей с двумя датчиками (один на улице, другой в соседней комнате) общается через радио модуль nrf2401. Также стоит датчик DH22 подключенный через ардуино уно к дисплею и стоит светодиод, имитирующий включения света управляемый через дисплей. Проблема заключается в том, что по отдельности части кода в функции Loop работают отлично, но когда собираешь все вместе, то светодиод включается и выключается через раз и также показания датчиков приходят с задержкой. Я понимаю, что нужна многозадачность, но как её реализовать в ситуации с nrf2401 не представляю. Извиняюсь за много букв и за код сильно не пинайте, недавно осваиваю ардуино.

    #include #include #include "DHT.h" #include #include #include "nRF24L01.h" #include "RF24.h" SoftwareSerial nextion(2, 3);// Nextion TX подключен к pin 2 и RX подключен pin 3 Ардуино Nextion myNextion(nextion, 9600); //создать объект с именем Nextion myNextion используя nextion последовательный порт @ 9600bps #define DHTPIN 4 #define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321 DHT dht(DHTPIN, DHTTYPE); unsigned long lastTime; unsigned long loopTime; long Temperature = 0, Pressure = 0; float TempNRF1 = 0; float TempNRF2 = 0; void getDataBMP180() { // заполняем данные в массив } typedef struct { int W; int X; float Y; float Z; } B_t; B_t duino1; B_t duino2; RF24 radio(9, 10); // определяем пины на радиомодуль const uint64_t pipe01 = 0xF0F1F2F3F1LL; const uint64_t pipe02 = 0xF0F1F2F3F4LL; #define U_NUM 0x71 // команда UART "Numerical variable data returns" #define PIN_OUT 6 // вывод для управления яркостью byte bright, bright_pwm; // значение яркости 0...100 и 0...255 (для ШИМ) char incomingByte; // входящие данные bool flag = false; // флаг того, что идут нужные нам данные int LEDpin = 7; void setup() { lastTime = millis(); //Считываем время, прошедшее с момента запуска программы loopTime = currentTime; Serial.begin(9600); Wire.begin(); myNextion.init(); dht.begin(); radio.begin(); delay(100); radio.setDataRate(RF24_250KBPS); // Скорость передачи radio.setChannel(110); // Номер канала от 0 до 127 radio.setRetries(15, 15); // Кол-во попыток и время между попытками radio.openWritingPipe(pipe01);// Открываем канал передачи radio.openWritingPipe(pipe02);// Открываем канал передачи radio.openReadingPipe(1, pipe01); // Открываем один из 6-ти каналов приема radio.openReadingPipe(2, pipe02); // Открываем один из 6-ти каналов приема radio.startListening();// Начинаем слушать эфир pinMode(LEDpin, OUTPUT); pinMode(PIN_OUT, OUTPUT); } void loop() { //....................Датчик температуры и влажности........................................ float h = dht.readHumidity();// влажность DH22 float t = dht.readTemperature();//температура DH22 if (millis() + 5000 >= lastTime) { if (isnan(h) || isnan(t)) { return; lastTime = millis(); } //....................Вывод на дисплей........................................ } //....................Включения выключения светодиода........................................ String message = myNextion.listen(); //получение сообщения от дисплея if (message == "65 0 4 1 ffff ffff ffff") { // если код кнопки совподает с зарезервироавнным тогда включается светоиод // myNextion.buttonToggle(button1, "b0", 0, 2); digitalWrite(LEDpin, !digitalRead(LEDpin)); } //....................Радио модуль........................................ String TempNRF2 = String(duino2.Y); String TempNRF1 = String(duino1.Y); uint8_t pipeNum = 0; if (radio.available(&pipeNum)) { if (pipeNum == 1) { radio.read(&duino1, sizeof(duino1)); Serial.print("Arduino 1 = "); if (duino1.Y < 0 ) Serial.print(""); else Serial.print("+"); Serial.println(duino1.Y); } if (pipeNum == 2) { radio.read(&duino2, sizeof(duino2)); Serial.print("Arduino 2 = "); if (duino2.Y = lastTime) { myNextion.setComponentText("t0", String(TempNRF1));//вывод на дисплей температуры с датчика №1 myNextion.setComponentText("t1", String(TempNRF2));//вывод на дисплей температуры с датчика №2 myNextion.setComponentText("t2", String(h));//вывод на дисплей влажности с датчика №3 myNextion.setComponentText("t3", String(t));//вывод на дисплей температуры с датчика №3 lastTime = millis(); } }

  62. Саня нуб:

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

    суть такая:

    есть WiFi датчик ESP8266 (01)

    есть 4 датчика DHT11

    звуковой дальнометр на 400 см

    есть 2 релешки

    1 реле управляет светом другая поливом, свет горит 14 часов подряд 10 часов выключена, вода поливает каждые 7 мин на 15 секунд, данные с датчиков и дальнометра должны отправляться на наш компьютер, уже месяц мучаюсь

    дальнометр считает количество воды в бочке

    и все это дело должно работать параллельно

  63. Денис:

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

  64. Денис:

    Подскажите пожалуйста, вот вы объявляете Flasher led1 (11, 123, 400); вне «петли», а в петле только «led1.Update ();». Вопрос в следующем: Можно ли задавать параметры класса из петли (хотя объявляются они и вне её)? В режиме онлайн, например с ПК?

    • Денис, здравствуйте!

      В первой строчке

      Flasher led1(11, 123, 400);

      мы объявляем объект led1 класса Flasher. При этом, выделяется соответствующий классу Flasher объем памяти. И объект led1 является глобальным (почитайте про области действия переменных, глобальные переменные, локальные переменные). Переменные объекта можно изменять и внутри цикла loop.

      в строчке

      led1.Update ();

      мы используем метод класса Flasher (функцию) для объекта led1.

      Я рекомендовал бы вам почитать какую-нибудь литературу по объектно-ориентирванному программированию на C++.

  65. Юрий:

    Здравствуйте Андрей помогите осмыслить строчку в коде Sweeper sweeper1 (15);

    я понимаю что она для объявления экземпляра сервы, но что за значение в скобках.

    Я пробовал менять значение меняется скорость сервы

  66. Андрей:

    Добрый день.

    Помогите пожалуйста. Делаю первые шаги в программировании Ардуино.

    Собираю цветомузыку с шаговым двигателем. Логика следующая: звучит музыка — работает цветомузыка — крутится двигатель с зеркальной призмой — нет музыки — стоп.

    В «железе» все готово, а в программе...

    Есть готовые скетчи для шаговика (писал сам):

    #include

    Stepper motor = Stepper (200, 7,8,9,10);

    void setup ()

    {

    motor.setSpeed (60);

    }

    void loop ()

    {

    motor.step (1000);

    delay (100);

    motor.step (-2000);

    delay (100);

    }

    Для цветомузыки (честно стырил):

    #include

    #define AUDIOPIN 3

    char im[127], data[127];

    int i=0, val;

    int out = 0;

    int br = 0;

    int redPin = 11; // pins that the LED are attached to

    int greenPin = 12;

    int bluePin = 13;

    void setup () {

    analogWrite (redPin,0); // just test connections

    analogWrite (greenPin,0);

    analogWrite (bluePin,0);

    delay (500);

    analogWrite (redPin,255);

    delay (500);

    analogWrite (redPin,0);

    analogWrite (greenPin,255);

    delay (500);

    analogWrite (greenPin,0);

    analogWrite (bluePin,255);

    delay (500);

    analogWrite (bluePin,0);

    }

    void loop () {

    for (i=0; i < 127; i++){

    val = analogRead (AUDIOPIN)*2;

    data[i] = val;

    im[i] = 0;

    }

    fix_fft (data,im,7,0);

    for (i=0; i<3;i++){

    data[i] = sqrt (data[i] * data[i] + im[i] * im[i]);

    if (i == 0) {

    out = data[i];

    Serial.print ("low — ");

    Serial.println (out);

    Serial.println (val);

    if (out = 10)

    {analogWrite (redPin,out*20);}}

    if (i == 1) {

    out = data[i];

    Serial.print ("mid — ");

    Serial.println (out);

    Serial.println (val);

    if (out = 10)

    {analogWrite (bluePin,out*15);}}

    if (i == 2) {

    out = data[i];

    Serial.print ("hi — ");

    Serial.println (out);

    Serial.println (val);

    if (out = 8)

    {analogWrite (greenPin,out*12);}}

    }

    }

    По отдельности все прекрасно работает. Как запустить их вместе? Спасибо.

  67. Вадим:

    Добрый день!

    Подскажите пожалуйста, как из цикла for убрать delay?

    и сделать так, чтобы светодиоды загорались и тухли плавно?

    Есть 2 светодиода и 2 геркона. герконы замыкаются — диоды загораются. При помощи millis () удалось сделать это «одновременно» и независимо (друг от друга).

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

    Раньше я это делал через for (). но там в каждой итерации присутствует хоть маленький но delay (25). и пропадает вся «многозадачность» и вообще начинает работать как попало.

    int ger1 = A0; // 0 геркон int ger2 = A1; // 1 геркон int ger3 = A2; // 2 геркон int pin1 = 10; // номер пина со светодиодом int ledState1 = LOW; // состояние светодиода // последний момент времени, когда состояние светодиода изменялось unsigned long previousMillis1 = 0; int pin2 = 11; int ledState2 = LOW; unsigned long previousMillis2 = 0; int pin3 = 9; int ledState3 = LOW; unsigned long previousMillis3 = 0; void setup() { pinMode(pin1, OUTPUT); pinMode(pin2, OUTPUT); pinMode(pin3, OUTPUT); pinMode(ger1, INPUT); pinMode(ger2, INPUT); pinMode(ger3, INPUT); } void loop() { // выясняем не настал ли момент сменить состояние светодиода unsigned long currentMillis = millis(); // текущее время в миллисекундах if ((digitalRead(ger1) == HIGH) && (ledState1 == LOW)) // && (currentMillis - previousMillis1 >= OnTime1)) { ledState1 = HIGH; previousMillis1 = currentMillis; // запоминаем момент времени digitalWrite(pin1, ledState1); // реализуем новое состояние } else { ledState1 = LOW; // выключаем previousMillis1 = currentMillis ; // запоминаем момент времени digitalWrite(pin1, ledState1); // реализуем новое состояние } if ((digitalRead(ger2) == HIGH) && (ledState2 == LOW)) // && (currentMillis - previousMillis2 >= OnTime2)) { ledState2 = HIGH; previousMillis2 = currentMillis; // запоминаем момент времени digitalWrite(pin2, ledState2); // реализуем новое состояние } else { ledState2 = LOW; // выключаем previousMillis2 = currentMillis ; // запоминаем момент времени digitalWrite(pin2, ledState2); // реализуем новое состояние } if ((digitalRead(ger3) == HIGH) && (ledState3 == LOW)) // && (currentMillis - previousMillis1 >= OnTime1)) { ledState3 = HIGH; previousMillis3 = currentMillis; // запоминаем момент времени digitalWrite(pin3, ledState3); // реализуем новое состояние } else { ledState3 = LOW; // выключаем previousMillis3 = currentMillis ; // запоминаем момент времени digitalWrite(pin3, ledState3); // реализуем новое состояние } }

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

  68. Илья:

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

    Код такой:

    int reley = 10;

    int rele1 = 13;// пин со светодиодом

    int rele2 = 6; // нога на бум

    long OnTime = 600; // время свечения светодиода, мс

    int ledState = LOW;

    int ledState2 = LOW;

    int pinButton = 7; // пин, к которому подключена кнопка

    int pinButton4 = 12;

    int pinButton2 = 9; // пин, к которому подключена кнопка2

    int pinButton3 = 8;

    unsigned long previousMillis = 0UL;

    bool key = true;

    bool keyLOW = true;

    bool key3 = true;

    void setup ()

    {

    pinMode (rele2, OUTPUT);

    pinMode (rele1, OUTPUT);

    pinMode (reley, OUTPUT);

    pinMode (pinButton, INPUT);

    pinMode (pinButton3, INPUT);

    pinMode (pinButton2, INPUT);

    pinMode (pinButton4, INPUT);

    }

    void loop (){

    if (digitalRead (pinButton3) == HIGH){

    previousMillis = millis (); // запоминаем время когда она нажалать

    digitalWrite (reley, HIGH);

    //ledState = HIGH; // взводим ключ на включение.

    key3 = true;

    }

    else{

    digitalWrite (reley, LOW);

    }

    if (millis () — previousMillis >= OnTime){ // таймер пяти секунд

    ledState = LOW; //потушить по истечении времени

    }

    if ((digitalRead (pinButton2) == LOW) && (keyLOW == false)){

    keyLOW = true;// триггер на отжатие кнопки, пока не отожмешь не пустит в если где проверяет нажатие.

    ledState2 = LOW;

    }

    if ((digitalRead (pinButton2) == HIGH) && (keyLOW == true)){ // если нажата кнопка и ключ true то

    previousMillis = millis (); // запоминаем время когда она нажалась

    ledState2 = HIGH; // взводим ключ на включение.

    keyLOW = false;

    }

    if ((digitalRead (pinButton) == LOW) && (key == false)) key = true;

    if ((digitalRead (pinButton) == HIGH) && (key == true) && key3){ // если нажата кнопка и ключ true то

    previousMillis = millis (); // запоминаем время когда она нажалась

    ledState = HIGH; // взводим ключ на включение.

    key = false;

    key3 = false;

    }

    if ((digitalRead (pinButton4) == LOW) && (key == false)) key = true;

    if ((digitalRead (pinButton4) == HIGH) && (key == true) && key3){ // если нажата кнопка и ключ true то

    previousMillis = millis (); // запоминаем время когда она нажалась

    ledState = HIGH; // взводим ключ на включение.

    key = false;

    key3 = false;

    }

    digitalWrite (rele1,ledState); // включаем или гасим диод в зависимости от переменной ledState

    digitalWrite (rele2,ledState2); // включаем или гасим диод в зависимости от переменной ledState

    }

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

    При нажатии кнопки pinButton3 загорается светик-1, при нажатии кнопки pinButton, светик-2 горит заданное время...не зависимо удерживаешь кнопку или нет. Повторное нажатие кнопки pinButton не должно срабатывать, пока не нажмешь pinButton3. При нажатии кнопки pinButton4 то же самое...но, мне нужно чтобы кнопка pinButton4 не была зависима от кнопки pinButton3...т.е. чтобы кнопка pinButton4 срабатывала по своему алгоритму всегда. Как это реализовать? Спасибо.

  69. Владимир:

    Андрей здравствуйте! Пожалуйста, убедительно прошу Вас, если Вам не трудно помогите старому человеку напишите скетч. Всю жизнь провел с паяльником, а, вот в программировании мне трудновато. Хотелось бы иметь скетч управляющий тремя сервомашинками. К скетчу из двух сервомашинок управляемых джойстиком добавить еще одну сервомашинку управляемую тремя кнопками. По умолчанию серво устанавливается на угол 90°, 1-я кнопка при кратковременном нажатии устанавливает серво в крайне левое положение, 2-я кнопка – в крайне правое. 3-я кнопка возвращает серву на угол 90° из любого положения. Важно серво, находясь в одном из крайних положений, и при случайном нажатии кнопки перевода в противоположное положение останавливалась в среднем положении. Дополнительно хотелось бы иметь возможность корректировать угол поворота и скорость вращения сервомашинки. Плата UNO Pro Micro (Mega 32U4). Буду очень благодарен!

  70. Вячеслав:

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

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

    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 ********************************

    void 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) // ВПЕРЕД

    {

    digitalWrite (MotorRightBack, LOW);

    digitalWrite (MotorRightForward, HIGH);

    analogWrite (MotorRightSpeed, sa);

    digitalWrite (MotorLeftBack, LOW);

    digitalWrite (MotorLeftForward, HIGH);

    analogWrite (MotorLeftSpeed, sa);

    delay (a);

    }

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

    {

    digitalWrite (MotorRightBack, LOW);

    digitalWrite (MotorRightForward, LOW);

    digitalWrite (MotorLeftBack, LOW);

    digitalWrite (MotorLeftForward, HIGH);

    analogWrite (MotorLeftSpeed, sb);

    delay (b);

    }

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

    {

    digitalWrite (MotorRightBack, LOW);

    digitalWrite (MotorRightForward, HIGH);

    analogWrite (MotorRightSpeed, sk);

    digitalWrite (MotorLeftBack, LOW);

    digitalWrite (MotorLeftForward, LOW);

    delay (k);

    }

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

    {

    digitalWrite (MotorRightBack, LOW);

    digitalWrite (MotorRightForward, LOW);

    digitalWrite (MotorLeftBack, LOW);

    digitalWrite (MotorLeftForward, LOW);

    delay (f);

    }

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

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

    {

    digitalWrite (trigPinL, HIGH);

    delay (10);

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

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

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

    delay (50);

    }

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

    {

    digitalWrite (trigPinC, HIGH);

    delay (10);

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

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

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

    delay (50);

    }

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

    {

    digitalWrite (trigPinR, HIGH);

    delay (10);

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

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

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

    delay (50);

    }

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

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

    void loop ()

    {

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

    {

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

    delay (50);

    noTone (Zumm);

    delay (50);

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

    delay (50);

    noTone (Zumm);

    delay (150);

    }

    unsigned long currentMillis = millis ();

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

    {

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

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

    ledState = HIGH;

    else

    ledState = LOW;

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

    }

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

    izmdistL ();

    izmdistC ();

    izmdistR ();

    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 // если измеренная дистанция меньше или равна минимальной — стоим

    {

    stopp (100); // СТОП

    }

    }

  71. Юра:

    подскажите пожалуйст как сделать так чтобы можнобыло задавать интервал мигания и порядок -например зажигаем 1 светодиод на 20сек потом 2 на 15 сек потом 3 и 1 на 10 сек потом 2 на 20 сек и создать светомузыку под определенный трек

  72. Сергей:

    Здравствуйте, Андрей!

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

    Эту схему хочу прикрутить к RAV4 3 поколения для отключения курсовой устойчивости.

    Заранее спасибо!!!!

  73. Андрей:

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

  74. Руслан:

    Добрый день. Спасибо за статью, но так и не получилось применить нормально к своему проекту. Може сможете помочь со скетчем. Кратко опишу ситуацию. В кладовке есть дверь с герконом, при открытии двери включается свет на 30 секунд, потом выключается, если свет не выключился то включается зумер.

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

    Сейчас это реализовано так:

    #define BUTTON_PIN 2 int Door_Led_Pin = 13; // выбрать контакт для светодиода int Door_Sensor_Pin = 12; // контакт для датчика int val = 0; // переменная для хранения состояния датчика void setup() { pinMode(Door_Led_Pin, OUTPUT); // установить Door_Led_Pin как выход pinMode(Door_Sensor_Pin, INPUT); // установить Door_Sensor_Pin как вход pinMode(7, OUTPUT); // объявляем пин 7 как выход это у нас зумер } void loop(){ val = digitalRead(Door_Sensor_Pin); // читать Door_Sensor_Pin if (val == HIGH) { digitalWrite(Door_Led_Pin, HIGH); //выключить реле } else { digitalWrite(Door_Led_Pin, LOW); //включить реле delay(30000); digitalWrite(Door_Led_Pin, HIGH); //выключить реле while(val == LOW){ val = digitalRead(Door_Sensor_Pin); // читать Door_Sensor_Pin tone(7, 900); // включаем на пьезодинамик 900 Гц delay(1000); // ждем 1 секунду noTone(7); // отключаем пьезодинамик на пин 11 delay(1000); // ждем 1 секунду } } }

  75. Александр:

    Добрый день. В Вашем скетче: независимое мигание трех светодиодов. в строке №40 допущена ошибка, Вы предлагаете заменить эту строку на if(currentMillis - previousMillis >= StartDelay) { FlagStart = false; ledState = HIGH; // включаем светодиод digitalWrite(ledPin, ledState); // реализуем новое состояние previousMillis = currentMillis ; // запоминаем момент времени }

    Я пробую поменять, но выдаёт ошибку. Вы не могли бы изменить и показать полностью скетч «от А до Я» с измененной ошибкой.

Оставить комментарий