Управление несколькими процессами с помощью millis() - создание диспетчера задач

v258
Offline
Зарегистрирован: 25.05.2020

В последнее время новички часто задают один и тот же вопрос: как заставить какие-то процессы выполняться независимо друг от друга. Стандартная отсылка к скетчу BlinkWithoutDelay очевидно не всегда помогает новичку понять суть вопроса. Здесь мы попробуем разжевать вопрос более подробно и попытаемся создать некое подобие диспетчера задач.

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

v258
Offline
Зарегистрирован: 25.05.2020

Рассматривать вопрос будем на примере все тех же светодиодов. Заставим работать независимо друг от друга 4 светодиода. Итак, нам нужно, чтобы первый светодиод мигал с интервалом 500 милисекунд. Немного сосредотачиваемся и строго по фэншую пишем такой код:

  // ==== первый светодиод ======================
  static uint32_t led1_timer = millis(); // таймер работы светодиода
  static uint32_t led1_interval = 500;   // интервал работы светодиода в милисекундах
  if (millis() - led1_timer >= led1_interval)
  {
    static bool led_state = false; // флаг состояния светодиода

    led1_timer = millis();  // сохраняем таймер для следующей сработки
    led_state = !led_state; // меняем флаг состояния светодиода на противоположный

    digitalWrite(LED1_PIN, led_state); // выполняем работу - включаем или выключаем светодиод
  }

Если выполняется условие if (millis() - led1_timer >= led1_interval) (стр.4), значит после последней сработки прошло 500 милисекунд, и снова настал момент выполнения кода управления первым светодиодом. Меняем состояние светодиода на противоположное (стр.9 и стр.11) и сохраняем текущее значение таймера для следующей сработки (стр.8). Через следующие 500 милисекунд.

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

  // ==== второй светодиод ======================
  static uint32_t led2_timer = millis(); // таймер работы светодиода
  static uint32_t led2_interval = 20;    // интервал изменения яркости светодиода в милисекундах
  if (millis() - led2_timer >= led2_interval)
  {
    static byte pwm_data = 0;    // значение ШИМ
    static bool dir_flag = true; // направление изменения значения ШИМ

    led2_timer = millis(); // сохраняем таймер для следующей сработки

    analogWrite(LED2_PIN, pwm_data); // выполняем работу -  изменяем яркость свечения

    // изменяем значение ШИМ в зависимости от направления и меняем направления на противоположное при достижении крайних значений
    if (dir_flag)
    {
      pwm_data += 5;
      if (pwm_data == 255)
      {
        dir_flag = false;
      }
    }
    else
    {
      pwm_data -= 5;
      if (pwm_data == 0)
      {
        dir_flag = true;
      }
    }
  }

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

Немного усложним задачу - третий светодиод должен мигать с неравномерным интервалом: включаться на 100 милисекунд и выключаться на 400 милисекунд. Код для него будет выглядеть так:

  // ==== третий светодиод ======================
  static uint32_t led3_timer = millis(); // таймер работы светодиода
  static uint32_t led3_interval = 0;     // интервал работы светодиода в милисекундах; здесь интервал будет меняться в зависимости от состояния светодиода
  if (millis() - led3_timer >= led3_interval)
  {
    static bool led_state = false; // флаг состояния светодиода

    led_state = !led_state; // меняем флаг состояния светодиода на противоположный
    led3_timer = millis();  // сохраняем таймер для следующей сработки

    digitalWrite(LED3_PIN, led_state); // выполняем работу - включаем или выключаем светодиод

    if (led_state) // меняем интервал срабатывания задачи в зависимости от состояния светодиода
    {
      led3_interval = 100; // если светодиод включен, устанавливаем интервал 100 милисекунд
    }
    else
    {
      led3_interval = 400; // если светодиод выключен, устанавливаем интервал 400 милисекунд
    }
  }

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

  // ==== четвертый светодиод ===================
  static uint32_t led4_timer = millis(); // таймер работы светодиода
  static uint32_t led4_interval = 300;   // интервал работы светодиода в милисекундах
  static byte flash_count = 0;           // счетчик срабатываний
  if ((flash_count < 10) && (millis() - led4_timer >= led4_interval))
  {
    static bool led_state = false; // флаг состояния светодиода

    led4_timer = millis();  // сохраняем таймер для следующей сработки
    led_state = !led_state; // меняем флаг состояния светодиода на противоположный

    digitalWrite(LED4_PIN, led_state); // выполняем работу - включаем или выключаем светодиод

    flash_count++; // увеличиваем счетчик срабатываний
  }

Здесь мы сначала проверяем количество срабатываний задачи (flash_count < 10) и, если оно составило 10 (5 * 2 - светодиод должен пять раз включиться и пять раз выключиться), то светодиод больше включаться не должен.

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

Итоговый скетч для всех четырех светодиодов.

#define LED1_PIN 4
#define LED2_PIN 5
#define LED3_PIN 6
#define LED4_PIN 7

void setup()
{
  pinMode(LED1_PIN, OUTPUT);
  pinMode(LED2_PIN, OUTPUT);
  pinMode(LED3_PIN, OUTPUT);
  pinMode(LED4_PIN, OUTPUT);
}

