Прерывания по таймеру и переменные.

meridian86
Offline
Зарегистрирован: 09.11.2019

Доброго времени суток, форумчане!

Если честно, то не понимаю, как правильно задать вопрос... Проблема следующая: Есть проект метеостанции. В качестве задающей временные интервалы функции(период измерений, обновления ЛСД и т.д.), использован таймер прерываний. Столкнулся с такой проблемой:

В алгоритме по факту нажатия кнопки: 1. выставляется два флага - флаг смены режима(flagUpdateMode), флаг таймера подсветки дисплея(flagTimerBacklight) 2. выставляется величина задержки подсветки дисплея (timerBacklight). Строки алгоритма 84 - 99.

Если в цикле loop() я вывожу хотя бы одну переменную(flagUpdateMode,flagTimerBacklight или mode) в серийный порт, то скетч функционирует исправно, если убираю строки вывода в порт, флажки перестают устанавливаться... Я вообще не понимаю, что происходит. Куда смотреть? Что читать и как бороться?

#include "DHT.h"                //Подключаем библиотеку для датчиков типа DHT11/DHT22     
#include "MsTimer2.h"           //Подключаем библиотеку для аппаратных прерываний
#include "Button.h"             //Подключаем библиотеку для обработки кнопки
#include <LiquidCrystal_I2C.h>  //Подключение библиотеки для LCD
#include <Adafruit_BMP085.h>    //Подключаем библиотеку для датчика BMP-180

//------Объявляем имена и значения констант------//
#define DHT_INDOOR_PIN A0               //Пин датчика температуры и влажности в помещении
#define DHT_OUTDOOR_PIN A1              //Пин датчика температуры и влажности уличного
#define BUTTON_1_PIN A2                 //Пин 1 кнопки
#define CYCLE_MEAS 2500                 //Время цикла измерений температуры и влажности * период прерывания по таймеру
#define CYCLE_BACKLIGHT 7500            //Время работы подсветки lcd

//------Создаем объекты------//
Button button1(BUTTON_1_PIN, 15);           //Создали кнопку с периодом антидребезга 15х2мс
DHT indoorSensor(DHT_INDOOR_PIN, DHT11);    //Создали датчик температуры и влажности в помещении
DHT outdoorSensor(DHT_OUTDOOR_PIN, DHT22);  //Создали датчик температуры и влажности уличный
LiquidCrystal_I2C lcd(0x27, 16, 2);         //Создаем дисплей. Указываем I2C адрес (наиболее распространенное значение), а также параметры экрана (в случае LCD 1602 - 2 строки по 16 символов в каждой
Adafruit_BMP085 bmp;                        //Создаем датчик атмосферного давления

//------Объявляем переменные------//
//переменные для работы с данными с датчиков
byte hIndoor;                                   //влажность в помещении
int tIndoor;                                    //температура в помещении
byte hOutdoor;                                  //влажность уличная
int tOutdoor;                                   //температура уличная
float pressure;                                 //атмосферное давление
//переменные для работы с таймерами
unsigned int timerMeas;                         //таймер измерений температуры и влажности
boolean flagTimerMeas = false;                  //флаг таймера измерений температуры и влажности
unsigned int timerBacklight = CYCLE_BACKLIGHT ; //таймер lcd подсветки
boolean flagTimerBacklight = true;              //флаг таймера lcd подсветки
//переменные основного цикла
byte mode;                                      //перменная определяющая режим работы
boolean flagUpdateMode;                         //флаг смены режима
boolean flagUpdateLCD;                          //флаг обновления дисплея

//------Создаем нужные символы для дисплея------//
byte deg[8]       = {B00110, B01001, B01001, B00110, B00000, B00000, B00000, B00000,}; // градус
byte up[8]        = {B00000, B00100, B01110, B10101, B00100, B00100, B00100, B00000,}; // вверх
byte down[8]      = {B00000, B00100, B00100, B00100, B10101, B01110, B00100, B00000,}; // вниз
byte outdoor[8]   = {B00011, B00001, B00000, B00100, B01010, B11111, B01010, B01110,}; // снаружи
byte indoor[8]    = {B00010, B00011, B00000, B00100, B01010, B11111, B01010, B01110,}; // внутри
byte hum[8]       = {B00100, B01010, B01010, B10001, B10001, B01110, B00000, B00000,}; // капля
byte term[8]      = {B01100, B01110, B01100, B01110, B01100, B10010, B10010, B01100,}; // термометр
byte press[8]     = {B00100, B00100, B01000, B01000, B00100, B10101, B01110, B00100,}; // давление
byte mr[8]        = {B10001, B11011, B10101, B10001, B01100, B01010, B01100, B01001,}; // мр
byte ms[8]        = {B10001, B11011, B10101, B10001, B01100, B10000, B10000, B01101,}; // мc
//byte bukva_B[8]   = {B11110, B10000, B10000, B11110, B10001, B10001, B11110, B00000,}; // Буква "Б"
byte bukva_G[8]   = {B11111, B10001, B10000, B10000, B10000, B10000, B10000, B00000,}; // Буква "Г"
byte bukva_D[8]   = {B01111, B00101, B00101, B01001, B10001, B11111, B10001, B00000,}; // Буква "Д"
//byte bukva_ZH[8]  = {B10101, B10101, B10101, B11111, B10101, B10101, B10101, B00000,}; // Буква "Ж"
//byte bukva_Z[8]   = {B01110, B10001, B00001, B00010, B00001, B10001, B01110, B00000,}; // Буква "З"
byte bukva_I[8]   = {B10001, B10011, B10011, B10101, B11001, B11001, B10001, B00000,}; // Буква "И"
//byte bukva_IY[8]  = {B01110, B00000, B10001, B10011, B10101, B11001, B10001, B00000,}; // Буква "Й"
//byte bukva_L[8]   = {B00011, B00111, B00101, B00101, B01101, B01001, B11001, B00000,}; // Буква "Л"
byte bukva_P[8]   = {B11111, B10001, B10001, B10001, B10001, B10001, B10001, B00000,}; // Буква "П"
//byte bukva_Y[8]   = {B10001, B10001, B10001, B01010, B00100, B01000, B10000, B00000,}; // Буква "У"
//byte bukva_F[8]   = {B00100, B11111, B10101, B10101, B11111, B00100, B00100, B00000,}; // Буква "Ф"
byte bukva_TS[8]  = {B10010, B10010, B10010, B10010, B10010, B10010, B11111, B00001,}; // Буква "Ц"
//byte bukva_CH[8]  = {B10001, B10001, B10001, B01111, B00001, B00001, B00001, B00000,}; // Буква "Ч"
//byte bukva_Sh[8]  = {B10101, B10101, B10101, B10101, B10101, B10101, B11111, B00000,}; // Буква "Ш"
//byte bukva_Shch[8] = {B10101, B10101, B10101, B10101, B10101, B10101, B11111, B00001,}; // Буква "Щ"
//byte bukva_Mz[8]  = {B10000, B10000, B10000, B11110, B10001, B10001, B11110, B00000,}; // Буква "Ь"
//byte bukva_IYI[8] = {B10001, B10001, B10001, B11001, B10101, B10101, B11001, B00000,}; // Буква "Ы"
//byte bukva_Yu[8]  = {B10010, B10101, B10101, B11101, B10101, B10101, B10010, B00000,}; // Буква "Ю"
byte bukva_Ya[8]  = {B01111, B10001, B10001, B01111, B00101, B01001, B10001, B00000,}; // Буква "Я"

//------Установки и прочее------//
void setup() {
  MsTimer2::set(2, timerInterupt);   //задаем период прерывания по таймеру 2 мс
  MsTimer2::start();                //запускаем таймер
  indoorSensor.begin();             //запускаем датчик внутренний
  outdoorSensor.begin();            //запускаем датчик уличный
  Serial.begin(9600);               //запускаем серийный порт
  lcd.begin();                      //запускаем дисплей
  bmp.begin();                      //запускаем датчик давления
  onOffBacklight();                 //включаем подсветку дисплея
  hello();                          //выводим приветствие
}

//------Основной цикл------//
void loop() {
  if (button1.flagClick == true) {    //был "клик" кнопкой
    timerBacklight = CYCLE_BACKLIGHT; //установка таймера подсветки lcd
    flagTimerBacklight = true;        //установить флаг включения подсветки lcd
//    Serial.print("flagTimerBacklight = ");
//    Serial.println(flagTimerBacklight);
    flagUpdateMode = true;            //установить флаг смены режима
//    Serial.print("flagUpdateMode = ");
//    Serial.println(flagUpdateMode);
    mode++;                           //смена режима
    if (mode > 1) {
      mode = 0;
    }
//    Serial.print("mode = ");
//    Serial.println(mode);
    button1.flagClick = false;        //сброс флага "клик" кнопкой
  }



  if (flagTimerMeas == true) measureTHP(); //измеряем параметры окр. среды

  switch (mode) {
    //режим по умолчанию(показания уличные)
    case 0:
      if (flagUpdateMode == true) {     //если была смена режима
        lcd.createChar(1, outdoor);     //создаем значек снаружи
        clearLCD();
        updateLcdOutdoorTHP();          //обновляем показания дисплея
        flagUpdateMode = false;         //сбросываем флаг смены режима
      }
      if (flagUpdateLCD == true) {      //если получили новые данные
        updateLcdOutdoorTHP();          //обновляем показания дисплея
      }
      break;
    //режим показаний в помещении
    case 1:
      if (flagUpdateMode == true) {    //если была смена режима
        lcd.createChar(1, indoor);     //создаем значек внутри
        clearLCD();
        updateLcdIndoorTH();           //обновляем показания дисплея
        flagUpdateMode = false;        //сбросываем флаг смены режима
      }
      if (flagUpdateLCD == true) {     //если получили новые данные
        updateLcdIndoorTH();           //обновляем показания дисплея
      }
      break;
  }

    onOffBacklight();               //управление подсветкой
}

//------Обработчик прерываний------//
void timerInterupt() {
  button1.scanState();            //проверяем нажатие кнопки

  //Таймер измерений и обновления lcd
  timerMeas++;                    //инкремент таймера измерений
  if (timerMeas >= CYCLE_MEAS) {  //проверка таймера измерений
    timerMeas = 0;                //сброс таймера измерений
    flagTimerMeas = true;         //выставление флага таймера измерений
    flagUpdateLCD = true;        //выставление флага обновить показания на дисплее
    //    Serial.print("flagTimerMeas = ");
    //    Serial.println(flagTimerMeas);
  }

  //Таймер подсветки lcd
  if (timerBacklight > 0) {       //проверка таймера подсветки
    timerBacklight--;             //декремент таймера подсветки
    if (timerBacklight == 0) {
      flagTimerBacklight = false; //выставление флага таймера подсветки
    }
  }
}

//------Измерение параметров окр.среды------//
void measureTHP() {
  float temp;
  tIndoor = indoorSensor.readTemperature() * 100.0;     //измеряем температуру в помещении
  hIndoor = indoorSensor.readHumidity();                //измеряем влажность в помещении
  tOutdoor = outdoorSensor.readTemperature() * 100.0;   //измеряем температуру уличную
  hOutdoor = outdoorSensor.readHumidity();              //измеряем влажность уличную
  temp = bmp.readTemperature();                         //необходимо по условиям расчета давления(см. datasheet)
  pressure = bmp.readPressure() * 0.0075;               //измеряем давление и переводим в мм.рт.ст.
  temp = bmp.readAltitude();
  temp = bmp.readSealevelPressure();
  temp = bmp.readAltitude(101500);
  flagTimerMeas = false;                                //сбрасываем флаг таймера измерений
  //  Serial.println("measureTHP() STOP");
}

