Как выполнять многозадачность с помощью arduino, например, обновлять данные, а также проверять состояние?

Здесь данные обновляются каждые 2 секунды. Но когда условие истинно, тогда запускается процесс клапана и двигателя, и данные обновляются только после завершения этой части (т. Е. Данные обновляются через 25 секунд). Я хочу, чтобы обновление данных и проверка состояния выполнялись одновременно.

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define ONE_WIRE_BUS 4
#define TEMPERATURE_PRECISION 9

#define motor 6
#define valve 7

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

const float thld = 35;

#define SCREEN_WIDTH 128 //ширина OLED-дисплея, в пикселях
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

void setup() {
  Serial.begin (115200);
  pinMode(motor, OUTPUT);
  pinMode(valve, OUTPUT);
  delay(10);

    if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Адрес 0x3D для 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  }
    delay(2000);
 
  display.setTextColor(WHITE);
}
 void loop() {   
 sensors.requestTemperatures(); 
  float temp = sensors.getTempCByIndex(0);   
  Serial.println(temp);
  Serial.println((char)176);
  Serial.println("C  |  ");
    if ( temp > thld)
  {
  digitalWrite (valve, LOW);
  delay (5000);
  digitalWrite (motor, LOW);
  delay (10000);

  digitalWrite (motor, HIGH);
  delay (6000);
    digitalWrite (valve, HIGH);
  delay (2000);
  }
  else
  {
    digitalWrite (motor, HIGH);
    digitalWrite (valve, HIGH);
  }

   display.clearDisplay();
   display.setCursor(0, 0);
   display.setTextSize(1);
   display.println("Temperature");   
   display.setCursor(0, 30);
   display.println("Soil & Water Engg.");
   display.setTextSize(2);
   display.setCursor(0,10);
   display.println(temp);
   display.setCursor(75, 10);
   display.println("C");
   display.setCursor(0,40);
     display.display();
   
delay (2000);
 }

, 👍3

Обсуждение

Не используйте delay() ...?, @Majenko