void loop()
{
  // ==== первый светодиод ======================
  static uint32_t led1_timer = millis(); // таймер работы светодиода
  static uint32_t led1_interval = 500;   // интервал работы светодиода в милисекундах
  if (millis() - led1_timer >= led1_interval)
  {
    static bool led_state = false; // флаг состояния светодиода

    led1_timer = millis();  // сохраняем таймер для следующей сработки
    led_state = !led_state; // меняем флаг состояния светодиода на противоположный

    digitalWrite(LED1_PIN, led_state); // выполняем работу - включаем или выключаем светодиод
  }

  // ==== второй светодиод ======================
  static uint32_t led2_timer = millis(); // таймер работы светодиода
  static uint32_t led2_interval = 20;    // интервал изменения яркости светодиода в милисекундах
  if (millis() - led2_timer >= led2_interval)
  {
    static byte pwm_data = 0;    // значение ШИМ
    static bool dir_flag = true; // направление изменения значения ШИМ

    led2_timer = millis(); // сохраняем таймер для следующей сработки

    analogWrite(LED2_PIN, pwm_data); // выполняем работу -  изменяем яркость свечения

    // изменяем значение ШИМ в зависимости от направления и меняем направления на противоположное при достижении крайних значений
    if (dir_flag)
    {
      pwm_data += 5;
      if (pwm_data == 255)
      {
        dir_flag = false;
      }
    }
    else
    {
      pwm_data -= 5;
      if (pwm_data == 0)
      {
        dir_flag = true;
      }
    }
  }

  // ==== третий светодиод ======================
  static uint32_t led3_timer = millis(); // таймер работы светодиода
  static uint32_t led3_interval = 0;     // интервал работы светодиода в милисекундах; здесь интервал будет меняться в зависимости от состояния светодиода
  if (millis() - led3_timer >= led3_interval)
  {
    static bool led_state = false; // флаг состояния светодиода

    led_state = !led_state; // меняем флаг состояния светодиода на противоположный
    led3_timer = millis();  // сохраняем таймер для следующей сработки

    digitalWrite(LED3_PIN, led_state); // выполняем работу - включаем или выключаем светодиод

    if (led_state) // меняем интервал срабатывания задачи в зависимости от состояния светодиода
    {
      led3_interval = 100; // если светодиод включен, устанавливаем интервал 100 милисекунд
    }
    else
    {
      led3_interval = 400; // если светодиод выключен, устанавливаем интервал 400 милисекунд
    }
  }

  // ==== четвертый светодиод ===================
  static uint32_t led4_timer = millis(); // таймер работы светодиода
  static uint32_t led4_interval = 300;   // интервал работы светодиода в милисекундах
  static byte flash_count = 0;           // счетчик срабатываний
  if ((flash_count < 10) && (millis() - led4_timer >= led4_interval))
  {
    static bool led_state = false; // флаг состояния светодиода

    led4_timer = millis();  // сохраняем таймер для следующей сработки
    led_state = !led_state; // меняем флаг состояния светодиода на противоположный

    digitalWrite(LED4_PIN, led_state); // выполняем работу - включаем или выключаем светодиод

    flash_count++; // увеличиваем счетчик срабатываний
  }
}

/* Скетч использует 1544 байт (5%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 68 байт (3%) динамической памяти, оставляя 1980 байт для локальных переменных. Максимум: 2048 байт.
*/

Светодиоды подключаются к цифровым выводам 4, 5, 6 и 7 ардуино.

 

v258
Offline
Зарегистрирован: 25.05.2020

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

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

Итак, мы имеем четыре задачи. Каждая задача имеет: 1 - интервал срабатывания, 2 - собственный таймер. Кроме того, задача может выполняться или не выполняться (см. четвертый светодиод). Т.е. задача должна иметь статус - активна или неактивна, выполняется или не выполняется. Итого имеем три обязательных свойства. А значит мы можем описать абстрактную задачу - ее свойства и методы (конечно, правильнее всего было бы оформить все это в виде класса, но мы - новички в программировании, поэтому будем делать как проще). Попробуем описать задачу в виде структуры. Назовем ее taskData.

struct taskData
{
  bool state;        // текущий статус задачи - активна/неактивна
  uint32_t interval; // интервал выполнения задачи в милисекундах
  uint32_t timer;    // таймер задачи
};

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

void setTaskInterval(taskData &task, uint32_t _interval)
{
  task.interval = _interval;
}

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

Теперь остановка выполнения задачи:

void stopTask(taskData &task)
{
  task.state = false;
}

Здесь тоже все просто - в функцию передается ссылка на задачу, выполнение которой нужно остановить, и статусу этой задачи присваивается значение false.

И для законченности картины - запуск выполнения задачи:

void startTask(taskData &task)
{
  task.state = true;
  task.timer = millis();
}

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

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