//------Управление подсветкой lcd------//
void onOffBacklight() {
  if ( flagTimerBacklight == true) {
    lcd.backlight();
  }
  else {
    lcd.noBacklight();
  }
}

//------Вывод текущих уличных значений на дисплей------//
void updateLcdOutdoorTHP() {
  char _tOutdoorStr[4];
  char _hOutdoorStr[4];
  char _pressureStr[4];
  float _tOutdoor = tOutdoor / 100.0;
  dtostrf(_tOutdoor, 4, 1, _tOutdoorStr);
  lcd.setCursor(0, 0);
  lcd.print("\1 ");
  lcd.print("\6 ");
  lcd.print(_tOutdoorStr);
  lcd.setCursor(9, 0);
  lcd.print("\3C");
  lcd.setCursor(12, 0);
  lcd.print("\5");
  dtostrf(hOutdoor, 2, 0, _hOutdoorStr);
  lcd.print(_hOutdoorStr);
  lcd.print("%");
  dtostrf(pressure, 4, 1, _pressureStr);
  lcd.setCursor(2, 1);
  lcd.print("\4\4 ");
  lcd.print(_pressureStr);
  lcd.print(" \2\7");
  flagUpdateLCD = false;
  //  Serial.println("updateLcdOutdoorTHP()");
}

//------Вывод текущих значений в помещении на дисплей------//
void updateLcdIndoorTH() {
  char _tIndoorStr[4];
  char _hIndoorStr[4];
  float _tIndoor = tIndoor / 100.0;
  dtostrf(_tIndoor, 4, 1, _tIndoorStr);
  lcd.setCursor(0, 0);
  lcd.print("\1 ");
  lcd.print("\6 ");
  lcd.print(_tIndoorStr);
  lcd.setCursor(9, 0);
  lcd.print("\3C");
  lcd.setCursor(12, 0);
  lcd.print("\5");
  dtostrf(hIndoor, 2, 0, _hIndoorStr);
  lcd.print(_hIndoorStr);
  lcd.print("%");
  lcd.setCursor(0, 1);
  flagUpdateLCD = false;
  //  Serial.println("updateLcdIndoorTH()");
}

//------Очистка дисплея------//
void clearLCD() {
  lcd.setCursor(0, 0);
  lcd.print("                ");
  lcd.setCursor(0, 1);
  lcd.print("                ");
}

//------Приветствие------//
void hello() {
  //  Serial.println("Погодная станция v.01.20");
  lcd.createChar(1, bukva_P);
  lcd.createChar(2, bukva_G);
  lcd.createChar(3, bukva_D);
  lcd.createChar(4, bukva_Ya);
  lcd.createChar(5, bukva_TS);
  lcd.createChar(6, bukva_I);
  lcd.setCursor(4, 0);             // Установка курсора в 5ю позицию первой строки
  lcd.print("\1O\2O\3HA\4");       // Набор текста на первой строке
  lcd.setCursor(0, 1);             // Установка курсора в начало второй строки
  lcd.print("CTAH\5\6\4 v.01.20"); // Набор текста на второй строке
  delay(2000);
  clearLCD();
  lcd.createChar(1, outdoor);     // Создаем значек снаружи
  lcd.createChar(2, mr);          // Создаем значек мр
  lcd.createChar(3, deg);         // Создаем значек градуса
  lcd.createChar(4, press);       // Создаем значек давления
  lcd.createChar(5, hum);         // Создаем значек влажность
  lcd.createChar(6, term);        // Создаем значек термометр
  lcd.createChar(7, ms);          // Создаем значек мс
}

 

sadman41
Offline
Зарегистрирован: 19.10.2016

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

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

1. Работать с Serial в прерывании по таймеру - не айс;

2. Почитайте про модификатор volatile;

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

4. Если не понимаете, как работать с прерываниями - для начала можно обойтись без них.

meridian86
Offline
Зарегистрирован: 09.11.2019

Хоть они и изменяются в основном цикле программы, я пробовал их объявить через volatile, не помогло. Хотя...

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

meridian86 пишет:

Хоть они и изменяются в основном цикле программы, я пробовал их объявить через volatile, не помогло. Хотя...

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

meridian86
Offline
Зарегистрирован: 09.11.2019

Спасибо! Будем вникать.

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

Таймеры, много:   https://github.com/DetSimen/Arduino_TimerList

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

DetSimen пишет:

Таймеры, много:   https://github.com/DetSimen/Arduino_TimerList

Отдаёшь в "хорошие руки"? ;)))) Привиты? К лотку приучены?

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

wdrakula пишет:

Отдаёшь в "хорошие руки"? ;)))) Привиты? К лотку приучены?

Да зря, наерна, самому такие нужны. Не отдам. :) 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

meridian86 пишет:

Хоть они и изменяются в основном цикле программы, я пробовал их объявить через volatile, не помогло. 

А зачем Вы их "взад" разобъявили?

Вы мне напомнили одного деятеля, заходил сюда как-то, но решил, что мы ему неправильно помогаем, обозвал всех мудаками и исчез. Так вот, у него датчик был присоединён к третьему пину, а читал он его с пятого. Ему сказали об этом, а он ответил: "поменял в скетче пятый на третий - всё равно не заработало, вернул всё как было". Я помню, долго не мог дыхание восстановить после такого ответа.

meridian86
Offline
Зарегистрирован: 09.11.2019

Не стоит неудачный опыт переносить на всех нуждающихся в помощи.

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

//------Обработчик прерываний------//
void timerInterupt() {
  button1.scanState();            //проверяем нажатие кнопки
  timerMeas++;                    //инкремент таймера измерений
}

А так же сборки всего алгоритма с начала. Все было замечательно пока в алгоритме не было конструкции switch-case. Вернее сказать, до того момента, пока в одном case не использовалось два оператора if :

  //!!!!!!!Режим работы!!!!!!!
  switch (mode) {
    //режим по умолчанию(показания уличные)
    case 0:
      if (flagUpdateMode == true) { //обновить дисплей по факту смены режима
        flagUpdateMode = false;
        clearLCD();
        updateLcdOutdoorTHP();
      }
//      if (flagUpdateLCD == true) { //обновить дисплей по факту получения новых измерений
//        flagUpdateLCD = false;
//        updateLcdOutdoorTHP();
//      }
      break;
    case 1:
      if (flagUpdateMode == true) { //обновить дисплей по факту смены режима
        flagUpdateMode = false;
        clearLCD();
        updateLcdIndoorTH();
      }
//      if (flagUpdateLCD == true) {//обновить дисплей по факту получения новых измерений
//        flagUpdateLCD = false;
//        updateLcdIndoorTH();
//      }
      break;
  }

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

Понимаю что вникать в мою портянку вряд ли кто-то станет, но все же:


#include "DHT.h"                //Подключаем библиотеку для датчиков типа DHT11/DHT22     
#include "MsTimer2.h"           //Подключаем библиотеку для аппаратных прерываний
#include "Button.h"             //Подключаем библиотеку для обработки кнопки
#include <LiquidCrystal_I2C.h>  //Подключение библиотеки для LCD
#include <Adafruit_BMP085.h>    //Подключаем библиотеку для датчика BMP-180

//------Объявляем имена и значения констант------//
#define DHT_INDOOR_PIN A0               //Пин датчика температуры и влажности в помещении
#define DHT_OUTDOOR_PIN A1              //Пин датчика температуры и влажности уличного
#define BUTTON_1_PIN A2                 //Пин 1 кнопки
#define CYCLE_MEAS 2500                 //Время цикла измерений температуры и влажности * период прерывания по таймеру
#define CYCLE_BACKLIGHT 7500            //Время работы подсветки lcd

//------Создаем объекты------//
Button button1(BUTTON_1_PIN, 15);           //Создали кнопку с периодом антидребезга 15х2мс
DHT indoorSensor(DHT_INDOOR_PIN, DHT11);    //Создали датчик температуры и влажности в помещении
DHT outdoorSensor(DHT_OUTDOOR_PIN, DHT22);  //Создали датчик температуры и влажности уличный
LiquidCrystal_I2C lcd(0x27, 16, 2);         //Создаем дисплей. Указываем I2C адрес (наиболее распространенное значение), а также параметры экрана (в случае LCD 1602 - 2 строки по 16 символов в каждой
Adafruit_BMP085 bmp;                        //Создаем датчик атмосферного давления

//------Объявляем переменные------//
//переменные для работы с данными с датчиков
byte hIndoor;                                   //влажность в помещении
int tIndoor;                                    //температура в помещении
byte hOutdoor;                                  //влажность уличная
int tOutdoor;                                   //температура уличная
float pressure;                                 //атмосферное давление
//переменные для работы с таймерами
volatile unsigned int timerMeas;                //таймер измерений температуры и влажности
boolean flagTimerMeas = false;                  //флаг таймера измерений температуры и влажности
volatile unsigned int timerBacklight;           //таймер lcd подсветки
boolean flagTimerBacklight = true;              //флаг таймера lcd подсветки
//переменные основного цикла
byte mode;                                      //перменная определяющая режим работы
boolean flagUpdateMode;                         //флаг смены режима
boolean flagUpdateLCD;                          //флаг обновления дисплея

//------Создаем нужные символы для дисплея------//
byte deg[8]       = {B00110, B01001, B01001, B00110, B00000, B00000, B00000, B00000,}; // градус
byte up[8]        = {B00000, B00100, B01110, B10101, B00100, B00100, B00100, B00000,}; // вверх
byte down[8]      = {B00000, B00100, B00100, B00100, B10101, B01110, B00100, B00000,}; // вниз
byte outdoor[8]   = {B00011, B00001, B00000, B00100, B01010, B11111, B01010, B01110,}; // снаружи
byte indoor[8]    = {B00010, B00011, B00000, B00100, B01010, B11111, B01010, B01110,}; // внутри
byte hum[8]       = {B00100, B01010, B01010, B10001, B10001, B01110, B00000, B00000,}; // капля
byte term[8]      = {B01100, B01110, B01100, B01110, B01100, B10010, B10010, B01100,}; // термометр
byte press[8]     = {B00100, B00100, B01000, B01000, B00100, B10101, B01110, B00100,}; // давление
byte mr[8]        = {B10001, B11011, B10101, B10001, B01100, B01010, B01100, B01001,}; // мр
byte ms[8]        = {B10001, B11011, B10101, B10001, B01100, B10000, B10000, B01101,}; // мc
//byte bukva_B[8]   = {B11110, B10000, B10000, B11110, B10001, B10001, B11110, B00000,}; // Буква "Б"
byte bukva_G[8]   = {B11111, B10001, B10000, B10000, B10000, B10000, B10000, B00000,}; // Буква "Г"
byte bukva_D[8]   = {B01111, B00101, B00101, B01001, B10001, B11111, B10001, B00000,}; // Буква "Д"
//byte bukva_ZH[8]  = {B10101, B10101, B10101, B11111, B10101, B10101, B10101, B00000,}; // Буква "Ж"
//byte bukva_Z[8]   = {B01110, B10001, B00001, B00010, B00001, B10001, B01110, B00000,}; // Буква "З"
byte bukva_I[8]   = {B10001, B10011, B10011, B10101, B11001, B11001, B10001, B00000,}; // Буква "И"
//byte bukva_IY[8]  = {B01110, B00000, B10001, B10011, B10101, B11001, B10001, B00000,}; // Буква "Й"
//byte bukva_L[8]   = {B00011, B00111, B00101, B00101, B01101, B01001, B11001, B00000,}; // Буква "Л"
byte bukva_P[8]   = {B11111, B10001, B10001, B10001, B10001, B10001, B10001, B00000,}; // Буква "П"
//byte bukva_Y[8]   = {B10001, B10001, B10001, B01010, B00100, B01000, B10000, B00000,}; // Буква "У"
//byte bukva_F[8]   = {B00100, B11111, B10101, B10101, B11111, B00100, B00100, B00000,}; // Буква "Ф"
byte bukva_TS[8]  = {B10010, B10010, B10010, B10010, B10010, B10010, B11111, B00001,}; // Буква "Ц"
//byte bukva_CH[8]  = {B10001, B10001, B10001, B01111, B00001, B00001, B00001, B00000,}; // Буква "Ч"
//byte bukva_Sh[8]  = {B10101, B10101, B10101, B10101, B10101, B10101, B11111, B00000,}; // Буква "Ш"
//byte bukva_Shch[8] = {B10101, B10101, B10101, B10101, B10101, B10101, B11111, B00001,}; // Буква "Щ"
//byte bukva_Mz[8]  = {B10000, B10000, B10000, B11110, B10001, B10001, B11110, B00000,}; // Буква "Ь"
//byte bukva_IYI[8] = {B10001, B10001, B10001, B11001, B10101, B10101, B11001, B00000,}; // Буква "Ы"
//byte bukva_Yu[8]  = {B10010, B10101, B10101, B11101, B10101, B10101, B10010, B00000,}; // Буква "Ю"
byte bukva_Ya[8]  = {B01111, B10001, B10001, B01111, B00101, B01001, B10001, B00000,}; // Буква "Я"