Для этого есть учебное пособие: [Моргайте без задержки] (https://www.arduino.cc/en/Tutorial/BuiltInExamples/BlinkWithoutDelay )., @Edgar Bonet


3 ответа


6

Вам нужно подумать об этом с помощью реального аналога.

Мне нравится использовать вареное яйцо...

Есть три способа сварить яйцо:

Блокирующая операция

  1. Положите яйцо в кипящую воду
  2. Стойте и смотрите на него минуты три или около того
  3. Достаньте яйцо из воды и съешьте его

Работа таймера

  1. Положите яйцо в кипящую воду
  2. Установите таймер на 3 минуты
  3. Уходи и займись чем-нибудь другим
  4. Когда зазвонит будильник достаньте яйцо и съешьте его

Опрос времени

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

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

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

В IDE есть пример под названием BlinkWithoutDelay, который реализует опрос времени очень упрощенным способом - запишите время, затем сравните это время и текущее время, а по истечении времени измените состояние светодиода, записав новое время.

,

0

Общий подход к неблокирующему коду заключается в использовании millis() и сравнении длительностей:

bool active = false;
unsigned long start = 0;
const unsigned long duration = 25000; // 25 секунд

void loop() {
    unsigned long now = millis();
    if (start_condition) {
        active = true;
        start = now;
        turn_on();
    }
    if (active && now - start >= duration) {
        active = false;
        turn_off();
    }
}

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

Одна ловушка для новичков: никогда не сравнивайте время, только продолжительность! Поскольку millis() обтекается (через 2 ^ 32 мс, что составляет ~ 49,7 дней), у вас может быть время начала , которое больше текущего времени, но если вычесть 2 раза, это всегда дает правильную продолжительность (если продолжительность сама по себе меньше 50 дней)

Таким образом, единственный правильный способ проверки времени с помощью millis - это:

(later_time - earlier_time) compared-with duration

Мне нравится библиотека, которая делает все это так просто: "Тикер".

  • https://www.arduino.cc/reference/en/libraries/ticker /
  • Документация: https://github.com/sstaub/Ticker

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

По умолчанию используется микросекундное разрешение с миллисекундной спецификацией:

Ticker x(callback, time_in_millisec); // повтор по умолчанию равен 0 (непрерывный), разрешение равно микронам

Это работает только для продолжительности до 70 минут, для более длительных периодов вы должны использовать миллисекунды:

Ticker x(callback, time_in_millisec, /*repeat=*/0, /*resolution=*/MILLIS);

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

void mycallback() {dostuff();}
Ticker x(mycallback, ....);

Или вы можете использовать лямбда-выражение C ++ для создания безымянной функции, которая делает то же самое.

Ticker x([](){dostuff();}, ....);

Вот ваш код, переписанный с помощью Ticker с некоторыми предположениями с моей стороны о том, что вы намереваетесь, вы должны быть в состоянии адаптировать идеи

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Ticker.h>

#define ONE_WIRE_BUS 4
#define TEMPERATURE_PRECISION 9

#define motor 6
#define valve 7

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

// используйте пороговое значение separatethreshold для включения / выключения, чтобы задать некоторый гистерезис
const float on_thld = 36;
const float off_thld = 35;

#define SCREEN_WIDTH 128 //ширина OLED-дисплея, в пикселях
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

void check_sensors();

// Этот тикер будет вызывать check_sensors каждые 2 секунды
Ticker ticker_sensors(check_sensors, 2000, 0);

// Когда мы включим клапаны, мы будем использовать это, чтобы запланировать включение двигателя на 5 секунд позже (событие 1 раз при каждом запуске ())
Ticker ticker_motor_start([](){ digitalWrite(motor, LOW); }, 5000, 1);
// если вам не нравится этот синтаксис, вы могли бы использовать:
// аннулировать turn_on_motor() {digitalWrite(двигатель, НИЗКИЙ);}
// Бегущая строка ticker_motor_start(turn_on_motor, 500, 1);

// Когда мы выключим двигатель, мы будем использовать это, чтобы запланировать отключение клапана на 5 секунд позже (событие 1 раз при каждом запуске ())
Ticker ticker_valve_off([](){ digitalWrite(valve, HIGH); }, 5000, 1);

void check_sensors() {
    sensors.requestTemperatures();
    float temp = sensors.getTempCByIndex(0);
    Serial.println(temp);
    Serial.println((char)176);
    Serial.println("C  |  ");
    if (temp > on_thld) {
        if (ticker_motor_start.state() != RUNNING) {
            // отмена любого ожидающего отключения клапана
            ticker_valve_off.stop();

            // включите клапан и запланируйте цифровую запись включения
            digitalWrite(valve, HIGH);
            ticker_motor_start.start();
        }
    } else if (temp < off_thld) {
        if (ticker_motor_start.state() == RUNNING) {
            // выключите двигатель и запланируйте клапаны, чтобы отключить
            digitalWrite(motor, HIGH);
            ticker_valve_off.start();
        }
    }

    display.clearDisplay();
    display.setCursor(0, 0);
    display.setTextSize(1);
    display.println("Temperature");
    display.setCursor(0, 30);
    display.println("Soil & Water Engg.");
    display.setTextSize(2);
    display.setCursor(0,10);
    display.println(temp);
    display.setCursor(75, 10);
    display.println("C");
    display.setCursor(0,40);
    display.display();
}

void setup() {
    Serial.begin(115200);
    digitalWrite(motor, HIGH); // установите значение перед включением вывода
    digitalWrite(valve, HIGH); // установите значение перед включением вывода
    pinMode(motor, OUTPUT);
    pinMode(valve, OUTPUT);

    if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Адрес 0x3D для 128x64
        Serial.println(F("SSD1306 allocation failed"));
        for(;;);
    }

    display.setTextColor(WHITE);
    ticker_sensors.start();
}


void loop() {
    // обновите все тикеры - если они не запущены, это не работает
    // Они только проверяют время и решают, когда вызывать свои обратные вызовы во время update()
    ticker_sensors.update();
    ticker_motor_start.update();
    ticker_valve_off.update();
}
,

0

Не все процессы основаны на времени, поэтому для полноты картины

loop:
  read the time;
  if time to run process1,
    process1();
  if time to run process2,
    process2();
  poll external event1;
  if event1 completed,
     external1();
  poll external event2;
  if event2 completed,
     external2();
end;
,