bool checkTask(taskData &task)
{
  bool result = false;
  if (task.state && (millis() - task.timer >= task.interval))
  {
    result = true;         // если задача активна и момент ее срабатывания наступил
    task.timer = millis(); // сохраняем таймер для следующей сработки
  }
  return (result); // возвращаем полученный результат
}

В функцию опять таки передается ссылка на задачу, которую нужно проверить. Функция вернет true только в случае, если задача активна, и момент ее срабатывания наступил, в противном случае будет возвращен false.

В итоге наша абстрактная задача приобрела следующий вид:

// свойства задачи
struct taskData
{
  bool state;        // текущий статус задачи - активна/неактивна
  uint32_t interval; // интервал выполнения задачи в милисекундах
  uint32_t timer;    // таймер задачи
};
// методы  задачи
void setTaskInterval(taskData &task, uint32_t _interval);
void stopTask(taskData &task);
void startTask(taskData &task);
bool checkTask(taskData &task);

 

v258
Offline
Зарегистрирован: 25.05.2020

Теперь можно приступить к практическому использованию наших наработок.

Повторим, что нам нужно сделать:

1. Первый светодиод мигает с интервалом 500 милисекунд.

2. Второй светодиод плавно разгорается и плавно гаснет в течение примерно двух секунд.

3. Третий светодиод мигает с неравномерным интервалом - включен 100 милисекунд и выключен 400 милисекунд.

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

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

Для начала, собственно, создадим наши задачи. Для этого нужно объявить и инициализировать четыре переменные типа taskData.

// ==== список задач ============================
taskData led1 = (taskData){true, 500, 0};  // задача активна, интервал срабатывания - 500 милисекунд
taskData led2 = (taskData){true, 20, 0};  // задача активна, интервал срабатывания - 20 милисекунд
taskData led3 = (taskData){false, 200, 0}; // задача неактивна, т.е. не начинает выполняться в момент старта МК, интервал срабатывания - 200 милисекунд
taskData led4 = (taskData){true, 300, 0};  // задача активна, интервал срабатывания - 300 милисекунд

Каждая задача должна выполнять какое-то действие. А значит должна быть функция, которая будет вызываться при срабатывании задачи. В нашем случае это включение и выключение светодиодов.

Итак, первый светодиод:

// ==== первый светодиод ========================
void setLed1()
{
  static bool led_state = false; // флаг состояния светодиода

  led_state = !led_state;            // меняем флаг состояния светодиода на противоположный
  digitalWrite(LED1_PIN, led_state); // выполняем работу - включаем или выключаем светодиод
}

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

Второй светодиод:

// ==== второй светодиод ========================
void setLed2()
{
  static byte pwm_data = 0;    // значение ШИМ
  static bool dir_flag = true; // направление изменения значения ШИМ

  analogWrite(LED2_PIN, pwm_data); // выполняем работу -  изменяем яркость свечения

  // изменяем значение ШИМ в зависимости от направления и меняем направления на противоположное при достижении крайних значений
  if (dir_flag)
  {
    pwm_data += 5;
    if (pwm_data == 255)
    {
      dir_flag = false;
    }
  }
  else
  {
    pwm_data -= 5;
    if (pwm_data == 0)
    {
      dir_flag = true;
    }
  }
}

Третий светодиод:

// ==== третий светодиод ========================
void setLed3()
{
  static bool led_state = false; // флаг состояния светодиода

  led_state = !led_state; // меняем флаг состояния светодиода на противоположный
  if (led_state)
  {
    setTaskInterval(led3, 100); // если светодиод включен, устанавливаем интервал 100 милисекунд
  }
  else
  {
    setTaskInterval(led3, 400); // если светодиод выключен, устанавливаем интервал 400 милисекунд
  }

  digitalWrite(LED3_PIN, led_state); // выполняем работу - включаем или выключаем светодиод
}

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

И четвертый светодиод:

// ==== четвертый светодиод =====================
void setLed4()
{
  static bool led_state = false; // флаг состояния светодиода
  static byte flash_count = 0;   // счетчик срабатываний

  led_state = !led_state;            // меняем флаг состояния светодиода на противоположный
  digitalWrite(LED4_PIN, led_state); // выполняем работу - включаем или выключаем светодиод

  if (++flash_count >= 10) // если задача сработала 10 раз (светодиод 5 раз включился и 5 раз выключился)
  {
    startTask(led3); // запускаем задачу - начинает мигать третий светодиод
    stopTask(led4);  // останавливаем задачу - больше четвертый светодиод включаться не будет
  }
}

Здесь добавлен блок управления задачами led3 (запускается) и led4 (останавливается) после того, как четвертый светодиод мигнет 5 раз.

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

void tick()
{
  if (checkTask(led1))
  {
    setLed1();
  }
  if (checkTask(led2))
  {
    setLed2();
  }
  if (checkTask(led3))
  {
    setLed3();
  }
  if (checkTask(led4))
  {
    setLed4();
  }
}

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

Ну и итоговый скетч:

#define LED1_PIN 4
#define LED2_PIN 5
#define LED3_PIN 6
#define LED4_PIN 7