//------Установки и прочее------//
void setup() {
  MsTimer2::set(2, timerInterupt);   //задаем период прерывания по таймеру 2 мс
  MsTimer2::start();                //запускаем таймер
  indoorSensor.begin();             //запускаем датчик внутренний
  outdoorSensor.begin();            //запускаем датчик уличный
  Serial.begin(9600);               //запускаем серийный порт
  lcd.begin();                      //запускаем дисплей
  bmp.begin();                      //запускаем датчик давления
  lcd.backlight();
  hello();                          //выводим приветствие
}

//------Основной цикл------//
void loop() {

  //!!!!!!!Обработка счетчиков!!!!!!!
  if (timerMeas >= CYCLE_MEAS) {  //проверка таймера измерений
    timerMeas = 0;                //сброс таймера измерений
    measureTHP();
  }

  //!!!!!!!Обработка кнопки!!!!!!!
  if (button1.flagClick == true) {    //был "клик" кнопкой
    button1.flagClick = false;        //сброс флага "клик" кнопкой
    flagUpdateMode = true;            //установить флаг смены режима
    mode++;                           //смена режима
    if (mode > 1) {
      mode = 0;
    }
  }

  //!!!!!!!Режим работы!!!!!!!
  switch (mode) {
    //режим по умолчанию(показания уличные)
    case 0:
      if (flagUpdateMode == true) { //обновить дисплей по факту смены режима
        flagUpdateMode = false;
        clearLCD();
        updateLcdOutdoorTHP();
      }
//      if (flagUpdateLCD == true) { //обновить дисплей по факту получения новых измерений
//        flagUpdateLCD = false;
//        updateLcdOutdoorTHP();
//      }
      break;
    case 1:
      if (flagUpdateMode == true) { //обновить дисплей по факту смены режима
        flagUpdateMode = false;
        clearLCD();
        updateLcdIndoorTH();
      }
//      if (flagUpdateLCD == true) {//обновить дисплей по факту получения новых измерений
//        flagUpdateLCD = false;
//        updateLcdIndoorTH();
//      }
      break;
  }

}

//------Обработчик прерываний------//
void timerInterupt() {
  button1.scanState();            //проверяем нажатие кнопки
  timerMeas++;                    //инкремент таймера измерений
}

//------Измерение параметров окр.среды------//
void measureTHP() {
  float temp;
  tIndoor = indoorSensor.readTemperature() * 100.0;     //измеряем температуру в помещении
  hIndoor = indoorSensor.readHumidity();                //измеряем влажность в помещении
  tOutdoor = outdoorSensor.readTemperature() * 100.0;   //измеряем температуру уличную
  hOutdoor = outdoorSensor.readHumidity();              //измеряем влажность уличную
  temp = bmp.readTemperature();                         //необходимо по условиям расчета давления(см. datasheet)
  pressure = bmp.readPressure() * 0.0075;               //измеряем давление и переводим в мм.рт.ст.
  temp = bmp.readAltitude();
  temp = bmp.readSealevelPressure();
  temp = bmp.readAltitude(101500);
  flagUpdateLCD = true;
  //  Serial.print("flagUpdateLCD = ");
  //  Serial.println(flagUpdateLCD);
  //  Serial.println("measureTHP() STOP");
}

//------Вывод текущих уличных значений на дисплей------//
void updateLcdOutdoorTHP() {
  char _tOutdoorStr[4];
  char _hOutdoorStr[4];
  char _pressureStr[4];
  float _tOutdoor = tOutdoor / 100.0;
  dtostrf(_tOutdoor, 4, 1, _tOutdoorStr);
  lcd.setCursor(0, 0);
  lcd.print("\1 ");
  lcd.print("\6 ");
  lcd.print(_tOutdoorStr);
  lcd.setCursor(9, 0);
  lcd.print("\3C");
  lcd.setCursor(12, 0);
  lcd.print("\5");
  dtostrf(hOutdoor, 2, 0, _hOutdoorStr);
  lcd.print(_hOutdoorStr);
  lcd.print("%");
  dtostrf(pressure, 4, 1, _pressureStr);
  lcd.setCursor(2, 1);
  lcd.print("\4\4 ");
  lcd.print(_pressureStr);
  lcd.print(" \2\7");
  flagUpdateLCD = false;
  //  Serial.println("updateLcdOutdoorTHP()");
}

//------Вывод текущих значений в помещении на дисплей------//
void updateLcdIndoorTH() {
  char _tIndoorStr[4];
  char _hIndoorStr[4];
  float _tIndoor = tIndoor / 100.0;
  dtostrf(_tIndoor, 4, 1, _tIndoorStr);
  lcd.setCursor(0, 0);
  lcd.print("\1 ");
  lcd.print("\6 ");
  lcd.print(_tIndoorStr);
  lcd.setCursor(9, 0);
  lcd.print("\3C");
  lcd.setCursor(12, 0);
  lcd.print("\5");
  dtostrf(hIndoor, 2, 0, _hIndoorStr);
  lcd.print(_hIndoorStr);
  lcd.print("%");
  lcd.setCursor(0, 1);
  flagUpdateLCD = false;
  //  Serial.println("updateLcdIndoorTH()");
}

//------Очистка дисплея------//
void clearLCD() {
  lcd.setCursor(0, 0);
  lcd.print("                ");
  lcd.setCursor(0, 1);
  lcd.print("                ");
}

//------Приветствие------//
void hello() {
  //  Serial.println("Погодная станция v.01.20");
  lcd.createChar(1, bukva_P);
  lcd.createChar(2, bukva_G);
  lcd.createChar(3, bukva_D);
  lcd.createChar(4, bukva_Ya);
  lcd.createChar(5, bukva_TS);
  lcd.createChar(6, bukva_I);
  lcd.setCursor(4, 0);             // Установка курсора в 5ю позицию первой строки
  lcd.print("\1O\2O\3HA\4");       // Набор текста на первой строке
  lcd.setCursor(0, 1);             // Установка курсора в начало второй строки
  lcd.print("CTAH\5\6\4 v.01.20"); // Набор текста на второй строке
  delay(2000);
  clearLCD();
  lcd.createChar(1, outdoor);     // Создаем значек снаружи
  lcd.createChar(2, mr);          // Создаем значек мр
  lcd.createChar(3, deg);         // Создаем значек градуса
  lcd.createChar(4, press);       // Создаем значек давления
  lcd.createChar(5, hum);         // Создаем значек влажность
  lcd.createChar(6, term);        // Создаем значек термометр
  lcd.createChar(7, ms);          // Создаем значек мс
}
meridian86
Offline
Зарегистрирован: 09.11.2019

DetSimen пишет:

Таймеры, много:   https://github.com/DetSimen/Arduino_TimerList

Спасибо!

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

метод решения проблемы "на коленке" - обновляйте экран за один цикл ЛУП только или по режиму, или по новым показаниям. У вас же режим, наверно. будет меняться редко - ну в этих случаях можно смирится с тем. что показания на жкране обновятся с задержкой.

meridian86
Offline
Зарегистрирован: 09.11.2019

b707 пишет:

метод решения проблемы "на коленке" - обновляйте экран за один цикл ЛУП только или по режиму, или по новым показаниям. У вас же режим, наверно. будет меняться редко - ну в этих случаях можно смирится с тем. что показания на жкране обновятся с задержкой.

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

sadman41
Offline
Зарегистрирован: 19.10.2016

А мне вот ясно где ошибка - слишком резкий старт. 

Я, в таких случаях, оставляю в лупе только вывод в сериал переменных, которые в обработчике дёргаются и начинаю с ними воевать. А потом уже мясо в виде LCD и прочей хренотени наращиваю.

meridian86
Offline
Зарегистрирован: 09.11.2019

sadman41 пишет:

А мне вот ясно где ошибка - слишком резкий старт. 

Я, в таких случаях, оставляю в лупе только вывод в сериал переменных, которые в обработчике дёргаются и начинаю с ними воевать. А потом уже мясо в виде LCD и прочей хренотени наращиваю.

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

meridian86
Offline
Зарегистрирован: 09.11.2019

В общем я вернулся к первоначальному своему посту...

Если в обработке факта нажатия кнопки в цикле ЛУП, выводить в Сериал значение flagUpdateMode:

//!!!!!!!Обработка кнопки!!!!!!!
  if (button1.flagClick == true) {    //был "клик" кнопкой
    button1.flagClick = false;        //сброс флага "клик" кнопкой
    flagUpdateMode = true;            //установить флаг смены режима
    mode++;                           //смена режима
    Serial.print("flagUpdateMode = ");
    Serial.println(flagUpdateMode);
    if (mode > 1) {
      mode = 0;
    }
  }

То программа полностью функционирует без замечаний, т.е. обновление дисплея происходит как по факту новых данных с датчиков, так и по факту смены режима работы... Стоит убрать вывод переменной flagUpdateMode в сериал, все "ломается". Это 3,14ЗДЕЦ какой-то... Как так-то?! Пойду посплю, ну на хрен!

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

А чё такого - нормальная практика. Для начала, поставьте вместо вывода задержку (delay), скажем миллисекунд на 100-200. Посмотрите помогло ли (и мне скажите). Если помогло, то понятно, что-то не успевает отработать, в Ваша печать как раз даёт нужное время.

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

Давайте, делайте.

meridian86
Offline
Зарегистрирован: 09.11.2019

Работает, зараза, с delay()... 

#include "DHT.h"                //Подключаем библиотеку для датчиков типа DHT11/DHT22     
#include "MsTimer2.h"           //Подключаем библиотеку для аппаратных прерываний
#include "Button.h"             //Подключаем библиотеку для обработки кнопки
#include <LiquidCrystal_I2C.h>  //Подключение библиотеки для LCD
#include <Adafruit_BMP085.h>    //Подключаем библиотеку для датчика BMP-180

