Помогите с прерыванием

Тема в разделе "Arduino & Shields", создана пользователем vlad1995, 30 май 2019.

  1. vlad1995

    vlad1995 Нуб

    Необходимо сделать терморегулятор. Есть arduino uno, 18b20 lcd 16:2 i2c, и энкодер. Все работает но из-за медленного 18b20 не работает энкодер. Соответственно необходимо использовать аппаратное прерывание. Энкодер должен начать работу после нажатие на кнопку. Помогите вынести работу энкодер в прерывание.
    Код (C++):
    #include <EEPROM.h>
    #include <Wire.h>
    #include <LiquidCrystal_I2C.h>
    #include <PID_v1.h>  // https://playground.arduino.cc/Code/PIDLibrary
    #include <OneWire.h>  // DS18b20
    OneWire ds(11);
    LiquidCrystal_I2C lcd(0x3F , 16 , 2 );
    //////////////////////////////////////////
    #define ENCODER_A 2  // вход энкодера
    #define ENCODER_B 3
    #define ENCODER_KEY 8


    #define TEMP_MAX 60.0  // Приделы настройки термостата
    #define TEMP_MIN 25.0

    #define RELAY_PIN 12  // выход на реле

    #define WindowSize 500  // периуд, для симистора можно оставить, для
      // реле увеличить в ~10 раз
    //////////////////////////////////////////////

    bool encoderPinALast = LOW;
    bool n = LOW;
    unsigned long windowStartTime;

    double Setpoint, Input, Output;
    double Kp=2, Ki=5, Kd=1.5;  // коэффициенты настройки PID
    PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
    float temperature;
    void setup() {
      Serial.begin(9600);

      lcd.init();
      lcd.backlight();
      lcd.clear();

      pinMode(RELAY_PIN, OUTPUT);
      pinMode (ENCODER_A, INPUT);
      pinMode (ENCODER_B, INPUT);
      pinMode (ENCODER_KEY, INPUT);

      myPID.SetOutputLimits(0, WindowSize);
      myPID.SetMode(AUTOMATIC);

      //// проверка ЕЕПРОМ для первого включения
      if (EEPROM.read(0) == 255 && EEPROM.read(4) == 255){
      EEPROM.write(0, 10);
      EEPROM.write(1, 0);
      EEPROM.write(2, 240);
      EEPROM.write(3, 65);
      }
      EEPROM.get(0, Setpoint);

    }

    void loop() {
      // Определяем температуру от датчика DS18b20
      byte data[2]; // Место для значения температуры

      ds.reset(); // Начинаем взаимодействие со сброса всех предыдущих команд и параметров
      ds.write(0xCC); // Даем датчику DS18b20 команду пропустить поиск по адресу. В нашем случае только одно устрйоство
      ds.write(0x44); // Даем датчику DS18b20 команду измерить температуру. Само значение температуры мы еще не получаем - датчик его положит во внутреннюю память

      delay(50); // Микросхема измеряет температуру, а мы ждем.

      ds.reset(); // Теперь готовимся получить значение измеренной температуры
      ds.write(0xCC);
      ds.write(0xBE); // Просим передать нам значение регистров со значением температуры

      // Получаем и считываем ответ
      data[0] = ds.read(); // Читаем младший байт значения температуры
      data[1] = ds.read(); // А теперь старший

      // Формируем итоговое значение:
      //  - сперва "склеиваем" значение,
      //  - затем умножаем его на коэффициент, соответсвующий разрешающей способности (для 12 бит по умолчанию - это 0,0625)
      float temperature =  ((data[1] << 8) | data[0]) * 0.0625;

      // Выводим полученное значение температуры в монитор порта
      //Serial.println(temperature);
      //temperature = analogRead(LM35_PIN) / 9.31;  // забераем температуру
      Input = temperature;

      myPID.Compute();


    ///////////////////////

      if(!digitalRead(ENCODER_KEY)){  // если надавили на кнопку, входим в настройки
      digitalWrite(RELAY_PIN, LOW);  // и выключаем нагреватель.
      lcd.clear();  // очищаем экран и выводим параметр
      lcd.setCursor(8, 0);
      lcd.print(Setpoint, 1);
      lcd.print("C  ");
      // управление энкодером
      n = digitalRead(ENCODER_A);
      if ((encoderPinALast == LOW) && (n == HIGH)) {
      if (digitalRead(ENCODER_B) == LOW) {
      Setpoint -= .1;
      if (Setpoint < TEMP_MIN) Setpoint = TEMP_MIN;  // проверяем приделы настроек
      } else {
      Setpoint += .1;
      if (Setpoint > TEMP_MAX) Setpoint = TEMP_MAX;
      }
      EEPROM.put(0, Setpoint);  // пишим в еепром настройку
      }
      encoderPinALast = n;
      delay(5);
      return;
      }

    //
      if (millis() - windowStartTime >= WindowSize) {  // таймер на миллис
      windowStartTime += WindowSize;
      if (windowStartTime > millis()) windowStartTime = 0;  // защита от переполнения

      lcd.setCursor(0, 0);
      lcd.print(temperature);  // вывод на экран перенес в таймер
      lcd.print("C  ");  //  чтение с LM35 происходит быстрее.
      lcd.setCursor(8, 0);  //  отсутствует необходимая LCD
      lcd.print(Setpoint, 1);  //  экрану зарежка
      lcd.print("C  ");
      lcd.setCursor(0, 1);
      lcd.print(map(Output, 0, WindowSize, 0, 100));
      lcd.print("%  ");
      lcd.setCursor(8, 1);
      lcd.print(Output);
      lcd.print("mS ");
      }
      if (Output < millis() - windowStartTime) digitalWrite(RELAY_PIN, LOW);
      else digitalWrite(RELAY_PIN, HIGH);

    }
     
  2. SergeiL

    SergeiL Оракул Модератор

    Так это не DS18B20 медленный, это Вы так с ним работаете.
    Уберите delay(); и все у Вас ускорится! :)
     
  3. vlad1995

    vlad1995 Нуб

    Вроде delay(); нужен чтобы данные успели прийти от датчика
     
  4. KindMan

    KindMan Гуру

    А зачем считывать температуру каждую секунду? Чтобы разогревать датчик?
     
  5. SergeiL

    SergeiL Оракул Модератор

    И еще дополнение:
    1) За 50 миллисекунд датчик не измерит температуру. Нужно минимум 750 мс, если я не ошибаюсь.
    2) Зачем так часто запрашивать температуру? Это нужно делать минимум раз в несколько секунд. Иначе только греете датчик.
     
  6. vlad1995

    vlad1995 Нуб

    Я вас понимаю, но Я даже убрал delay(50); но все рано не работает энкодер. Температура продолжает измерятся. Получается датчик тормозит цикл пока не измеряет температуру?
     
  7. SergeiL

    SergeiL Оракул Модератор

    Посмотрите пример blink without delay, там используется millis() для обеспечения интервала времени.
    У Вас не успевает датчик получить новую температуру и выдает старую.
     
  8. vlad1995

    vlad1995 Нуб

    Получается нельзя скрестить 18b20 и энкодер?
     
  9. SergeiL

    SergeiL Оракул Модератор

    @parovoZZ , не гони про свою абдурину, все будет работать, если правильно написать.
     
  10. vlad1995

    vlad1995 Нуб

    На сколько я понял надо опрашивать датчик. Спустя время. Между опросами датчика будет срабатывать энкодер.
     
  11. vlad1995

    vlad1995 Нуб

    Получается использовать кнопки или другой датчик.
     
  12. SergeiL

    SergeiL Оракул Модератор

    Хватит рекламировать свою абдурину! :)

    Будет! Не путайте человека! (есть много корректно работающих вариантов)
    Пускай пока разбирается с блинк без делэй.
     
  13. vlad1995

    vlad1995 Нуб

    Подскажите где ошибка в millis()
    Код (C++):
    #include <EEPROM.h>
    #include <Wire.h>
    #include <LiquidCrystal_I2C.h>
    #include <PID_v1.h>  // https://playground.arduino.cc/Code/PIDLibrary
    #include <OneWire.h>  // DS18b20
    OneWire ds(11);
    LiquidCrystal_I2C lcd(0x3F , 16 , 2 );
    //////////////////////////////////////////
    #define ENCODER_A 2  // вход энкодера
    #define ENCODER_B 3
    #define ENCODER_KEY 8


    #define TEMP_MAX 60.0  // Приделы настройки термостата
    #define TEMP_MIN 25.0

    #define RELAY_PIN 12  // выход на реле

    #define WindowSize 500  // периуд, для симистора можно оставить, для
      // реле увеличить в ~10 раз
    //////////////////////////////////////////////

    bool encoderPinALast = LOW;
    bool n = LOW;
    unsigned long windowStartTime;
    long previousMillis = 0;        // в переменной previousMillis будет информация о времени, прошедшем с тех пор, как была измерена температура;
    long interval = 100;           // интервал, после которого быдет измерена температура (измеряется в миллисекундах)
    double Setpoint, Input, Output;
    double Kp=2, Ki=5, Kd=1.5;  // коэффициенты настройки PID
    PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
    float temperature;
    void setup() {
      Serial.begin(9600);

      lcd.init();
      lcd.backlight();
      lcd.clear();

      pinMode(RELAY_PIN, OUTPUT);
      pinMode (ENCODER_A, INPUT);
      pinMode (ENCODER_B, INPUT);
      pinMode (ENCODER_KEY, INPUT);

      myPID.SetOutputLimits(0, WindowSize);
      myPID.SetMode(AUTOMATIC);

      //// проверка ЕЕПРОМ для первого включения
      if (EEPROM.read(0) == 255 && EEPROM.read(4) == 255){
      EEPROM.write(0, 10);
      EEPROM.write(1, 0);
      EEPROM.write(2, 240);
      EEPROM.write(3, 65);
      }
      EEPROM.get(0, Setpoint);

    }

    void loop() {
        unsigned long currentMillis = millis();
        if(currentMillis - previousMillis > interval) {
       
     
     
      // Определяем температуру от датчика DS18b20
      byte data[2]; // Место для значения температуры

      ds.reset(); // Начинаем взаимодействие со сброса всех предыдущих команд и параметров
      ds.write(0xCC); // Даем датчику DS18b20 команду пропустить поиск по адресу. В нашем случае только одно устрйоство
      ds.write(0x44); // Даем датчику DS18b20 команду измерить температуру. Само значение температуры мы еще не получаем - датчик его положит во внутреннюю память

      //delay(50); // Микросхема измеряет температуру, а мы ждем.

      ds.reset(); // Теперь готовимся получить значение измеренной температуры
      ds.write(0xCC);
      ds.write(0xBE); // Просим передать нам значение регистров со значением температуры

      // Получаем и считываем ответ
      data[0] = ds.read(); // Читаем младший байт значения температуры
      data[1] = ds.read(); // А теперь старший

      // Формируем итоговое значение:
      //  - сперва "склеиваем" значение,
      //  - затем умножаем его на коэффициент, соответсвующий разрешающей способности (для 12 бит по умолчанию - это 0,0625)
      float temperature =  ((data[1] << 8) | data[0]) * 0.0625;

      // Выводим полученное значение температуры в монитор порта
      //Serial.println(temperature);
      //temperature = analogRead(LM35_PIN) / 9.31;  // забераем температуру
      Input = temperature;
    }
      myPID.Compute();


    ///////////////////////

      if(!digitalRead(ENCODER_KEY)){  // если надавили на кнопку, входим в настройки
      digitalWrite(RELAY_PIN, LOW);  // и выключаем нагреватель.
      lcd.clear();  // очищаем экран и выводим параметр
      lcd.setCursor(8, 0);
      lcd.print(Setpoint, 1);
      lcd.print("C  ");
      // управление энкодером
      n = digitalRead(ENCODER_A);
      if ((encoderPinALast == LOW) && (n == HIGH)) {
      if (digitalRead(ENCODER_B) == LOW) {
      Setpoint -= .1;
      if (Setpoint < TEMP_MIN) Setpoint = TEMP_MIN;  // проверяем приделы настроек
      } else {
      Setpoint += .1;
      if (Setpoint > TEMP_MAX) Setpoint = TEMP_MAX;
      }
      EEPROM.put(0, Setpoint);  // пишим в еепром настройку
      }
      encoderPinALast = n;
      delay(5);
      return;
      }

    //
      if (millis() - windowStartTime >= WindowSize) {  // таймер на миллис
      windowStartTime += WindowSize;
      if (windowStartTime > millis()) windowStartTime = 0;  // защита от переполнения

      lcd.setCursor(0, 0);
      lcd.print(temperature);  // вывод на экран перенес в таймер
      lcd.print("C  ");  //  чтение с LM35 происходит быстрее.
      lcd.setCursor(8, 0);  //  отсутствует необходимая LCD
      lcd.print(Setpoint, 1);  //  экрану зарежка
      lcd.print("C  ");
      lcd.setCursor(0, 1);
      lcd.print(map(Output, 0, WindowSize, 0, 100));
      lcd.print("%  ");
      lcd.setCursor(8, 1);
      lcd.print(Output);
      lcd.print("mS ");
      }
      if (Output < millis() - windowStartTime) digitalWrite(RELAY_PIN, LOW);
      else digitalWrite(RELAY_PIN, HIGH);

    }
     
  14. KindMan

    KindMan Гуру

    Я вот не могу понять последние две строчки в коде… объясните, пожалуйста, взаимосвязь.
     
  15. ИгорьК

    ИгорьК Гуру

    Здесь был я
     
  16. ИгорьК

    ИгорьК Гуру

    Я не просто тру. Что-то летит в "Столбик для почесать", что-то получает приз в виде предупреждения.
    Они набираются, набираются, и, как говорил тов. Гегель, количество обязательно перерастёт в качество.
    А тов. Сталин утверждал, что "количество уже само по себе есть качество".
     
  17. SergeiL

    SergeiL Оракул Модератор

    Не, тут нужно не так.
    Через millis() определять время старта измерения температуры датчика. Тут все как блинк без делэй.
    Дальше, когда запустили измерение - взводить флаг и запускать "новый таймер" на чтение результата.
     
    Последнее редактирование: 30 май 2019
  18. znamen

    znamen Guest

    В первом посте данной темы автор пишет,что у ds1820 слишком большой цыкл измерения температуры.
    И процессор не успевает обрабатывать информацию с энкодера. И решение проблемы видит в использовании
    прерывания. И весь сыр-бор в этой теме разгорелся из за этого. Можно,конечно изловчится и сделать. И зачем все эти извращения с делей и без делей. И зачем прерывание?
    Перед входом в цыкл выдаем команду Начать измерение.
    В основном программном цыкле
    Опрашиваем энкодер и пр. Опрашиваем шину Wire. Если=1 (измерение
    закончилось) передаем на индикацию и выдаем очередную команду на измерение. В противном случае
    в начало цыкла. При этом не важно сколько времени длится измерение. И нет лишних телодвижений