// свойства задачи
struct taskData
{
  bool state;        // текущий статус задачи - активна/неактивна
  uint32_t interval; // интервал выполнения задачи в милисекундах
  uint32_t timer;    // таймер задачи
};
// методы  задачи
void setTaskInterval(taskData &task, uint32_t _interval);
void stopTask(taskData &task);
void startTask(taskData &task);
bool checkTask(taskData &task);

// ==== список задач ============================
taskData led1 = (taskData){true, 500, 0};  // задача активна, интервал срабатывания - 500 милисекунд
taskData led2 = (taskData){true, 20, 0};   // задача активна, интервал срабатывания - 20 милисекунд
taskData led3 = (taskData){false, 200, 0}; // задача неактивна, т.е. не начинает выполняться в момент старта МК, интервал срабатывания - 200 милисекунд
taskData led4 = (taskData){true, 300, 0};  // задача активна, интервал срабатывания - 300 милисекунд

// ==============================================
void setTaskInterval(taskData &task, uint32_t _interval)
{
  task.interval = _interval;
}

void stopTask(taskData &task)
{
  task.state = false;
}

void startTask(taskData &task)
{
  task.state = true;
  task.timer = millis();
}

bool checkTask(taskData &task)
{
  bool result = false;
  if (task.state && (millis() - task.timer >= task.interval))
  {
    result = true;         // если задача активна и момент ее срабатывания наступил
    task.timer = millis(); // сохраняем таймер для следующей сработки
  }
  return (result); // возвращаем полученный результат
}

// ==== первый светодиод ========================
void setLed1()
{
  static bool led_state = false; // флаг состояния светодиода

  led_state = !led_state;            // меняем флаг состояния светодиода на противоположный
  digitalWrite(LED1_PIN, led_state); // выполняем работу - включаем или выключаем светодиод
}

// ==== второй светодиод ========================
void setLed2()
{
  static byte pwm_data = 0;    // значение ШИМ
  static bool dir_flag = true; // направление изменения значения ШИМ

  analogWrite(LED2_PIN, pwm_data); // выполняем работу -  изменяем яркость свечения

  // изменяем значение ШИМ в зависимости от направления и меняем направления на противоположное при достижении крайних значений
  if (dir_flag)
  {
    pwm_data += 5;
    if (pwm_data == 255)
    {
      dir_flag = false;
    }
  }
  else
  {
    pwm_data -= 5;
    if (pwm_data == 0)
    {
      dir_flag = true;
    }
  }
}

// ==== третий светодиод ========================
void setLed3()
{
  static bool led_state = false; // флаг состояния светодиода

  led_state = !led_state; // меняем флаг состояния светодиода на противоположный
  if (led_state)
  {
    setTaskInterval(led3, 100); // если светодиод включен, устанавливаем интервал 100 милисекунд
  }
  else
  {
    setTaskInterval(led3, 400); // если светодиод выключен, устанавливаем интервал 400 милисекунд
  }

  digitalWrite(LED3_PIN, led_state); // выполняем работу - включаем или выключаем светодиод
}

// ==== четвертый светодиод =====================
void setLed4()
{
  static bool led_state = false; // флаг состояния светодиода
  static byte flash_count = 0;   // счетчик срабатываний

  led_state = !led_state;            // меняем флаг состояния светодиода на противоположный
  digitalWrite(LED4_PIN, led_state); // выполняем работу - включаем или выключаем светодиод

  if (++flash_count >= 10) // если задача сработала 10 раз (светодиод 5 раз включился и 5 раз выключился)
  {
    startTask(led3); // запускаем задачу - начинает мигать третий светодиод
    stopTask(led4);  // останавливаем задачу - больше четвертый светодиод включаться не будет
  }
}

// ==============================================
void tick()
{
  if (checkTask(led1))
  {
    setLed1();
  }
  if (checkTask(led2))
  {
    setLed2();
  }
  if (checkTask(led3))
  {
    setLed3();
  }
  if (checkTask(led4))
  {
    setLed4();
  }
}

void setup()
{
  pinMode(LED1_PIN, OUTPUT);
  pinMode(LED2_PIN, OUTPUT);
  pinMode(LED3_PIN, OUTPUT);
  pinMode(LED4_PIN, OUTPUT);
}

void loop()
{
  tick();
}

/* Скетч использует 1380 байт (4%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 52 байт (2%) динамической памяти, оставляя 1996 байт для локальных переменных. Максимум: 2048 байт.
*/

Светодиоды подключаются к цифровым выводам 4, 5, 6 и 7 ардуино.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Выполняю Вашу просьбу "не цитировать".

"чтобы первый светодиод мигал с частотой 500 милисекунд"

Ну что на это можно сказать? Пожалуй, лишь: "нам удалось ввести систему в такой режим, когда частота колебаний не зависит от периода"

v258
Offline
Зарегистрирован: 25.05.2020

Итак, подведем итоги. 

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

Для добавления новой задачи достаточно сделать три шага:

1. Создать задачу, например:

taskData new_task = (taskData){true, 1000, 0); // создана задача с именем new_task, активная, т.е. начинающая выполняться с момента старта МК, интервал выполнения 1000 милисекунд