//------Объявляем имена и значения констант------//
#define DHT_INDOOR_PIN A0               //Пин датчика температуры и влажности в помещении
#define DHT_OUTDOOR_PIN A1              //Пин датчика температуры и влажности уличного
#define BUTTON_1_PIN A2                 //Пин 1 кнопки
#define CYCLE_MEAS 2500                 //Время цикла измерений температуры и влажности * период прерывания по таймеру
#define CYCLE_BACKLIGHT 7500            //Время работы подсветки lcd

//------Создаем объекты------//
Button button1(BUTTON_1_PIN, 15);           //Создали кнопку с периодом антидребезга 15х2мс
DHT indoorSensor(DHT_INDOOR_PIN, DHT11);    //Создали датчик температуры и влажности в помещении
DHT outdoorSensor(DHT_OUTDOOR_PIN, DHT22);  //Создали датчик температуры и влажности уличный
LiquidCrystal_I2C lcd(0x27, 16, 2);         //Создаем дисплей. Указываем I2C адрес (наиболее распространенное значение), а также параметры экрана (в случае LCD 1602 - 2 строки по 16 символов в каждой
Adafruit_BMP085 bmp;                        //Создаем датчик атмосферного давления

//------Объявляем переменные------//
//переменные для работы с данными с датчиков
byte hIndoor;                                   //влажность в помещении
int tIndoor;                                    //температура в помещении
byte hOutdoor;                                  //влажность уличная
int tOutdoor;                                   //температура уличная
float pressure;                                 //атмосферное давление
//переменные для работы с таймерами
volatile unsigned int timerMeas;                //таймер измерений температуры и влажности
boolean flagTimerMeas = false;                  //флаг таймера измерений температуры и влажности
volatile unsigned int timerBacklight;           //таймер lcd подсветки
boolean flagTimerBacklight = true;              //флаг таймера lcd подсветки
//переменные основного цикла
byte mode;                                      //перменная определяющая режим работы
boolean flagUpdateMode;                         //флаг смены режима
boolean flagUpdateLCD;                          //флаг обновления дисплея

//------Создаем нужные символы для дисплея------//
byte deg[8]       = {B00110, B01001, B01001, B00110, B00000, B00000, B00000, B00000,}; // градус
byte up[8]        = {B00000, B00100, B01110, B10101, B00100, B00100, B00100, B00000,}; // вверх
byte down[8]      = {B00000, B00100, B00100, B00100, B10101, B01110, B00100, B00000,}; // вниз
byte outdoor[8]   = {B00011, B00001, B00000, B00100, B01010, B11111, B01010, B01110,}; // снаружи
byte indoor[8]    = {B00010, B00011, B00000, B00100, B01010, B11111, B01010, B01110,}; // внутри
byte hum[8]       = {B00100, B01010, B01010, B10001, B10001, B01110, B00000, B00000,}; // капля
byte term[8]      = {B01100, B01110, B01100, B01110, B01100, B10010, B10010, B01100,}; // термометр
byte press[8]     = {B00100, B00100, B01000, B01000, B00100, B10101, B01110, B00100,}; // давление
byte mr[8]        = {B10001, B11011, B10101, B10001, B01100, B01010, B01100, B01001,}; // мр
byte ms[8]        = {B10001, B11011, B10101, B10001, B01100, B10000, B10000, B01101,}; // мc
//byte bukva_B[8]   = {B11110, B10000, B10000, B11110, B10001, B10001, B11110, B00000,}; // Буква "Б"
byte bukva_G[8]   = {B11111, B10001, B10000, B10000, B10000, B10000, B10000, B00000,}; // Буква "Г"
byte bukva_D[8]   = {B01111, B00101, B00101, B01001, B10001, B11111, B10001, B00000,}; // Буква "Д"
//byte bukva_ZH[8]  = {B10101, B10101, B10101, B11111, B10101, B10101, B10101, B00000,}; // Буква "Ж"
//byte bukva_Z[8]   = {B01110, B10001, B00001, B00010, B00001, B10001, B01110, B00000,}; // Буква "З"
byte bukva_I[8]   = {B10001, B10011, B10011, B10101, B11001, B11001, B10001, B00000,}; // Буква "И"
//byte bukva_IY[8]  = {B01110, B00000, B10001, B10011, B10101, B11001, B10001, B00000,}; // Буква "Й"
//byte bukva_L[8]   = {B00011, B00111, B00101, B00101, B01101, B01001, B11001, B00000,}; // Буква "Л"
byte bukva_P[8]   = {B11111, B10001, B10001, B10001, B10001, B10001, B10001, B00000,}; // Буква "П"
//byte bukva_Y[8]   = {B10001, B10001, B10001, B01010, B00100, B01000, B10000, B00000,}; // Буква "У"
//byte bukva_F[8]   = {B00100, B11111, B10101, B10101, B11111, B00100, B00100, B00000,}; // Буква "Ф"
byte bukva_TS[8]  = {B10010, B10010, B10010, B10010, B10010, B10010, B11111, B00001,}; // Буква "Ц"
//byte bukva_CH[8]  = {B10001, B10001, B10001, B01111, B00001, B00001, B00001, B00000,}; // Буква "Ч"
//byte bukva_Sh[8]  = {B10101, B10101, B10101, B10101, B10101, B10101, B11111, B00000,}; // Буква "Ш"
//byte bukva_Shch[8] = {B10101, B10101, B10101, B10101, B10101, B10101, B11111, B00001,}; // Буква "Щ"
//byte bukva_Mz[8]  = {B10000, B10000, B10000, B11110, B10001, B10001, B11110, B00000,}; // Буква "Ь"
//byte bukva_IYI[8] = {B10001, B10001, B10001, B11001, B10101, B10101, B11001, B00000,}; // Буква "Ы"
//byte bukva_Yu[8]  = {B10010, B10101, B10101, B11101, B10101, B10101, B10010, B00000,}; // Буква "Ю"
byte bukva_Ya[8]  = {B01111, B10001, B10001, B01111, B00101, B01001, B10001, B00000,}; // Буква "Я"

//------Установки и прочее------//
void setup() {
  MsTimer2::set(2, timerInterupt);   //задаем период прерывания по таймеру 2 мс
  MsTimer2::start();                //запускаем таймер
  indoorSensor.begin();             //запускаем датчик внутренний
  outdoorSensor.begin();            //запускаем датчик уличный
  Serial.begin(9600);               //запускаем серийный порт
  lcd.begin();                      //запускаем дисплей
  bmp.begin();                      //запускаем датчик давления
  lcd.backlight();
  hello();                          //выводим приветствие
}

//------Основной цикл------//
void loop() {

  //!!!!!!!Обработка счетчиков!!!!!!!
  if (timerMeas >= CYCLE_MEAS) {  //проверка таймера измерений
    timerMeas = 0;                //сброс таймера измерений
    measureTHP();
//    Serial.println("timerMeas");
  }

  //!!!!!!!Обработка кнопки!!!!!!!
  if (button1.flagClick == true) {    //был "клик" кнопкой
    button1.flagClick = false;        //сброс флага "клик" кнопкой
    flagUpdateMode = true;            //установить флаг смены режима
    mode++;                           //смена режима
//    Serial.print("flagUpdateMode = ");
//    Serial.println(flagUpdateMode);
    delay(200);
    if (mode > 1) {
      mode = 0;
    }
  }

  //!!!!!!!Режим работы!!!!!!!
  switch (mode) {
    //режим по умолчанию(показания уличные)
    case 0:
      if (flagUpdateMode == true) { //обновить дисплей по факту смены режима
        flagUpdateMode = false;
        clearLCD();
        updateLcdOutdoorTHP();
      }
      if (flagUpdateLCD == true) { //обновить дисплей по факту получения новых измерений
        flagUpdateLCD = false;
        updateLcdOutdoorTHP();
      }
      break;
    case 1:
      if (flagUpdateMode == true) { //обновить дисплей по факту смены режима
        flagUpdateMode = false;
        clearLCD();
        updateLcdIndoorTH();
      }
      if (flagUpdateLCD == true) {//обновить дисплей по факту получения новых измерений
        flagUpdateLCD = false;
        updateLcdIndoorTH();
      }
      break;
  }

}

//------Обработчик прерываний------//
void timerInterupt() {
  button1.scanState();            //проверяем нажатие кнопки
  timerMeas++;                    //инкремент таймера измерений
}

//------Измерение параметров окр.среды------//
void measureTHP() {
  float temp;
  tIndoor = indoorSensor.readTemperature() * 100.0;     //измеряем температуру в помещении
  hIndoor = indoorSensor.readHumidity();                //измеряем влажность в помещении
  tOutdoor = outdoorSensor.readTemperature() * 100.0;   //измеряем температуру уличную
  hOutdoor = outdoorSensor.readHumidity();              //измеряем влажность уличную
  temp = bmp.readTemperature();                         //необходимо по условиям расчета давления(см. datasheet)
  pressure = bmp.readPressure() * 0.0075;               //измеряем давление и переводим в мм.рт.ст.
  temp = bmp.readAltitude();
  temp = bmp.readSealevelPressure();
  temp = bmp.readAltitude(101500);
  flagUpdateLCD = true;
//  Serial.print("flagUpdateLCD = ");
//  Serial.println(flagUpdateLCD);
//  Serial.println("measureTHP() STOP");
}

//------Вывод текущих уличных значений на дисплей------//
void updateLcdOutdoorTHP() {
  char _tOutdoorStr[4];
  char _hOutdoorStr[4];
  char _pressureStr[4];
  float _tOutdoor = tOutdoor / 100.0;
  dtostrf(_tOutdoor, 4, 1, _tOutdoorStr);
  lcd.setCursor(0, 0);
  lcd.print("\1 ");
  lcd.print("\6 ");
  lcd.print(_tOutdoorStr);
  lcd.setCursor(9, 0);
  lcd.print("\3C");
  lcd.setCursor(12, 0);
  lcd.print("\5");
  dtostrf(hOutdoor, 2, 0, _hOutdoorStr);
  lcd.print(_hOutdoorStr);
  lcd.print("%");
  dtostrf(pressure, 4, 1, _pressureStr);
  lcd.setCursor(2, 1);
  lcd.print("\4\4 ");
  lcd.print(_pressureStr);
  lcd.print(" \2\7");
  flagUpdateLCD = false;
  //  Serial.println("updateLcdOutdoorTHP()");
}

//------Вывод текущих значений в помещении на дисплей------//
void updateLcdIndoorTH() {
  char _tIndoorStr[4];
  char _hIndoorStr[4];
  float _tIndoor = tIndoor / 100.0;
  dtostrf(_tIndoor, 4, 1, _tIndoorStr);
  lcd.setCursor(0, 0);
  lcd.print("\1 ");
  lcd.print("\6 ");
  lcd.print(_tIndoorStr);
  lcd.setCursor(9, 0);
  lcd.print("\3C");
  lcd.setCursor(12, 0);
  lcd.print("\5");
  dtostrf(hIndoor, 2, 0, _hIndoorStr);
  lcd.print(_hIndoorStr);
  lcd.print("%");
  lcd.setCursor(0, 1);
  flagUpdateLCD = false;
  //  Serial.println("updateLcdIndoorTH()");
}

//------Очистка дисплея------//
void clearLCD() {
  lcd.setCursor(0, 0);
  lcd.print("                ");
  lcd.setCursor(0, 1);
  lcd.print("                ");
}

