Код с множественными условиями

coding-standards

В моем проекте есть 4 поплавковых выключателя, 2 клапана и 3 насоса. Я создал скетч и загрузил его. Но мой проект все время держит все «ВЫХОД» на «ВЫСОКОМ». Не могу найти, в чем проблема, можете помочь?

Это мой набросок:

#include <DS3231.h>
#include <Wire.h>
#include <LiquidCrystal.h>
DS3231  rtc(SDA, SCL); 

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
int x = 45;
int y = 47;
int z = 41;
int w = 43;

int WaterIn =  40;
int WaterOut = 42;

int Pump1 = 25;
int Pump2 = 24;
int Pump3 = 27;

void setup() 
{
  rtc.begin();
  lcd.begin(16,2);
  // Запускаем интерфейс I2C
  Wire.begin();
  // Запускаем последовательный интерфейс
  Serial.begin(9600);
  //rtc.setDOW(ВТОРНИК); // Установить день недели на ВОСКРЕСЕНЬЕ
  //rtc.setTime(1, 15, 0); // Установите время на 12:00:00 (24-часовой формат)
  //rtc.setDate(6, 6, 2017); // Месяц день год

 //Настройка контактов поплавкового переключателя
  pinMode (x, INPUT);
  pinMode (y, INPUT);
  pinMode (z, INPUT);
  pinMode (w, INPUT);

 //Настройка ирригационных клапанов
  pinMode(WaterIn, OUTPUT);  //клапан полива "in"
  pinMode(WaterOut, OUTPUT);  //ирригационный "выходной" клапан

//Настройка насосов
  pinMode(Pump1, OUTPUT);  
  pinMode(Pump2, OUTPUT);  
  pinMode(Pump3, OUTPUT);    
}  // конец настройки

void Calendar(){

 lcd.setCursor(0,0);
 lcd.print("Real Time Clock  ");
 lcd.setCursor(0,1);
 lcd.print("Time: ");
 lcd.print(rtc.getTimeStr());
 delay(1000);
 lcd.setCursor(0,1);
 lcd.print("Date: ");
 lcd.print(rtc.getDateStr());
 delay(1000);
 lcd.setCursor(0,1);
 lcd.print("Day: ");
 lcd.print(rtc.getDOWStr());
 lcd.print("           ");
 delay(1000);
 lcd.setCursor(0,1);
 lcd.print("Temp: ");

 lcd.print(rtc.getTemp());
 lcd.print(" C");
 lcd.print("          ");
 delay(1000);
}  // конец настройки

void Valves(){
    int pinToTurnHigh = 24;
    if ((x == HIGH) && (y == HIGH) && (z == HIGH) && (w == LOW))  //Условие 1
    {
        pinToTurnHigh = WaterIn;
        pinToTurnHigh = Pump1;
        pinToTurnHigh = Pump2;
        pinToTurnHigh = Pump3;
    }       
    if ((x == LOW) && (y == LOW) && (z == HIGH) && (w == HIGH))  //Условие 2
    {
       pinToTurnHigh = WaterOut;
    } 
    resetAllPumpValve();
    digitalWrite(pinToTurnHigh, HIGH);
}  // конец настройки

void resetAllPumpValve(){
     digitalWrite(WaterIn, LOW); 
     digitalWrite(Pump1, LOW); 
     digitalWrite(Pump2, LOW);
     digitalWrite(Pump3, LOW);
     digitalWrite(WaterOut, LOW);
}  // конец настройки
void loop()
{
    Calendar();
    Valves();
    resetAllPumpValve();  
}  // конец цикла

, 👍2

Обсуждение

используйте serial.print() для отображения отладочной информации на последовательной консоли .... убедитесь, что поток программы соответствует вашим ожиданиям., @jsotola

Я новичок в ардуино, можете объяснить? Вы имеете в виду заменить lcd.print() на serial.print()?, @user56886

Нет, скорее, в дополнение к командам lcd.print(). Таким образом, вы можете видеть на последовательном мониторе, куда идет ваш код во время выполнения. delay() также может пригодиться., @sa_leinad


3 ответа


Лучший ответ:

1

Крисл провел отличный анализ вашей программы и проблем в нем. Я просто хотел бы добавить пункт о том, как справиться с Функция Календарь(). Вам было предложено ознакомиться с Учебное пособие по Blink Without Delay по Arduino, и я поддерживаю это предложение. Это действительно первое, что вы должны сделать.

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

  • НИЗКИЙВЫСОКИЙНИЗКИЙ