2. Написать функцию, которая будет выполняться при срабатывании задачи, например:

void newTask()
{
 // здесь что-то делается
}

3. В функцию tick() добавить новый блок if, который будет проверять эту задачу и вызывать при необходимости ее функцию:

 if (checkTask(new_task))
 {
  newTask();
 }

И после этих манипуляций функция newTask() будет автоматически выполняться через каждые 1000 милисекунд.

ЗЫ: и бонусом мы добились некоторого уменьшения использования памяти. Мелочь, а приятно ))

 

v258
Offline
Зарегистрирован: 25.05.2020

andriano пишет:

Выполняю Вашу просьбу "не цитировать".

"чтобы первый светодиод мигал с частотой 500 милисекунд"

Ну что на это можно сказать? Пожалуй, лишь: "нам удалось ввести систему в такой режим, когда частота колебаний не зависит от периода"

Уел )) 

Заменил на "мигал с интервалом 500 милисекунд". Остальные тоже ))

ua6em
ua6em аватар
Онлайн
Зарегистрирован: 17.08.2016

Ждём когда к обсуждению подключится DetSimen

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

v258 пишет:

Уел )) 

По хорошему там бы еще в "стандартном" для Ардуино стиле "Си с классами" вместо четырех строк 

if (checkTask(led1))
  {
    setLed1();
  }

достаточно было бы одной:

led1.run();

 

v258
Offline
Зарегистрирован: 25.05.2020

andriano пишет:

По хорошему там бы еще в "стандартном" для Ардуино стиле "Си с классами" вместо четырех строк 

if (checkTask(led1))
  {
    setLed1();
  }

достаточно было бы одной:

led1.run();

В этом случае в структуру нужно добавлять еще одно поле типа void (*)(void) и привязывать туда функцию.

Собственно, такую библиотеку я для себя уже давно написал, но тут для новичков... ))

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

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

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

qwone пишет:

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

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

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

v258 пишет:

В этом случае в структуру нужно добавлять еще одно поле типа void (*)(void) и привязывать туда функцию.

Не обязательно.

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

v258
Offline
Зарегистрирован: 25.05.2020

andriano пишет:

v258 пишет:

В этом случае в структуру нужно добавлять еще одно поле типа void (*)(void) и привязывать туда функцию.

Не обязательно.

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

Зачем? В случае с классом функция tick() вообще делается методом класса. И уже сама в дебрях класса делает всю работу. Нужно только указать, какой задаче какая callback-функция соответствует. И собрать все задачи в один массив. И заодно иметь идентификаторы задач (суть индекс задачи в массиве) для управления ими из вне. Но это уже явно не для новичков 

inspiritus
Offline
Зарегистрирован: 17.12.2012

Независимо это иллюзия!

я бы назвал все эти миллисы и тикеры « условно последовательно/параллельно»

главное - внутри каждого дожно быть НА ПОРЯДКИ короче, чем все вместе взятое. Тогда создается иллюзия параллельности.

но лишь ИЛЛЮЗИЯ!

v258
Offline
Зарегистрирован: 25.05.2020

inspiritus пишет:

Независимо это иллюзия!

я бы назвал все эти миллисы и тикеры « условно последовательно»

главное - внутри каждого дожно быть НА ПОРЯДКИ короче, чем все вместе взятое. Тогда создается иллюзия параллельности.

но лишь ИЛЛЮЗИЯ!

Это естественно. Никакой реальной многопоточности в МК не может быть в принципе

rkit
Offline
Зарегистрирован: 23.11.2016

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

template <auto TICKS()> class Timer_base {
    public:
    using T = decltype(TICKS());
    T started_at = 0;
    T interval = 0;
    bool running = false;

    Timer_base() {}
    
    Timer_base(T timeout) {
    	start(timeout);
    }

    void start(T _interval) {
        started_at = TICKS();
        interval = _interval;
        running = true;
    }

    void start_loop() {
        started_at += interval;
        running = true;
    }

    void start_loop(T _interval) {
        started_at += interval;
        interval = _interval;
        running = true;
    }

    bool done() {
        if (running && ((TICKS() - started_at) > interval)) {
            running = false;
            return true;
        }
        return false;
    }

    bool not_done() {
    	if (running) {
    		if ((TICKS() - started_at) > interval) {
    			running = false;
    		}
    		return running;
    	}
    	return false;
    }

    bool done_loop() {
        if (done()) {
            start_loop(interval);
            return true;
        }
        return false;
    }
    
    bool done_loop(T interval) {
        if (done()) {
            start_loop(interval);
            return true;
        }
        return false;
    }

    void stop() {
        running = false;
    }  

    T time_passed() {
    	return TICKS() - started_at;
    }
    
    T time_left() {
        if (!running) return 0;
        return interval > (TICKS() - started_at) ? interval - (TICKS() - started_at) : 0;
    }
    
    T time_end() {
        return started_at + interval;
    }
};

using Timer = Timer_base<millis>;
using Timer_us = Timer_base<micros>;
#include "timer.hpp"