//------Приветствие------//
void hello() {
  //  Serial.println("Погодная станция v.01.20");
  lcd.createChar(1, bukva_P);
  lcd.createChar(2, bukva_G);
  lcd.createChar(3, bukva_D);
  lcd.createChar(4, bukva_Ya);
  lcd.createChar(5, bukva_TS);
  lcd.createChar(6, bukva_I);
  lcd.setCursor(4, 0);             // Установка курсора в 5ю позицию первой строки
  lcd.print("\1O\2O\3HA\4");       // Набор текста на первой строке
  lcd.setCursor(0, 1);             // Установка курсора в начало второй строки
  lcd.print("CTAH\5\6\4 v.01.20"); // Набор текста на второй строке
  delay(2000);
  clearLCD();
  lcd.createChar(1, outdoor);     // Создаем значек снаружи
  lcd.createChar(2, mr);          // Создаем значек мр
  lcd.createChar(3, deg);         // Создаем значек градуса
  lcd.createChar(4, press);       // Создаем значек давления
  lcd.createChar(5, hum);         // Создаем значек влажность
  lcd.createChar(6, term);        // Создаем значек термометр
  lcd.createChar(7, ms);          // Создаем значек мс
}

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, вот, значит проблема в задержке. Что-то не успевает. Сейчас посмотрим, что может не успевать.

Ну, примерно, понятно. Вам ведь давно говорили, что 

sadman41 пишет:
Переменные, которые изменяются в прерывании, следует объявлять как volatile. Иначе компилятор их соптимизирует неочевидным для человека способом.

Но Вы это игнорируете. Переменная button1 изменяется в обработчике прерывания, но при этом не volatile ни грамма. Я не знаю как устроен объект Button, потому не могу сказать, достаточно ли просто объявить её volatile в строке 15 (можете попробовать), но если не поможет, то надо будет смотреть на объект Button.

И ещё, Вам также говорили, что

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

но Вы и это игнорируете. Вы не понимаете этих слов? Или считаете их глупостью?

Попытайтесь объявить button1 как volatile. Хотя, на самом деле, уж лучше сделать правильно, примерно так:

...
volatile bool timerTickDetected = false;
...
// в обработчике прерывания ТОЛЬКО
timerTickDetected = true;
...
// в начале функции loop
cli();
const bool timeToScan = timerTickDetected ;
timerTickDetected  = false;
sei();

if (timeToScan) {
    button1.scanState();            //проверяем нажатие кнопки
    timerMeas++;                    //инкремент таймера измерений
}
...
sadman41
Offline
Зарегистрирован: 19.10.2016

А не "гуверовская" ли кнопка туда приделана? 

meridian86
Offline
Зарегистрирован: 09.11.2019

Слов по поводу атомарного копирования реально не понимаю... И уж тем более глупостью не считаю. Про кнопку, просто не подумал, т.к. сам флаг button1.flagClick работает исправно. А ошибка возникает не в ходе обработки прерывания, а в ходе выполнения цикла loop(). 

Объявление Button через volatile не помогло.

Button.h :

// проверка, что библиотека еще не подключена
#ifndef Button_h // если библиотека Button не подключена
#define Button_h // тогда подключаем ее

#include "Arduino.h"

// класс обработки сигналов
class Button {
  public:
    Button(byte pin, byte timeButton);  // конструктор
    boolean flagPress;    // признак кнопка нажата (сигнал в низком уровне)
    boolean flagClick;    // признак клика кнопки (фронт)
    void  scanState();    // метод проверки ожидание стабильного состояния сигнала
    void  filterAvarage(); // метод фильтрации по среднему значению
    void setPinTime(byte pin, byte timeButton); // установка номера вывода и времени фильтрации
 
 private:
    byte  _buttonCount;    // счетчик времени фильтрации  
    byte _timeButton;      // время фильтрации
    byte _pin;             // номер вывода
};

#endif

Button.cpp :

#include "Arduino.h"
#include "Button.h"
// метод фильтрации сигнала по среднему значению
// при сигнале низкого уровня flagPress= true
// при сигнале высокого уровня flagPress= false
// при изменении состояния с высокого на низкий flagClick= true
void Button::filterAvarage() {

 if ( flagPress != digitalRead(_pin) ) {
     //  состояние кнопки осталось прежним
     if ( _buttonCount != 0 ) _buttonCount--; // счетчик подтверждений - 1 с ограничением на 0
  }
  else {
     // состояние кнопки изменилось
     _buttonCount++;   // +1 к счетчику подтверждений

     if ( _buttonCount >= _timeButton ) {
      // состояние сигнала достигло порога _timeButton
      flagPress= ! flagPress; // инверсия признака состояния
     _buttonCount= 0;  // сброс счетчика подтверждений

      if ( flagPress == true ) flagClick= true; // признак клика кнопки      
     }   
  }
}
// метод проверки ожидание стабильного состояния сигнала
// при нажатой кнопке flagPress= true
// при отжатой кнопке flagPress= false
// при нажатии на кнопку flagClick= true
void Button::scanState() {

 if ( flagPress != digitalRead(_pin) ) {
     // признак flagPress = текущему состоянию кнопки
     // (инверсия т.к. активное состояние кнопки LOW)
     // т.е. состояние кнопки осталось прежним
     _buttonCount= 0;  // сброс счетчика подтверждений состояния кнопки
  }
  else {
     // признак flagPress не = текущему состоянию кнопки
     // состояние кнопки изменилось
     _buttonCount++;   // +1 к счетчику состояния кнопки

     if ( _buttonCount >= _timeButton ) {
      // состояние кнопки не мянялось в течение заданного времени
      // состояние кнопки стало устойчивым
      flagPress= ! flagPress; // инверсия признака состояния
     _buttonCount= 0;  // сброс счетчика подтверждений состояния кнопки

      if ( flagPress == true ) flagClick= true; // признак фронта кнопки на нажатие     
     }   
  }
}
// метод установки номера вывода и времени подтверждения
void Button::setPinTime(byte pin, byte timeButton)  {

  _pin= pin;
  _timeButton= timeButton;
  pinMode(_pin, INPUT_PULLUP);  // определяем вывод как вход
}

// описание конструктора класса Button
Button::Button(byte pin, byte timeButton) {

  _pin= pin;
  _timeButton= timeButton;
  pinMode(_pin, INPUT_PULLUP);  // определяем вывод как вход
}

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Место ли такой кнопке в прерывании... Засадите-ка button1.scanState(); в loop()

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, не помогло, значит надо в класс лезть. Но это не самая систематическая практика. Посмотрите #19 я там дописал как это правильно делается.

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

meridian86
Offline
Зарегистрирован: 09.11.2019

sadman41 пишет:

Место ли такой кнопке в прерывании... Засадите-ка button1.scanState(); в loop()

Спасибо! Работает исправно.

Буду рад пинку в направлении полезной информации по поводу прерываний. В основном все заканчивается volatile. И все на этом. Хотя это не обязательно - в гугле на забанили пока.

meridian86
Offline
Зарегистрирован: 09.11.2019

Вечером поэкспериментирую - обед закончился...

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

meridian86 пишет:

sadman41 пишет:

Место ли такой кнопке в прерывании... Засадите-ка button1.scanState(); в loop()

Спасибо! Работает исправно.

Надеюсь, Вы его не тупо запихали в луп, а как я писал выше? Код бы показали.

meridian86
Offline
Зарегистрирован: 09.11.2019

Запихивание в  loop() работает, но в контексте - разобраться в тонкостях меня не устраивает. По-этому буду вникать в Ваш метод.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

meridian86 пишет:

Запихивание в  loop() работае

Т.е., тупо - вообще без учёта времени (да, приведите ж Вы код, наконец). Если без времени, то боюсь, что работает криво, т.к. Вы убили этим свою библиотеку кнопки, у неё вроде анитдребезг забит на то, что scan вызывается с разумными интервалами, а не когда попало.

meridian86
Offline
Зарегистрирован: 09.11.2019

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

#include "DHT.h"                //Подключаем библиотеку для датчиков типа DHT11/DHT22     
#include "MsTimer2.h"           //Подключаем библиотеку для аппаратных прерываний
#include "Button.h"             //Подключаем библиотеку для обработки кнопки
#include <LiquidCrystal_I2C.h>  //Подключение библиотеки для LCD
#include <Adafruit_BMP085.h>    //Подключаем библиотеку для датчика BMP-180

//------Объявляем имена и значения констант------//
#define DHT_INDOOR_PIN A0               //Пин датчика температуры и влажности в помещении
#define DHT_OUTDOOR_PIN A1              //Пин датчика температуры и влажности уличного
#define BUTTON_1_PIN A2                 //Пин 1 кнопки
#define CYCLE_MEAS 2500                 //Время цикла измерений температуры и влажности * период прерывания по таймеру
#define CYCLE_BACKLIGHT 7500            //Время работы подсветки lcd

//------Создаем объекты------//
Button button1(BUTTON_1_PIN, 15);           //Создали кнопку с периодом антидребезга 15х2мс
DHT indoorSensor(DHT_INDOOR_PIN, DHT11);    //Создали датчик температуры и влажности в помещении
DHT outdoorSensor(DHT_OUTDOOR_PIN, DHT22);  //Создали датчик температуры и влажности уличный
LiquidCrystal_I2C lcd(0x27, 16, 2);         //Создаем дисплей. Указываем I2C адрес (наиболее распространенное значение), а также параметры экрана (в случае LCD 1602 - 2 строки по 16 символов в каждой
Adafruit_BMP085 bmp;                        //Создаем датчик атмосферного давления

//------Объявляем переменные------//
//переменные для работы с данными с датчиков
byte hIndoor;                                   //влажность в помещении
int tIndoor;                                    //температура в помещении
byte hOutdoor;                                  //влажность уличная
int tOutdoor;                                   //температура уличная
float pressure;                                 //атмосферное давление
//переменные для работы с таймерами
volatile unsigned int timerMeas;                //таймер измерений температуры и влажности
boolean flagTimerMeas = false;                  //флаг таймера измерений температуры и влажности
volatile unsigned int timerBacklight;           //таймер lcd подсветки
boolean flagTimerBacklight = true;              //флаг таймера lcd подсветки
//переменные основного цикла
byte mode;                                      //перменная определяющая режим работы
boolean flagUpdateMode;                         //флаг смены режима
boolean flagUpdateLCD;                          //флаг обновления дисплея