Вместо этого ваша подпрограмма отображения будет иметь четыре состояния и выполнять следующие действия: переходы:

  • SHOW_TIMESHOW_DATESHOW_DAYSHOW_TEMPSHOW_TIME

Подходящая концепция программирования для работы с такими системами называется «конечным автоматом», и я предлагаю вам также изучить это руководство по конечному автомату. Этот учебник предлагает очень общая управляющая структура switch/case для программирования FSM. Однако, если есть какое-то поведение, общее для всех состояний (в вашем случае: тот факт, что каждый из них длится ровно одну секунду), вы можете учесть это поведение вне конструкции switch/case.

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

const LCD_DISPLAY_TIME = 1000;  // display each item for 1,000 ms

Затем setup(), сразу после инициализации LCD:

lcd.setCursor(0, 0);
lcd.print("Real Time Clock ");

Как правило, setup() — это правильное место для всего, что только нужно сделать один раз, в начале программы.

И, наконец, функция Calendar(), которая будет вызываться из loop():

void Calendar()
{
    // Ничего не делать, если не пришло время обновить дисплей.
    static uint32_t last_change = -LCD_DISPLAY_TIME;
    if (millis() - last_change < LCD_DISPLAY_TIME) return;
    last_change += LCD_DISPLAY_TIME;

    // Переход к следующему элементу: Время -> Дата -> День -> Температура -> Время
    lcd.setCursor(0, 1);
    static enum { SHOW_TEMP, SHOW_TIME, SHOW_DATE, SHOW_DAY } state;
    switch (state) {
    case SHOW_TEMP:
        lcd.print("Time: ");
        lcd.print(rtc.getTimeStr());
        state = SHOW_TIME;
        break;
    case SHOW_TIME:
        lcd.print("Date: ");
        lcd.print(rtc.getDateStr());
        state = SHOW_DATE;
        break;
    case SHOW_DATE:
        lcd.print("Day: ");
        lcd.print(rtc.getDOWStr());
        state = SHOW_DAY;
        break;
    case SHOW_DAY:
        lcd.print("Temp: ");
        lcd.print(rtc.getTemp());
        lcd.print(" C");
        state = SHOW_TEMP;
        break;
    }
    lcd.print("           ");  // накладка
}
,