class Led {
    uint8_t pin;
    Timer t;
    bool state = false;
    public:
    Led(uint8_t pin, uint32_t interval): pin(pin), t(Timer(interval)) {}
    void setup() {
       pinMode(pin, OUTPUT);
    }
    void loop() {
       if(t.done_loop()) digitalWrite(pin, state = !state);
    }
};

Led l1(10, 1000);
Led l2(11, 2000);

void setup() {
    l1.setup();
    l2.setup();
}

void loop() {
    l1.loop();
    l2.loop();
}
v258
Offline
Зарегистрирован: 25.05.2020

rkit пишет:

Сделай четко таймер и используй его в любом контектсте.

Еще раз - это некое подобие пособия для новичков. Для себя я эту задачу решил давно

rkit
Offline
Зарегистрирован: 23.11.2016

Это вредное пособие.

b707
Offline
Зарегистрирован: 26.05.2017

rkit пишет:

Это вредное пособие.

ну не совсем. Для v258 точно полезно, что он все это расписал.  А если ему еще и на ошибки укажут - вообще будет хорошо.

В остальном rkit прав. Новички подобные гайды не читают.. Разве что от гайвера :)

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

ua6em пишет:

Ждём когда к обсуждению подключится DetSimen

А я тута каким боком?  У мня давно таймеры от задач отделены. 

v258
Offline
Зарегистрирован: 25.05.2020

b707 пишет:

В остальном rkit прав. Новички подобные гайды не читают.. Разве что от гайвера :)

Это не он написал, а qwone. rkit вообще никогда по делу не пишет )))

v258
Offline
Зарегистрирован: 25.05.2020

b707 пишет:

А если ему еще и на ошибки укажут - вообще будет хорошо.

Вот это было бы по делу

v258
Offline
Зарегистрирован: 25.05.2020

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

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

Поэтому критику типа "setLed1 и setLed2 суть одно и тоже" или "напиши шаблон класса и его используй" конструктивной здесь я считать не буду - в реальной жизни они НЕ обязаны быть одинаковыми, и свой класс диспетчера задач я написал давно и давно использую ))

Зачем этот топик вообще? Чтобы на следующий вопрос "как заставить какие-то процессы выполняться независимо друг от друга" было куда отправить вопрошающего ))

lilik
Offline
Зарегистрирован: 19.10.2017

:-)

Тут глубже...

Есть два и более скетчей с делееями и без кнопок. Как их соединить, не ломая структуру внешнего вида каждого скетча и добавить кнопки - всё через миллис (без всяких классов, наследований, доп. библиотек и т.п.)?

:-)

Ещё проще: есть скетчи - для снежинки и звёздочки на умных пикселях, по три эффекта в каждом с цикличным перебором. Как именно их объединить, а не полностью переписать и добавить кнопку выбора "фефекта-моргалки"?

 

b707
Offline
Зарегистрирован: 26.05.2017

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

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

v258
Offline
Зарегистрирован: 25.05.2020

b707, неконструктивно ))

b707
Offline
Зарегистрирован: 26.05.2017

ну что поделать. Я вообще не люблю учителей и учеников :)

ua6em
ua6em аватар
Онлайн
Зарегистрирован: 17.08.2016

b707 пишет:

ну что поделать. Я вообще не люблю учителей и учеников :)

так кто ты? два обнулённых топора?

v258
Offline
Зарегистрирован: 25.05.2020

b707 пишет:

ну что поделать. Я вообще не люблю учителей и учеников :)


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

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

b707 пишет:

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

Это вы разрабам итальянским скажите. У них что ни пример в официальном тьюториале, то через строку delay(1000);

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

Не смотрел подробно, просто пробежался.

Не как оценку. а просто как мнение про "многопоточность":

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

Став старше, и хорошо контролируя природные позывы, программист вынужденно приходит к правильным выводам:

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

2. Все псевдо-универсальные способы требуют приложения головы "ардуинщика" (при наличии). В таком случае лучше бы "ардуинщику" просто научиться автоматному программированию, чем изучать ЛЮБОЙ суррогат.

3. В контроллерах на ARM обычно достаточно памяти и есть либо библиотека thread или целая FreeRTOS (и подобные). И не нужно собирать велосипед из водопроводных труб.

=======================================================

ВЫВОД: Не нужно создавать суррогаты для облегчения жизни новичкам. Пусть просто учатся строить автоматы. Даже Дедовы "Таймеры и Сообщения" новичок пусть использует не потому, что носом ткнули, а потому, что он понял ЧТО и ЗАЧЕМ ему нужно.

Повтор: новичков, даже если отбиваются, нужно привязывать к батарее и учить писать автоматы. Просто на С++. Вообще без библиотек, пока не поймут какие им библиотеки нужны. Тогда можно отвязывать! ;)) (это иносказание, а не призыв к противоправным действиям).

b707
Offline
Зарегистрирован: 26.05.2017

ОК, не буду мешать.

v258 - а вы не ведитесь на неконструктив, делайте как считаете нужным.

 

Добавка - полностью согласен с Дракулой. примерно это и хотел выразить.