//------Создаем нужные символы для дисплея------//
byte deg[8]       = {B00110, B01001, B01001, B00110, B00000, B00000, B00000, B00000,}; // градус
byte up[8]        = {B00000, B00100, B01110, B10101, B00100, B00100, B00100, B00000,}; // вверх
byte down[8]      = {B00000, B00100, B00100, B00100, B10101, B01110, B00100, B00000,}; // вниз
byte outdoor[8]   = {B00011, B00001, B00000, B00100, B01010, B11111, B01010, B01110,}; // снаружи
byte indoor[8]    = {B00010, B00011, B00000, B00100, B01010, B11111, B01010, B01110,}; // внутри
byte hum[8]       = {B00100, B01010, B01010, B10001, B10001, B01110, B00000, B00000,}; // капля
byte term[8]      = {B01100, B01110, B01100, B01110, B01100, B10010, B10010, B01100,}; // термометр
byte press[8]     = {B00100, B00100, B01000, B01000, B00100, B10101, B01110, B00100,}; // давление
byte mr[8]        = {B10001, B11011, B10101, B10001, B01100, B01010, B01100, B01001,}; // мр
byte ms[8]        = {B10001, B11011, B10101, B10001, B01100, B10000, B10000, B01101,}; // мc
//byte bukva_B[8]   = {B11110, B10000, B10000, B11110, B10001, B10001, B11110, B00000,}; // Буква "Б"
byte bukva_G[8]   = {B11111, B10001, B10000, B10000, B10000, B10000, B10000, B00000,}; // Буква "Г"
byte bukva_D[8]   = {B01111, B00101, B00101, B01001, B10001, B11111, B10001, B00000,}; // Буква "Д"
//byte bukva_ZH[8]  = {B10101, B10101, B10101, B11111, B10101, B10101, B10101, B00000,}; // Буква "Ж"
//byte bukva_Z[8]   = {B01110, B10001, B00001, B00010, B00001, B10001, B01110, B00000,}; // Буква "З"
byte bukva_I[8]   = {B10001, B10011, B10011, B10101, B11001, B11001, B10001, B00000,}; // Буква "И"
//byte bukva_IY[8]  = {B01110, B00000, B10001, B10011, B10101, B11001, B10001, B00000,}; // Буква "Й"
//byte bukva_L[8]   = {B00011, B00111, B00101, B00101, B01101, B01001, B11001, B00000,}; // Буква "Л"
byte bukva_P[8]   = {B11111, B10001, B10001, B10001, B10001, B10001, B10001, B00000,}; // Буква "П"
//byte bukva_Y[8]   = {B10001, B10001, B10001, B01010, B00100, B01000, B10000, B00000,}; // Буква "У"
//byte bukva_F[8]   = {B00100, B11111, B10101, B10101, B11111, B00100, B00100, B00000,}; // Буква "Ф"
byte bukva_TS[8]  = {B10010, B10010, B10010, B10010, B10010, B10010, B11111, B00001,}; // Буква "Ц"
//byte bukva_CH[8]  = {B10001, B10001, B10001, B01111, B00001, B00001, B00001, B00000,}; // Буква "Ч"
//byte bukva_Sh[8]  = {B10101, B10101, B10101, B10101, B10101, B10101, B11111, B00000,}; // Буква "Ш"
//byte bukva_Shch[8] = {B10101, B10101, B10101, B10101, B10101, B10101, B11111, B00001,}; // Буква "Щ"
//byte bukva_Mz[8]  = {B10000, B10000, B10000, B11110, B10001, B10001, B11110, B00000,}; // Буква "Ь"
//byte bukva_IYI[8] = {B10001, B10001, B10001, B11001, B10101, B10101, B11001, B00000,}; // Буква "Ы"
//byte bukva_Yu[8]  = {B10010, B10101, B10101, B11101, B10101, B10101, B10010, B00000,}; // Буква "Ю"
byte bukva_Ya[8]  = {B01111, B10001, B10001, B01111, B00101, B01001, B10001, B00000,}; // Буква "Я"

//------Установки и прочее------//
void setup() {
  MsTimer2::set(2, timerInterupt);   //задаем период прерывания по таймеру 2 мс
  MsTimer2::start();                //запускаем таймер
  indoorSensor.begin();             //запускаем датчик внутренний
  outdoorSensor.begin();            //запускаем датчик уличный
  Serial.begin(9600);               //запускаем серийный порт
  lcd.begin();                      //запускаем дисплей
  bmp.begin();                      //запускаем датчик давления
  lcd.backlight();
  hello();                          //выводим приветствие
}

//------Основной цикл------//
void loop() {

  button1.scanState();
  
  //!!!!!!!Обработка счетчиков!!!!!!!
  if (timerMeas >= CYCLE_MEAS) {  //проверка таймера измерений
    timerMeas = 0;                //сброс таймера измерений
    measureTHP();
//    Serial.println("timerMeas");
  }

  //!!!!!!!Обработка кнопки!!!!!!!
  if (button1.flagClick == true) {    //был "клик" кнопкой
    button1.flagClick = false;        //сброс флага "клик" кнопкой
    flagUpdateMode = true;            //установить флаг смены режима
    mode++;                           //смена режима
//    Serial.print("flagUpdateMode = ");
//    Serial.println(flagUpdateMode);
//    delay(200);
    if (mode > 1) {
      mode = 0;
    }
  }

  //!!!!!!!Режим работы!!!!!!!
  switch (mode) {
    //режим по умолчанию(показания уличные)
    case 0:
      if (flagUpdateMode == true) { //обновить дисплей по факту смены режима
        flagUpdateMode = false;
        clearLCD();
        updateLcdOutdoorTHP();
      }
      if (flagUpdateLCD == true) { //обновить дисплей по факту получения новых измерений
        flagUpdateLCD = false;
        updateLcdOutdoorTHP();
      }
      break;
    case 1:
      if (flagUpdateMode == true) { //обновить дисплей по факту смены режима
        flagUpdateMode = false;
        clearLCD();
        updateLcdIndoorTH();
      }
      if (flagUpdateLCD == true) {//обновить дисплей по факту получения новых измерений
        flagUpdateLCD = false;
        updateLcdIndoorTH();
      }
      break;
  }

}

//------Обработчик прерываний------//
void timerInterupt() {
//  button1.scanState();            //проверяем нажатие кнопки
  timerMeas++;                    //инкремент таймера измерений
}

//------Измерение параметров окр.среды------//
void measureTHP() {
  float temp;
  tIndoor = indoorSensor.readTemperature() * 100.0;     //измеряем температуру в помещении
  hIndoor = indoorSensor.readHumidity();                //измеряем влажность в помещении
  tOutdoor = outdoorSensor.readTemperature() * 100.0;   //измеряем температуру уличную
  hOutdoor = outdoorSensor.readHumidity();              //измеряем влажность уличную
  temp = bmp.readTemperature();                         //необходимо по условиям расчета давления(см. datasheet)
  pressure = bmp.readPressure() * 0.0075;               //измеряем давление и переводим в мм.рт.ст.
  temp = bmp.readAltitude();
  temp = bmp.readSealevelPressure();
  temp = bmp.readAltitude(101500);
  flagUpdateLCD = true;
//  Serial.print("flagUpdateLCD = ");
//  Serial.println(flagUpdateLCD);
//  Serial.println("measureTHP() STOP");
}

//------Вывод текущих уличных значений на дисплей------//
void updateLcdOutdoorTHP() {
  char _tOutdoorStr[4];
  char _hOutdoorStr[4];
  char _pressureStr[4];
  float _tOutdoor = tOutdoor / 100.0;
  dtostrf(_tOutdoor, 4, 1, _tOutdoorStr);
  lcd.setCursor(0, 0);
  lcd.print("\1 ");
  lcd.print("\6 ");
  lcd.print(_tOutdoorStr);
  lcd.setCursor(9, 0);
  lcd.print("\3C");
  lcd.setCursor(12, 0);
  lcd.print("\5");
  dtostrf(hOutdoor, 2, 0, _hOutdoorStr);
  lcd.print(_hOutdoorStr);
  lcd.print("%");
  dtostrf(pressure, 4, 1, _pressureStr);
  lcd.setCursor(2, 1);
  lcd.print("\4\4 ");
  lcd.print(_pressureStr);
  lcd.print(" \2\7");
  flagUpdateLCD = false;
  //  Serial.println("updateLcdOutdoorTHP()");
}

//------Вывод текущих значений в помещении на дисплей------//
void updateLcdIndoorTH() {
  char _tIndoorStr[4];
  char _hIndoorStr[4];
  float _tIndoor = tIndoor / 100.0;
  dtostrf(_tIndoor, 4, 1, _tIndoorStr);
  lcd.setCursor(0, 0);
  lcd.print("\1 ");
  lcd.print("\6 ");
  lcd.print(_tIndoorStr);
  lcd.setCursor(9, 0);
  lcd.print("\3C");
  lcd.setCursor(12, 0);
  lcd.print("\5");
  dtostrf(hIndoor, 2, 0, _hIndoorStr);
  lcd.print(_hIndoorStr);
  lcd.print("%");
  lcd.setCursor(0, 1);
  flagUpdateLCD = false;
  //  Serial.println("updateLcdIndoorTH()");
}

//------Очистка дисплея------//
void clearLCD() {
  lcd.setCursor(0, 0);
  lcd.print("                ");
  lcd.setCursor(0, 1);
  lcd.print("                ");
}

//------Приветствие------//
void hello() {
  //  Serial.println("Погодная станция v.01.20");
  lcd.createChar(1, bukva_P);
  lcd.createChar(2, bukva_G);
  lcd.createChar(3, bukva_D);
  lcd.createChar(4, bukva_Ya);
  lcd.createChar(5, bukva_TS);
  lcd.createChar(6, bukva_I);
  lcd.setCursor(4, 0);             // Установка курсора в 5ю позицию первой строки
  lcd.print("\1O\2O\3HA\4");       // Набор текста на первой строке
  lcd.setCursor(0, 1);             // Установка курсора в начало второй строки
  lcd.print("CTAH\5\6\4 v.01.20"); // Набор текста на второй строке
  delay(2000);
  clearLCD();
  lcd.createChar(1, outdoor);     // Создаем значек снаружи
  lcd.createChar(2, mr);          // Создаем значек мр
  lcd.createChar(3, deg);         // Создаем значек градуса
  lcd.createChar(4, press);       // Создаем значек давления
  lcd.createChar(5, hum);         // Создаем значек влажность
  lcd.createChar(6, term);        // Создаем значек термометр
  lcd.createChar(7, ms);          // Создаем значек мс
}

 

meridian86
Offline
Зарегистрирован: 09.11.2019

Что дает строка ? Почему timeToScan объявляем, как const?

const bool timeToScan = timerTickDetected ;

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

meridian86 пишет:

Что дает строка ? 

const bool timeToScan = timerTickDetected ;

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

meridian86 пишет:

Почему timeToScan объявляем, как const?

Потому, что мы не собираемся её изменять.

meridian86 пишет:

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

Глянул код. timerMeas по-прежнему читается "неатомарно", т.е. грабли лежат и ждут незадачливого прохожего. Делайте, как я написал - так Вы себе замедленную мину закладываете.

meridian86
Offline
Зарегистрирован: 09.11.2019

Если я все правильно понял, то button.scanState() мы будем вызывать один раз за период loop? Т.е. также с не очень понятным периодом антидребезга? 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

meridian86 пишет:

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

Неправильно понял. См. #19 - строки №№ 14 и 15 выполняются только тогда, когда сработал таймер. И не выполняются тогда, когда он не срабатывал.

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

meridian86
Offline
Зарегистрирован: 09.11.2019

ЕвгенийП пишет:

meridian86 пишет:

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

Неправильно понял. См. #19 - строки №№ 14 и 15 выполняются только тогда, когда сработал таймер. И не выполняются тогда, когда он не срабатывал.

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

Неправильно сформулировал вопрос - строки 14 и 15 выполняются только тогда, когда сработал таймер, но не чаще 1 раза за итерацию loop(). Т.о. если выполнение loop() займет более 2 мс, то и период вызова scanState() тоже изменится. А выполнятся loop() может дольше 2мс, например когда вызовем функцию measureTHP().

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

#include "DHT.h"                //Подключаем библиотеку для датчиков типа DHT11/DHT22     
#include "MsTimer2.h"           //Подключаем библиотеку для аппаратных прерываний
#include "Button.h"             //Подключаем библиотеку для обработки кнопки
#include <LiquidCrystal_I2C.h>  //Подключение библиотеки для LCD
#include <Adafruit_BMP085.h>    //Подключаем библиотеку для датчика BMP-180