4

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

  1. Следующие объявления в глобальной области действия кажутся номерами контактов:

    int x = 45;
    int y = 47;
    int z = 41;
    int w = 43;
    
    int WaterIn =  40;
    int WaterOut = 42;
    
    int Pump1 = 25;
    int Pump2 = 24;
    int Pump3 = 27;
    

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

    if ((x == HIGH) && (y == HIGH) && (z == HIGH) && (w == LOW))
    

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

    if( digitalRead(x) == HIGH && digitalRead(y) == HIGH && digitalRead(z) == HIGH && digitalRead(w) == HIGH)
    

    И это можно сократить до:

    if( digitalRead(x) && digitalRead(y) && digitalRead(z) && digitalRead(w) )
    
  2. В упомянутых операторах if вы устанавливаете переменную несколько раз, ничего с ней не делая. Любая последовательная запись в эту переменную перезапишет предыдущее значение. После блока кода

    pinToTurnHigh = WaterIn;
    pinToTurnHigh = Pump1;
    pinToTurnHigh = Pump2;
    pinToTurnHigh = Pump3;
    

    Переменная pinToTurnHigh равна 27 (Pump3). Значения, которые вы присвоили непосредственно перед этой строкой, теряются. Вам действительно нужно что-то делать с переменной, иначе первые 3 строки этого блока не имеют смысла.

  3. В большинстве случаев не рекомендуется использовать переменные с изменяющимися значениями для обработки номеров выводов, поскольку это затруднит понимание кода. Схема в основном зашита, она не изменится. Таким образом, нет необходимости присваивать номера выводов изменяющейся переменной. Вместо этого вы можете напрямую делать то, что вам нужно, с постоянным номером контакта. (На самом деле оптимизатор кода, идущий в комплекте с компилятором, может оптимизировать неизменяемые переменные с номером контакта, чтобы вы прописали их в своем коде, но в самом коде их может и не быть.) Тут вроде бы и нужно чтобы записать значение HIGH для всех упомянутых контактов. Вы можете сделать это напрямую:

    digitalWrite(WaterIn, HIGH);
    digitalWrite(Pump1, HIGH);
    digitalWrite(Pump2, HIGH);
    digitalWrite(Pump3, HIGH);
    

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

  4. Теперь время вашего кода: функция Calendar() заблокирует остальную часть кода примерно на 4 секунды, потому что вы использовали так много delay(). В это время Arduino ни на что не реагирует. Переключатели тоже ничего не сделают (поскольку Arduino занят ожиданием в delay() вместо проверки переключателей). Код, который вызывается после функции Calendar(), не содержит delay(), поэтому он будет работать очень быстро. В функции Valves() вы сначала сбрасываете все насосы и клапаны на LOW. Затем вы пишете HIGH на один контакт (как упоминалось выше, я предполагаю, что вы хотели написать их все при условии). После этого программа вернется к функции loop() и снова выполнит функцию resetAllPumpValve(), которая установит для всех выходов значение LOW.

    В целом это означает, что Arduino возится с функцией Calendar(), не делая ничего другого. Затем он будет сбрасывать, устанавливать и снова сбрасывать контакты. Поскольку там нет временного кода, это произойдет очень быстро. Таким образом, контакты должны быть LOW большую часть времени и включаться примерно каждые 4 секунды в течение небольшого промежутка времени (небольшое количество миллисекунд, так как это примерно время для выполнения digitalWrite). () или его братья и сестры). При этом моторы или ценности вряд ли успеют сдвинуться с места.

    Чтобы решить эту проблему, вы можете изучить пример BlinkWithoutDelay, поставляемый с Arduino IDE. Он показывает неблокирующий стиль кодирования, который будет что-то делать, только если пришло время это сделать. Так же, как когда вы печете пиццу. Вы не будете сидеть перед духовкой и терпеливо ждать, пока пицца полностью пропечется. Вы поставите ее в духовку и регулярно (может быть, каждые 5 минут) смотрите, готова ли пицца к употреблению. Тем временем вы займетесь другими делами. Это то, что там делается с помощью функции millis(), которая вернет количество миллисекунд, прошедших с момента запуска программы. Вы можете изучить пример и узнать больше о нем в Google. Это неоднократно обсуждалось в Интернете.

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

Развлекайтесь во время обучения :-)

,

**Выдающийся** анализ проблем с этим кодом. (проголосовал) Я прошел часть пути и развел руками. Я не мог понять, что он должен был делать, и видел так много проблем, что отказался от попыток их всех решить. Вы были очень внимательны и терпеливы, и дали четкие объяснения. Ура!, @Duncan C


1

Я бы добавил к ответу Крисла, сказав, что если вы хотите сделать код более компактным, вы можете представить 4 логические переменные одним байтом,

например, w HIGH + x HIGH + y LOW + z LOW = 1100.
например w HIGH + x HIGH + y LOW + z HIGH = 1101.

Тогда было бы проще реализовать оператор switch вместо if( digitalRead(x) == HIGH && digitalRead(y) == HIGH && digitalRead (z) == HIGH && digitalRead(w) == HIGH) и т. д.

Например:

byte state;

void setup(){}

void loop(){
 // вставляем код для чтения состояния
 switch (state)
 {
     case 1: 
         // вставляем код для запуска, если state = 0b0001;
     break;

     case 13: 
         // вставляем код для запуска, если state = 0b1101;
     break;

     default: // код для запуска, если состояние не соответствует ни одному из случаев
 }
 // вставляем другой код
}

Если вы хотите использовать цифровые контакты 30-37 ATmega, вы можете использовать команду порта AVR PINC для одновременного считывания всех состояний HIGH/LOW с помощью одной инструкции. Я оставляю это вам в качестве упражнения!

,

Это может затруднить чтение и понимание программы. Бывают ситуации, когда ваше предложение сделает программу _более_ читаемой, например, если необходимо рассмотреть все 16 случаев и нет простого способа факторизации случаев. Но если нужно обработать только два или три случая, if/else, скорее всего, будет более компактным и более читаемым., @Edgar Bonet

Совершенно верно, но я написал «если вы хотели сделать код более компактным», не обязательно читабельным., @MichaelT

Я исправил Calendar(), он работает отлично., @user56886