Logik
Offline
Зарегистрирован: 05.08.2014

wdrakula пишет:

Не смотрел подробно, просто пробежался.

Не как оценку. а просто как мнение про "многопоточность":

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

Став старше, и хорошо контролируя природные позывы, программист вынужденно приходит к правильным выводам:

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

2. Все псевдо-универсальные способы требуют приложения головы "ардуинщика" (при наличии). В таком случае лучше бы "ардуинщику" просто научиться автоматному программированию, чем изучать ЛЮБОЙ суррогат.

3. В контроллерах на ARM обычно достаточно памяти и есть либо библиотека thread или целая FreeRTOS (и подобные). И не нужно собирать велосипед из водопроводных труб.

=======================================================

ВЫВОД: Не нужно создавать суррогаты для облегчения жизни новичкам. Пусть просто учатся строить автоматы. Даже Дедовы "Таймеры и Сообщения" новичок пусть использует не потому, что носом ткнули, а потому, что он понял ЧТО и ЗАЧЕМ ему нужно.

Повтор: новичков, даже если отбиваются, нужно привязывать к батарее и учить писать автоматы. Просто на С++. Вообще без библиотек, пока не поймут какие им библиотеки нужны. Тогда можно отвязывать! ;)) (это иносказание, а не призыв к противоправным действиям).

Ты шо там, трезвый шоле пишешь?

А то мне и добавить нече тока согласится!

v258
Offline
Зарегистрирован: 25.05.2020

Изменил коды примеров, повторяющихся кодов больше нет

Logik
Offline
Зарегистрирован: 05.08.2014

v258 пишет:

Изменил коды примеров, повторяющихся кодов больше нет

А если найду?

ПС. Где код?

Green
Offline
Зарегистрирован: 01.10.2015

Другой пример со светодиодами. Это конечно не диспетчер, но что бы понимать что есть и другие варианты.

#define LED1_PIN 4
#define LED2_PIN 5
#define LED3_PIN 6
#define LED4_PIN 7

void setup() {
  pinMode(LED1_PIN, OUTPUT);
  pinMode(LED2_PIN, OUTPUT);
  pinMode(LED3_PIN, OUTPUT);
  pinMode(LED4_PIN, OUTPUT);
}

#define blink(L,H,P,C) /* Led, High, Period, Count */                 \
  do { static uint16_t t, c = C * 2;                                  \
    if (c && (uint16_t)millis() - t >= (digitalRead(L) ?  H : P - H)) \
      { t = millis(); digitalWrite(L, !digitalRead(L)); --c; }        \
  } while (0)

void loop() {
  blink(LED1, 100,  500, 10);
  blink(LED2, 100, 1000,  5);
  blink(LED3, 200, 2000, 15);
  blink(LED4, 300, 1500, 25);
}

 

v258
Offline
Зарегистрирован: 25.05.2020

Logik пишет:

v258 пишет:

Изменил коды примеров, повторяющихся кодов больше нет

А если найду?

ПС. Где код?

Посты #1, #3 и разъяснение в посте #23 ))

v258
Offline
Зарегистрирован: 25.05.2020

Green пишет:

Другой пример со светодиодами. Это конечно не диспетчер, но что бы понимать что есть и другие варианты.

#define LED1_PIN 4
#define LED2_PIN 5
#define LED3_PIN 6
#define LED4_PIN 7

void setup() {
  pinMode(LED1_PIN, OUTPUT);
  pinMode(LED2_PIN, OUTPUT);
  pinMode(LED3_PIN, OUTPUT);
  pinMode(LED4_PIN, OUTPUT);
}

#define blink(L,H,P,C) /* Led, High, Period, Count */                 \
  do { static uint16_t t, c = C * 2;                                  \
    if (c && (uint16_t)millis() - t >= (digitalRead(L) ?  H : P - H)) \
      { t = millis(); digitalWrite(L, !digitalRead(L)); --c; }        \
  } while (0)

void loop() {
  blink(LED1, 100,  500, 10);
  blink(LED2, 100, 1000,  5);
  blink(LED3, 200, 2000, 15);
  blink(LED4, 300, 1500, 25);
}

Это не совсем то. Здесь чистый блинк с разными периодами. У меня же светодиоды - только для примера.

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

Но тоже интересно ))

Logik
Offline
Зарегистрирован: 05.08.2014

Green пишет:

Другой пример со светодиодами. 

Дублирование кода убрано, но не совсем. В исходнике - да. Но дефайник развернется и в исполняемом коде будет дублирование. Почему макрос blinc не функция? К тому же задача решена не в полном объеме. Там для 3 и 4-го светодиодов есть допусловия.

Green
Offline
Зарегистрирован: 01.10.2015