//------Объявляем имена и значения констант------//
#define DHT_INDOOR_PIN A0               //Пин датчика температуры и влажности в помещении
#define DHT_OUTDOOR_PIN A1              //Пин датчика температуры и влажности уличного
#define BUTTON_1_PIN A2                 //Пин 1 кнопки
#define CYCLE_MEAS 2500                 //Время цикла измерений температуры и влажности * период прерывания по таймеру
#define CYCLE_BACKLIGHT 7500            //Время работы подсветки lcd

//------Создаем объекты------//
Button button1(BUTTON_1_PIN, 15);           //Создали кнопку с периодом антидребезга 15х2мс
DHT indoorSensor(DHT_INDOOR_PIN, DHT11);    //Создали датчик температуры и влажности в помещении
DHT outdoorSensor(DHT_OUTDOOR_PIN, DHT22);  //Создали датчик температуры и влажности уличный
LiquidCrystal_I2C lcd(0x27, 16, 2);         //Создаем дисплей. Указываем I2C адрес (наиболее распространенное значение), а также параметры экрана (в случае LCD 1602 - 2 строки по 16 символов в каждой
Adafruit_BMP085 bmp;                        //Создаем датчик атмосферного давления

//------Объявляем переменные------//
//переменные для работы с данными с датчиков
byte hIndoor;                                   //влажность в помещении
int tIndoor;                                    //температура в помещении
byte hOutdoor;                                  //влажность уличная
int tOutdoor;                                   //температура уличная
float pressure;                                 //атмосферное давление
//переменные для работы с таймерами
unsigned int timerMeas;                //таймер измерений температуры и влажности
boolean flagTimerMeas = false;                  //флаг таймера измерений температуры и влажности
unsigned int timerBacklight;           //таймер lcd подсветки
boolean flagTimerBacklight = true;              //флаг таймера lcd подсветки
//переменные основного цикла
byte mode;                                      //перменная определяющая режим работы
boolean flagUpdateMode;                         //флаг смены режима
boolean flagUpdateLCD;                         //флаг обновления дисплея
volatile bool timerTickDetected = false;

//------Создаем нужные символы для дисплея------//
byte deg[8]       = {B00110, B01001, B01001, B00110, B00000, B00000, B00000, B00000,}; // градус
byte up[8]        = {B00000, B00100, B01110, B10101, B00100, B00100, B00100, B00000,}; // вверх
byte down[8]      = {B00000, B00100, B00100, B00100, B10101, B01110, B00100, B00000,}; // вниз
byte outdoor[8]   = {B00011, B00001, B00000, B00100, B01010, B11111, B01010, B01110,}; // снаружи
byte indoor[8]    = {B00010, B00011, B00000, B00100, B01010, B11111, B01010, B01110,}; // внутри
byte hum[8]       = {B00100, B01010, B01010, B10001, B10001, B01110, B00000, B00000,}; // капля
byte term[8]      = {B01100, B01110, B01100, B01110, B01100, B10010, B10010, B01100,}; // термометр
byte press[8]     = {B00100, B00100, B01000, B01000, B00100, B10101, B01110, B00100,}; // давление
byte mr[8]        = {B10001, B11011, B10101, B10001, B01100, B01010, B01100, B01001,}; // мр
byte ms[8]        = {B10001, B11011, B10101, B10001, B01100, B10000, B10000, B01101,}; // мc
//byte bukva_B[8]   = {B11110, B10000, B10000, B11110, B10001, B10001, B11110, B00000,}; // Буква "Б"
byte bukva_G[8]   = {B11111, B10001, B10000, B10000, B10000, B10000, B10000, B00000,}; // Буква "Г"
byte bukva_D[8]   = {B01111, B00101, B00101, B01001, B10001, B11111, B10001, B00000,}; // Буква "Д"
//byte bukva_ZH[8]  = {B10101, B10101, B10101, B11111, B10101, B10101, B10101, B00000,}; // Буква "Ж"
//byte bukva_Z[8]   = {B01110, B10001, B00001, B00010, B00001, B10001, B01110, B00000,}; // Буква "З"
byte bukva_I[8]   = {B10001, B10011, B10011, B10101, B11001, B11001, B10001, B00000,}; // Буква "И"
//byte bukva_IY[8]  = {B01110, B00000, B10001, B10011, B10101, B11001, B10001, B00000,}; // Буква "Й"
//byte bukva_L[8]   = {B00011, B00111, B00101, B00101, B01101, B01001, B11001, B00000,}; // Буква "Л"
byte bukva_P[8]   = {B11111, B10001, B10001, B10001, B10001, B10001, B10001, B00000,}; // Буква "П"
//byte bukva_Y[8]   = {B10001, B10001, B10001, B01010, B00100, B01000, B10000, B00000,}; // Буква "У"
//byte bukva_F[8]   = {B00100, B11111, B10101, B10101, B11111, B00100, B00100, B00000,}; // Буква "Ф"
byte bukva_TS[8]  = {B10010, B10010, B10010, B10010, B10010, B10010, B11111, B00001,}; // Буква "Ц"
//byte bukva_CH[8]  = {B10001, B10001, B10001, B01111, B00001, B00001, B00001, B00000,}; // Буква "Ч"
//byte bukva_Sh[8]  = {B10101, B10101, B10101, B10101, B10101, B10101, B11111, B00000,}; // Буква "Ш"
//byte bukva_Shch[8] = {B10101, B10101, B10101, B10101, B10101, B10101, B11111, B00001,}; // Буква "Щ"
//byte bukva_Mz[8]  = {B10000, B10000, B10000, B11110, B10001, B10001, B11110, B00000,}; // Буква "Ь"
//byte bukva_IYI[8] = {B10001, B10001, B10001, B11001, B10101, B10101, B11001, B00000,}; // Буква "Ы"
//byte bukva_Yu[8]  = {B10010, B10101, B10101, B11101, B10101, B10101, B10010, B00000,}; // Буква "Ю"
byte bukva_Ya[8]  = {B01111, B10001, B10001, B01111, B00101, B01001, B10001, B00000,}; // Буква "Я"

//------Установки и прочее------//
void setup() {
  MsTimer2::set(2, timerInterupt);   //задаем период прерывания по таймеру 2 мс
  MsTimer2::start();                //запускаем таймер
  indoorSensor.begin();             //запускаем датчик внутренний
  outdoorSensor.begin();            //запускаем датчик уличный
  Serial.begin(9600);               //запускаем серийный порт
  lcd.begin();                      //запускаем дисплей
  bmp.begin();                      //запускаем датчик давления
  lcd.backlight();
  hello();                          //выводим приветствие
}

//------Основной цикл------//
void loop() {
  cli();
  const bool timeToScan = timerTickDetected;
  timerTickDetected = false;
  sei();

  if (timeToScan) {
    button1.scanState();
    timerMeas++;
  }


  //!!!!!!!Обработка счетчиков!!!!!!!
  if (timerMeas >= CYCLE_MEAS) {  //проверка таймера измерений
    timerMeas = 0;                //сброс таймера измерений
    measureTHP();
    //    Serial.println("timerMeas");
  }

  //!!!!!!!Обработка кнопки!!!!!!!
  if (button1.flagClick == true) {    //был "клик" кнопкой
    button1.flagClick = false;        //сброс флага "клик" кнопкой
    flagUpdateMode = true;            //установить флаг смены режима
    mode++;                           //смена режима
    //    Serial.print("flagUpdateMode = ");
    //    Serial.println(flagUpdateMode);
    //    delay(200);
    if (mode > 1) {
      mode = 0;
    }
  }

  //!!!!!!!Режим работы!!!!!!!
  switch (mode) {
    //режим по умолчанию(показания уличные)
    case 0:
      if (flagUpdateMode == true) { //обновить дисплей по факту смены режима
        flagUpdateMode = false;
        clearLCD();
        updateLcdOutdoorTHP();
      }
      if (flagUpdateLCD == true) { //обновить дисплей по факту получения новых измерений
        flagUpdateLCD = false;
        updateLcdOutdoorTHP();
      }
      break;
    case 1:
      if (flagUpdateMode == true) { //обновить дисплей по факту смены режима
        flagUpdateMode = false;
        clearLCD();
        updateLcdIndoorTH();
      }
      if (flagUpdateLCD == true) {//обновить дисплей по факту получения новых измерений
        flagUpdateLCD = false;
        updateLcdIndoorTH();
      }
      break;
  }
}

//------Обработчик прерываний------//
void timerInterupt() {
  //  button1.scanState();            //проверяем нажатие кнопки
  //  timerMeas++;                    //инкремент таймера измерений
  timerTickDetected = true;
}

//------Измерение параметров окр.среды------//
void measureTHP() {
  float temp;
  tIndoor = indoorSensor.readTemperature() * 100.0;     //измеряем температуру в помещении
  hIndoor = indoorSensor.readHumidity();                //измеряем влажность в помещении
  tOutdoor = outdoorSensor.readTemperature() * 100.0;   //измеряем температуру уличную
  hOutdoor = outdoorSensor.readHumidity();              //измеряем влажность уличную
  temp = bmp.readTemperature();                         //необходимо по условиям расчета давления(см. datasheet)
  pressure = bmp.readPressure() * 0.0075;               //измеряем давление и переводим в мм.рт.ст.
  temp = bmp.readAltitude();
  temp = bmp.readSealevelPressure();
  temp = bmp.readAltitude(101500);
  flagUpdateLCD = true;
  //  Serial.print("flagUpdateLCD = ");
  //  Serial.println(flagUpdateLCD);
  //    Serial.println("measureTHP() STOP");
}

//------Вывод текущих уличных значений на дисплей------//
void updateLcdOutdoorTHP() {
  char _tOutdoorStr[4];
  char _hOutdoorStr[4];
  char _pressureStr[4];
  float _tOutdoor = tOutdoor / 100.0;
  dtostrf(_tOutdoor, 4, 1, _tOutdoorStr);
  lcd.setCursor(0, 0);
  lcd.print("\1 ");
  lcd.print("\6 ");
  lcd.print(_tOutdoorStr);
  lcd.setCursor(9, 0);
  lcd.print("\3C");
  lcd.setCursor(12, 0);
  lcd.print("\5");
  dtostrf(hOutdoor, 2, 0, _hOutdoorStr);
  lcd.print(_hOutdoorStr);
  lcd.print("%");
  dtostrf(pressure, 4, 1, _pressureStr);
  lcd.setCursor(2, 1);
  lcd.print("\4\4 ");
  lcd.print(_pressureStr);
  lcd.print(" \2\7");
  flagUpdateLCD = false;
  //  Serial.println("updateLcdOutdoorTHP()");
}

//------Вывод текущих значений в помещении на дисплей------//
void updateLcdIndoorTH() {
  char _tIndoorStr[4];
  char _hIndoorStr[4];
  float _tIndoor = tIndoor / 100.0;
  dtostrf(_tIndoor, 4, 1, _tIndoorStr);
  lcd.setCursor(0, 0);
  lcd.print("\1 ");
  lcd.print("\6 ");
  lcd.print(_tIndoorStr);
  lcd.setCursor(9, 0);
  lcd.print("\3C");
  lcd.setCursor(12, 0);
  lcd.print("\5");
  dtostrf(hIndoor, 2, 0, _hIndoorStr);
  lcd.print(_hIndoorStr);
  lcd.print("%");
  lcd.setCursor(0, 1);
  flagUpdateLCD = false;
  //  Serial.println("updateLcdIndoorTH()");
}