Это не точная копия, просто для примера.) Функция - если только inline, потому как переменные не могут быть общими. Но даже force_ inline/always_inline, как я понимаю, не гарантирует что компилятор выполнит указание.(

lilik
Offline
Зарегистрирован: 19.10.2017
#define LED1_PIN 4
#define LED2_PIN 5
#define LED3_PIN 6
#define LED4_PIN 7

void setup() {
  pinMode(LED1_PIN, OUTPUT);
  pinMode(LED2_PIN, OUTPUT);
  pinMode(LED3_PIN, OUTPUT);
  pinMode(LED4_PIN, OUTPUT);
}
void loop() {
  blink_(LED1_PIN, 100,  500, 10);
  blink_(LED2_PIN, 100, 1000,  5);
  blink_(LED3_PIN, 200, 2000, 15);
  blink_(LED4_PIN, 300, 1500, 25);
}
void blink_ (int L,int H,int P,int C){
  do { static uint16_t t, c = C * 2;                                  \
    if (c && (uint16_t)millis() - t >= (digitalRead(L) ?  H : P - H)) \
      { t = millis(); digitalWrite(L, !digitalRead(L)); --c; }        \
  } while (0);
}

Посмотрел, переделал так, а то ИДЕ ругалась упорно и долго.

Green
Offline
Зарегистрирован: 01.10.2015

Да, похоже что это даже сложнее для начинающих.(
В лупе должно быть, естественно:

  blink(LED1_PIN, 100,  500, 10);
  blink(LED2_PIN, 100, 1000,  5);
  blink(LED3_PIN, 200, 2000, 15);
  blink(LED4_PIN, 300, 1500, 25);

Но совсем не так как у вас, lilik.

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018

Green, я не понял твою задумку - разъясни. У меня тоже ругался компилятор, пока не изменил твой код вот так:

#define LED1_PIN 4
#define LED2_PIN 5
#define LED3_PIN 6
#define LED4_PIN 7

void setup() {
  pinMode(LED1_PIN, OUTPUT);
  pinMode(LED2_PIN, OUTPUT);
  pinMode(LED3_PIN, OUTPUT);
  pinMode(LED4_PIN, OUTPUT);
}

#define blink(L,H,P,C) /* Led, High, Period, Count */                 \
  do { static uint16_t t, c = C * 2;                                  \
    if (c && (uint16_t)millis() - t >= (digitalRead(L) ?  H : P - H)) \
      { t = millis(); digitalWrite(L, !digitalRead(L)); --c; }        \
  } while (0)

void loop() {
  blink(LED1_PIN, 100,  500, 10);
  blink(LED2_PIN, 100, 1000,  5);
  blink(LED3_PIN, 200, 2000, 15);
  blink(LED4_PIN, 300, 1500, 25);
}

 

Green
Offline
Зарегистрирован: 01.10.2015

Да какая там задумка! Препроцессор в loop() заменяет blink c аргументами на описанный выше.
Ошибся я в loop-е. Только тема то v258, а мы тут макросы перетираем.)

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018

Green пишет:

Да какая там задумка!

А... А меня смутили слова lilik, что не компилируется. Попробовал - действительно. Оказалось очепятка в loop() )))

Logik
Offline
Зарегистрирован: 05.08.2014

Green пишет:

В лупе должно быть, естественно:

  blink(LED1_PIN, 100,  500, 10);
  blink(LED2_PIN, 100, 1000,  5);
  blink(LED3_PIN, 200, 2000, 15);
  blink(LED4_PIN, 300, 1500, 25);

А ниче, что повторяющиеся действия не оформлены в цикл? ;)

Green
Offline
Зарегистрирован: 01.10.2015

Logik пишет:

А ниче, что повторяющиеся действия не оформлены в цикл? ;)

What is this?
В loop-е ведь всё. Вот и повторяется.

Logik
Offline
Зарегистрирован: 05.08.2014

This is laga!

Будет 20 светодиодов, буш 20 раз повторять вызов. Циклы делать в бурсе не учили?

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018

Green пишет:

Logik пишет:

А ниче, что повторяющиеся действия не оформлены в цикл? ;)

What is this?
В loop-е ведь всё. Вот и повторяется.

А мне, как бывшему ПэХэПэшнику интересно, как это конструкцию LED{x}_PIN в Си сделать можно? В PHP то запросто, там и функции так можно вызывать и даже объекты создавать ))

Logik
Offline
Зарегистрирован: 05.08.2014

Чисто тебе, как  бывшему ПэХэПэшнику. Делай как то так


typedef struct LED
{
    uint8_t pin;  //куда подключен светодиод
    uint8_t stat; //текущее состояни светодиода
    uint16_t cnt;  //счетчик оставшихся времени
    uint16_t poluperiod;  //пол периода моргания
};


LED led[]={
  {2, 0, 0,500}, 
  {3, 0, 0,400},
  {4, 0, 0,100},
  {13,0, 0,300};

void setup()
{
 for(uint8_t a=0;a<sizeof(led)/sizeof(led[0]);a++)
  {
    pinMode(led[a].pin,OUTPUT);
  }
.....

void loop()
{
 for(uint8_t a=0;a<sizeof(led)/sizeof(led[0]);a++)
  {
    blink(a);
  }





Ну и блинк поправить, теперь он получает номер записи в led, где все переменные относящиеся к этому светодиоду лежат.  Если надо подробней (ну т.е. решить задачу ТС в полном объеме), то позже. Пришло время трапезы.