//------Очистка дисплея------//
void clearLCD() {
  lcd.setCursor(0, 0);
  lcd.print("                ");
  lcd.setCursor(0, 1);
  lcd.print("                ");
}

//------Приветствие------//
void hello() {
  //  Serial.println("Погодная станция v.01.20");
  lcd.createChar(1, bukva_P);
  lcd.createChar(2, bukva_G);
  lcd.createChar(3, bukva_D);
  lcd.createChar(4, bukva_Ya);
  lcd.createChar(5, bukva_TS);
  lcd.createChar(6, bukva_I);
  lcd.setCursor(4, 0);             // Установка курсора в 5ю позицию первой строки
  lcd.print("\1O\2O\3HA\4");       // Набор текста на первой строке
  lcd.setCursor(0, 1);             // Установка курсора в начало второй строки
  lcd.print("CTAH\5\6\4 v.01.20"); // Набор текста на второй строке
  delay(2000);
  clearLCD();
  lcd.createChar(1, outdoor);     // Создаем значек снаружи
  lcd.createChar(2, mr);          // Создаем значек мр
  lcd.createChar(3, deg);         // Создаем значек градуса
  lcd.createChar(4, press);       // Создаем значек давления
  lcd.createChar(5, hum);         // Создаем значек влажность
  lcd.createChar(6, term);        // Создаем значек термометр
  lcd.createChar(7, ms);          // Создаем значек мс
}

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

meridian86 пишет:
но не чаще 1 раза за итерацию loop(). Т.о. если выполнение loop() займет более 2 мс, то и период вызова scanState() тоже изменится.
Так и есть.

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

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

meridian86
Offline
Зарегистрирован: 09.11.2019

А можно, чуть подробнее про блокирующий/неблокирующий вызов функции?

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

meridian86 пишет:

А можно, чуть подробнее про блокирующий/неблокирующий вызов функции?

нужно открыть исходник библиотек длясенсоров DHT и BMP посмотреть, есть ли в них задержки длиннее 1-2мс. Если нет - то можно ничего не делать, а если есть - придется переписывать на основе миллис

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

meridian86 пишет:

А можно, чуть подробнее про блокирующий/неблокирующий вызов функции?

Ну, чего тут подробнее. Большинство библиотек, которыми Вы пользуетесь, написаны не разработчиками автоматики, а разработчиками конкретных устройств. И цель их  продемонстрировать работоспособность устройства и не более того.

Вот, например, Вы пользуетесь библиотекой DHT. Откройте файл DHT.cpp и найдите там функцию boolean DHT::read(bool force) (она вызывается из readTemperature и их readHumidity). Смотрим на неё и что видим (я вставил комментарий "!!!!!" в важных для нас местах:

boolean DHT::read(bool force) {
  // Check if sensor was read less than two seconds ago and return early
  // to use last reading.
  uint32_t currenttime = millis();
  if (!force && ((currenttime - _lastreadtime) < 2000)) {
    return _lastresult; // return last correct measurement
  }
  _lastreadtime = currenttime;

  // Reset 40 bits of received data to zero.
  data[0] = data[1] = data[2] = data[3] = data[4] = 0;

  // Send start signal.  See DHT datasheet for full signal diagram:
  //   http://www.adafruit.com/datasheets/Digital%20humidity%20and%20temperature%20sensor%20AM2302.pdf

  // Go into high impedence state to let pull-up raise data line level and
  // start the reading process.
  digitalWrite(_pin, HIGH);
  delay(250);	// 250 мс (!!!!!)

  // First set data line low for 20 milliseconds.
  pinMode(_pin, OUTPUT);
  digitalWrite(_pin, LOW);
  delay(20);	// 20 мс (!!!!!)

  uint32_t cycles[80];
  {
    // Turn off interrupts temporarily because the next sections are timing critical
    // and we don't want any interruptions.
    InterruptLock lock;

    // End the start signal by setting data line high for 40 microseconds.
    digitalWrite(_pin, HIGH);
    delayMicroseconds(40);

    // Now start reading the data line to get the value from the DHT sensor.
    pinMode(_pin, INPUT_PULLUP);
    delayMicroseconds(10);  // Delay a bit to let sensor pull data line low.

    // First expect a low signal for ~80 microseconds followed by a high signal
    // for ~80 microseconds again.
    if (expectPulse(LOW) == 0) {
      DEBUG_PRINTLN(F("Timeout waiting for start signal low pulse."));
      _lastresult = false;
      return _lastresult;
    }
    if (expectPulse(HIGH) == 0) {
      DEBUG_PRINTLN(F("Timeout waiting for start signal high pulse."));
      _lastresult = false;
      return _lastresult;
    }

    // Now read the 40 bits sent by the sensor.  Each bit is sent as a 50
    // microsecond low pulse followed by a variable length high pulse.  If the
    // high pulse is ~28 microseconds then it's a 0 and if it's ~70 microseconds
    // then it's a 1.  We measure the cycle count of the initial 50us low pulse
    // and use that to compare to the cycle count of the high pulse to determine
    // if the bit is a 0 (high state cycle count < low state cycle count), or a
    // 1 (high state cycle count > low state cycle count). Note that for speed all
    // the pulses are read into a array and then examined in a later step.
    for (int i=0; i<80; i+=2) { // Итого 40*2 - до 80 мс (!!!!!)
      cycles[i]   = expectPulse(LOW);	// До 1 мс (!!!!!)
      cycles[i+1] = expectPulse(HIGH); //  До 1 мс (!!!!!)
    }
  } // Timing critical code is now complete.

  // Inspect pulses and determine which ones are 0 (high state cycle count < low
  // state cycle count), or 1 (high state cycle count > low state cycle count).
  for (int i=0; i<40; ++i) {
    uint32_t lowCycles  = cycles[2*i];
    uint32_t highCycles = cycles[2*i+1];
    if ((lowCycles == 0) || (highCycles == 0)) {
      DEBUG_PRINTLN(F("Timeout waiting for pulse."));
      _lastresult = false;
      return _lastresult;
    }
    data[i/8] <<= 1;
    // Now compare the low and high cycle times to see if the bit is a 0 or 1.
    if (highCycles > lowCycles) {
      // High cycles are greater than 50us low cycle count, must be a 1.
      data[i/8] |= 1;
    }
    // Else high cycles are less than (or equal to, a weird case) the 50us low
    // cycle count so this must be a zero.  Nothing needs to be changed in the
    // stored data.
  }

  DEBUG_PRINTLN(F("Received:"));
  DEBUG_PRINT(data[0], HEX); DEBUG_PRINT(F(", "));
  DEBUG_PRINT(data[1], HEX); DEBUG_PRINT(F(", "));
  DEBUG_PRINT(data[2], HEX); DEBUG_PRINT(F(", "));
  DEBUG_PRINT(data[3], HEX); DEBUG_PRINT(F(", "));
  DEBUG_PRINT(data[4], HEX); DEBUG_PRINT(F(" =? "));
  DEBUG_PRINTLN((data[0] + data[1] + data[2] + data[3]) & 0xFF, HEX);

  // Check we read 40 bits and that the checksum matches.
  if (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) {
    _lastresult = true;
    return _lastresult;
  }
  else {
    DEBUG_PRINTLN(F("Checksum failure!"));
    _lastresult = false;
    return _lastresult;
  }
}

Итого, эта функция в худшем случае может Вам дать до 350 мс задержки (это я ещё не учитывал копейки типа delayMicroseconds). В лучшем же случае она Вам даст 270мс с копейками.

Вы же у себя в measureTHP вызываете её 4 раза. Итого, в худшем случае задержки составят 1,4 секунды, а в самом лучшем - 1,08 секунды.

Далее, там же в measureTHP Вы вызываете несколько функций из библиотеки Adafruit_BMP085 (я не буду приводить текст библиотеки, сами смотрите, я только времена задержек приведу). Смотрим на неё. Функции, которые вызываете: readTemperature (delay - 5мс), readPressure (delay - до 26 мс), readAltitude два раза (delay  26 мс в каждой), readSealevelPressure (ещё 26 мс)

Итого, имеем, что вызов всех этих библиотечных функций обходится Вам в 1,1 - 1,5 секунды тупых delay'ев. Ну, и какие кнопки? Как они, по-вашему, должны работать?

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

Что делать?

1. Обходиться без таких библиотек (искать другие или писать самому, что обычно и делают те, кто умеет писать)
2. Сажать кнопки на прерывания (но тут свои грабли - см. ниже)
3. Терпеть.
4. Придумать пользовательский интерфейс, в котором это некритично.

По поводу п 2. - не всё так просто. Ну, хорошо отловили Вы нажатие кнопки прерыванием прямо во время измерения. И что делать? Прерывать измерение и плевать на него? Так на это опять же библиотеки не заточены - не умеют они прерываться на полпути.

По п.4. мой внук как-о выдал такое решение. При нажатии на кнопку (он её через прерывание ловил) нужная команда исполнялась не сразу, а сначала начинала "разбег" дорожка из светодиодов. А команда исполнялась после этого светового шоу. Он подавал это как крутую фичу, но на деле замаскировал задержку :-)

sadman41
Offline
Зарегистрирован: 19.10.2016

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

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

meridian86
Offline
Зарегистрирован: 09.11.2019

Большое спасибо, за подробные разъяснения! 

Еще один вопрос:

cli();
  const bool timeToScan = timerTickDetected;
  timerTickDetected = false;
  sei();

Это и есть атомарное изменение переменной? Т.е. при запрете прерываний на время ее записи?

bwn
Offline
Зарегистрирован: 25.08.2014

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

sadman41
Offline
Зарегистрирован: 19.10.2016

Рассматривая пример:

// в начале функции loop
cli();
const bool timeToScan = timerTickDetected ;
// <<< момент Хэ
timerTickDetected  = false;
sei();

Мы можем увидеть, что прерывание потенциально может произойти между операциями, в момент Хэ и обработчик установит timerTickDetected в true. После возврата из обработчика выполнится следующая операция и timerTickDetected перевернётся в false. Таким образом - новое значение timerTickDetected будет утеряно. 

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

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

bwn пишет:

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

А ежели не булеан? А ежели несколько булеан? Фишка в том, что раз мы в loop проверяем чего-то там (необязательно тупые флаги), изменяющееся в прерывании, то мы должны гарантировать, что по центру наших проверок нас не прервёт прерывание и не изменит в этих переменных чего-либо. Отсюда - требование атомарности, т.е. гарантий, что мы в конкретной точке следования работаем с неизменяемыми данными. Проще всего это сделать - быренько выключив прерывания, скопировав данные в локальные переменные, и быренько включив прерывания.

Есть ещё atomic.h - там есть разные макросы. Короче - вот: http://microsin.net/programming/avr/avr-gcc-atomically-and-non-atomically-executed-code-blocks.html

bwn
Offline
Зарегистрирован: 25.08.2014

ОК, благодарствую, осознал.

meridian86
Offline
Зарегистрирован: 09.11.2019

Спасибо за разъяснения! Ушел учиться.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

sadman41 пишет:

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

Ну, если бы мне это сказали авторы данной библиотеки, я бы ответил им словами Л.Н. Толстого из письма Ф.А. Желтову: "До Вас это не относится" :-)

Лев Николаевич - он же поручиком артиллерии был, а артиллеристский офицер - он зря не скажет, посмотрите на нашего Ворота :-)