Проект двухступенчатого водоснабжения загородного дома

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

по сути приведеного тобой кода, IDE-шка ругается сильно

я не знаток С++, сидел всегда в Delphi :) так что я ориентируюсь на компилятор именно ардуинского IDE

ей нужно так: int PROGRAM[NUM]
а не так: int arr[NUM] PROGRAM

а вот это не может выкурить ни IDE ни я

digitalRead(arr[b])

waterSystem:10: error: 'arr' was not declared in this scope
exit status 1
'arr' was not declared in this scope

 

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Angbor пишет:
а в чем разница по сути?

В моем случае без разницы какие номера у входов и следуют ли они по порядку или нет. В случае переопределения входа

#define TOP_SWITCH   10

твой код перестанет работать, а моему фиолетово.

Angbor пишет:
а вот это не понял, можно поподробнее.

4 линии данных, которые идут к D4-D7 индикатора, можно использовать и для записи в регистр светодиодов. Пока ты не дергаешь выводы E, R/W, RS, индикатору все равно, что происходит на шине данных. Выдай на неё данные для светодиодов и используй clockPin или latchPin для записи в регистр светодиодов. У ардуины освободится еще пара ног.

Angbor пишет:
по сути приведеного тобой кода, IDE-шка ругается сильно

Опечатался, надо PROGMEM , а не PROGRAM. Смысл: разместить массив в памяти програм, а не памяти данных.

const int arr[NUM] PROGMEM={BOTTOM_SWITCH, MIDDLE_SWITCH, TOP_SWITCH, OVERFLOW_SWITCH};

 

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

Спасибо, Andy, попозже еще раз попробую с твоим советом про массив пинов.

Про использование дисплея... Как-то не хочется все лепить в кучу. А в чем выгода такого варианта по сравнению с двумя регистрами?

В моем случае я могу еще добавить безболезненно 4 диода. Мне вобще понравились регистры :) Я думал использовать регистр и для поплавковых выключателей. Их сейчас 4, а так можно будет и до 8 использовать на разные нужды.

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

А сейчас вот думаю над куском кода, чтобы не ежемилисекундно тыркать

//=========
const long BLINK_DELAY    = 200;
const long READ_PIN_DELAY = 100;
const long REGISTR_DELAY  = 150;

unsigned long previousMillis = 0;

...
...
...
void loop() {
  unsigned long currentMillis = millis();
  int differences = currentMillis - previousMillis;
  if (differences >= READ_PIN_DELAY/*100*/) {
    pinReading();
  }
  if (differences >= REGISTR_DELAY /*150*/) {
    regWork();
  }
  if (differences >= BLINK_DELAY   /*200*/) {
    blinkS();
    previousMillis = currentMillis;
  }
}

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

Что думаешь?

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Angbor пишет:
если посмотрите на картинку выше - подключил через регистр LCD и релешу - еле еле  вобщем-то

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

Angbor пишет:
Про использование дисплея... Как-то не хочется все лепить в кучу. А в чем выгода такого варианта по сравнению с двумя регистрами?

В моем случае я могу еще добавить безболезненно 4 диода. Мне вобще понравились регистры :)

Это как бы не лепка в кучу, а попытка структурировать схему и код. Взгляни на устройство компа: шина данных (и не только она) проходит по всем устройствам.  Кроме того, запас по ногам надо иметь, любой проект имеет тенденцию к развитию.

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

Angbor пишет:
А сейчас вот думаю над куском кода, чтобы не ежемилисекундно тыркать

У тебя константы размещены в памяти данных, в этом нет необходимости. Сделай так:

unsigned long time, t1=0, t2=0;
#define T1_INTERVAL 50
#define T2_INTERVAL 150
#define TIMER(t) (time - t)
#define CLR_TIMER(t) t=time
void loop()
{
  time=millis();
  if(TIMER(t1) > T1_INTERVAL) {CLR_TIMER(t1);  pinReading();}
  if(TIMER(t2) > T2_INTERVAL) {CLR_TIMER(t2);  regWork();}
  // и т.д.
}

 

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015
#define TIMER(t) (time - t)
#define CLR_TIMER(t) t=time

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

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

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

несколько циклов 0111, потом 1111, потом снова 0111

при этом никакие контакты физически не замыкаются вообще
возвращаю свой код с костылем i-2 и все опять работает как надо...

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Angbor пишет:
я думал раньше, что определение типа define - это указатель на константу

Это команда препроцессору типа replace, перед компиляцией пробежаться по тексту и заменить совпадающие выражения определением.

Как-то не увязывается у меня 16 разрядная переменная sw с тем, что выведено в порт. По логике вещей цикл должен выглядеть так:

for (byte i=0; i<NUM; i++)
 {
   switchState[i]=GET(i);
 }

 

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

так у меня так и выглядит, как у вас.

если я все правильно понимаю, то

switchState[i]=GET(i) это значит присвоить значение, возвращаемое GET(i) в switchState[i] - а это либо 1 (HIGH), либо 0 (LOW)
и поскольку был глюк, я просто выводил в порт значение, возвращаемое этим самым GET(i)

for (byte i=0; i<NUM; i++)
 {
   switchState[i]=GET(i);
 }

я изменил на

Serial.println(sw);
for (byte i=0; i<NUM; i++)
 {
   int sw = GET(i);
  Serial.print(sw);
  delay(200);
 }

и вот собственно возвращается странный набор состояний пинов: 0111, потом спустя несколько циклов 1111, хотя я не замыкаю и не размыкаю никакие кнопки.
если же я возвращаю свой старый код, то все работает

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

Обнаружил, что arr[i] возвращает

arr[0] 6
arr[1] 0
arr[2] 0
arr[3] -256

откуда такие значения? ведь вроде там должно быть 2, 3, 4, 5...
если же я определяю массив так
const int arr[] ={BOTTOM_SWITCH, MIDDLE_SWITCH, TOP_SWITCH, OVERFLOW_SWITCH};
то все работает...
это что же за PROGMEM такой?...

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

В общем-то, переменная int sw объявлена внутри цикла, и возвращает значение только одной ноги. Я ожидал увидеть 16-разрядное значение либо 0 либо -1 либо нечто подобное. Что такое 1111 и 0111 мне не понятно. Добавь в вывод Serial.print(i), Serial.print(arr[i]), Serial.print(digitalRead(arr[i])), что бы понять где проблема.

Цитата:
это что же за PROGMEM такой?
Рекомендуют для PROGMEM

#include <avr/pgmspace.h>

Можешь оставить массив в памяти данных. Это на случай нехватки памяти.

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

ну судя по тому, что с таким объявлением
const int arr[] ={BOTTOM_SWITCH, MIDDLE_SWITCH, TOP_SWITCH, OVERFLOW_SWITCH};
все работает, дело именно в использовании PROGMEM

а что там может быть не так?

вернул PROGMEM, сделал вывод
вот результат, собственно

вместо 2, 3, 4, и 5 там 6, 0, 0 и -256 :/

вот объявление

// определяем пины для переключателей
#define BOTTOM_SWITCH   2
#define MIDDLE_SWITCH   3
#define TOP_SWITCH      4
#define OVERFLOW_SWITCH 5

#define NUM 4
#define GET(b) digitalRead(arr[b])
const int arr[] PROGMEM ={BOTTOM_SWITCH, MIDDLE_SWITCH, TOP_SWITCH, OVERFLOW_SWITCH};

проверил, нигде больше в коде BOTTOM_ и т.д. не переопределяются и не перписываются.

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Цитата:
а что там может быть не так?

Похоже все дело в

#include <avr/pgmspace.h>

Добавь эту строку в начало проекта. С ней должно работать...

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

добавил, не помогло :(
да и ладно, пока ведь все помещается, программа готова на половину, а использовано только 27%
а вот с таймерами работает все прекрасно, спасибо за идею

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

нужна помощь, уже просто голову сломал над тем, как уйти от delay

логика вот. работает но с delay

bool checkPause() {
  time = millis();
  if (TIMER(t4) > PUMP_DELAY * 1000) { // если таймер простоя превышен
    if (!twvIsOpen) {
      // если вентиль не открыт
      digitalWrite(THREE_WAY_VALWE, TWV_ON);
      Serial.println("VALWE OPEN");
      twvIsOpen = true;
      return false;
    }
    if (twvIsOpen) {
      delay(TWV_OPENING);// минуту как бы ждем открытия вентиля
      pumpIsOn = true;
      Serial.println("PUMP OPEN"); // включаем насос
      digitalWrite(PUMP, PUMP_ON);
      delay (TWV_DELAY);// 15 минут как бы прокачиваем
      digitalWrite(THREE_WAY_VALWE, TWV_OFF);
      Serial.println("VALWE CLOSE");
      twvIsOpen = false;
      CLR_TIMER(t4);
      return false;
    }
  }
}
void pumpOnOff (int onOrOff) {
  if ((onOrOff == 1) && !pumpIsOn && checkPause()) {
    digitalWrite(PUMP, PUMP_ON);
    pumpIsOn = true;
  }
  if ((onOrOff == 0) && pumpIsOn) {
    digitalWrite(PUMP, PUMP_OFF);
    CLR_TIMER(t4);
    pumpIsOn = false;
  }
}

 когда начинаю "в лоб" заменять delay и писать if b т.д. просто дебри получаются и постоянно сбиваюсь
может просто сам подход неверный?

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

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Angbor пишет:
когда начинаю "в лоб" заменять delay и писать if b т.д. просто дебри получаются и постоянно сбиваюсь
может просто сам подход неверный?
Подойди к делу так: опиши все состояния твоей системы, например:

0-исходное, все реле выключены.

1-закачка воды в бак, включено реле 1.

2-слив воды перед наполнением бака, включено реле 2.

3-аварийное состояние...

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

0->1 нажатие кнопки 1 или средний датчик уровня =0, включить реле 1, включить светодиод 1.

0->2 истек 3 часовой таймер, запустить 15 минутный таймер.

2->1 истек 15 минутный таймер, включить реле 1, включить светодиод 2.

1->0 верхний датчик уровня =1, выключить реле 1, запустить 3х часовой таймер.

1->3 датчики уровня в странном состоянии, выключить все реле, включить светодиод "авария".

Теперь пример код:

byte status=0; //состояние твоей системы
switch (status)
{
  case 0: if(кнопка или уровень_воды_1) {вкл_реле(1); светодиод(1); status=1; break;}
          if(истек_таймер_3ч) {пуск_таймера_15мин(); status=2; break;}
          break;
  case 1: if(уровень_воды_2){выкл_реле(1); светодиод(2); пуск_таймера_3ч; status=0;} break;
  case 2: и т.д.
}

 

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

спасибо огромное!
вчера вечером на этой же мысли остановился - описать состояния системы. сегодня днем попробую, отпишусь

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Для наглядности нарисуй граф состояний, нечто вроде этого:

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

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

Смешно конечно, и банально. Но сложность кода превысила порог просто "с наскока". Так что погрузился в чтение некоторой литературы :)

В частности меня интересует вопрос прерываний и таймеров.

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

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

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Angbor пишет:

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

Нет смысла. Периодичность изменения сигналов от поплавков измеряется часами и минутами. Прерывания разумно использовать, когда сигнал меняется быстрее, чем основной цикл. Хотя, если цель "сделать не как у всех" , то можно и по прерываниям :)

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

:)

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

а потом прочел фразу "если 10 событий из 10 важные, то ни одно из 10 не важно"... и понял, что прерывания мне точно не нужны, а вот с таймером поигрался немного. в данном случае проверка раз в милисекунду

class WaterTimer
{
  private:
    long timerLength; // длительность таймера
    bool timerState;  // Текущее состояние On или Off
    unsigned long previousMillis;

  public:
    WaterTimer(long tLength)
    {
      timerLength = tLength;
      timerState = false;
      previousMillis = 0;
    }
    void setTimerState(bool stateChang) {
      timerState = stateChang;
    }

    bool getTimerState(unsigned long currentMillis)
    {
      if ((timerState) && (currentMillis - previousMillis >= timerLength))
      {
        previousMillis = currentMillis;
        return true;
      }
      else {
        return false;
      }
    }
};

WaterTimer timer1(1000);
WaterTimer timer2(500);
WaterTimer timer3(800);

void setup()
{
  Serial.begin(9600);
  OCR0A = 0xAF;
  TIMSK0 |= _BV(OCIE0A);
}

SIGNAL(TIMER0_COMPA_vect)
{
  unsigned long currentMillis = millis();
  if (timer1.getTimerState(currentMillis)) {
    Serial.println("TIMER 3H");
  }
  if (timer2.getTimerState(currentMillis)) {
    Serial.println("TIMER 15M");
  }
  if (timer3.getTimerState(currentMillis)) {
    Serial.println("TIMER 1M");
  }
}

void loop()
{
  delay(1000);
  Serial.println("ALL is ON");
  timer1.setTimerState(true);
  timer2.setTimerState(true);
  timer3.setTimerState(true);
  delay(10000);
  timer1.setTimerState(false);
  timer2.setTimerState(false);
  timer3.setTimerState(false);
  Serial.println("ALL is OFF");
  delay(3000);
}

любопытно, учитывая у меня три таймера в проекте, вполне вариант. пока не вижу подвожных камней

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

значит С++ не пугает.... :)

Angbor пишет:
пока не вижу подводных камней
Есть один, class WaterTimer надо несколько изменить, метод setTimerStstate() перезапускает таймер только если он истек, т.е. если вызвать метод setTimerStstate(true) за минуту до того, как истечет 3-х часовой таймер, то событие от таймера произойдет через минуту, а не через 3 часа.

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

В предложенном коде есть еще одна засада, которая рано или поздно приведет к сбою или зависанию программы - рекурсия функции Serial.print((). Функция вызывается и в основном цикле и в прерывании. Нужно либо избегать доступа к одному и тому же объекту и из основного цикла и из прерывания, либо запрещать прерывания на время доступа из основного цикла.

Правило хорошего тона при программировании - минимум в прерываниях, максимум в основном цикле.

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

Andy пишет:
значит С++ не пугает.... :)
ну Delphi меня не напугал, и С++ не страшнее. Вот asm я несколько раз пробовал осилить, но за неимением серьезной мотивации и области применения каждый раз забрасывал.

Angbor пишет:
Есть один, class WaterTimer надо несколько изменить, метод setTimerStstate() перезапускает таймер только если он истек, т.е. если вызвать метод setTimerStstate(true) за минуту до того, как истечет 3-х часовой таймер, то событие от таймера произойдет через минуту, а не через 3 часа.
а и правда что...

на самом деле я понял, что get и set в данном случае неправильные идеи - нужно ON и OFF со сбросом при каждом OFF.

вот сделал примерный прикид по состояниям

T3H - трехчасовой таймер, T15M - 15-ти минутный и т.д.

3W - трехходовой вентиль

всего 4 основных состояния + 2 аварийных

 

Что касается serial.print - то это я просто для отладки временно воткнул

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

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

а вот на чем я просто сломал голову, так это над тем, как в конструктор класса передать указатель на функцию... уже просто крыша отъезжает :)

public:
    WaterTimer(long tLength, void (*whatToDo)(void));

и потом

WaterTimer :: WaterTimer(long tLength, void (*whatToDo)(void))
    {
      timerLength = tLength;
      timerState = false;
      previousMillis = 0;
    }

но теперь если я указываю

WaterTimer timer1(300, &blinkS());

получаю сообщение, что нету такого blinkS()...

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Angbor пишет:
получаю сообщение, что нету такого blinkS().

blinkS() должна быть объявлена раньше, чем её вызов.

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

хм...

не понимаю. А почему тогда

SIGNAL(TIMER0_COMPA_vect)
{
  unsigned long currentMillis = millis();
   
  if (timer1.getTimerState(currentMillis)) {
    blinkS();
  }
}

срабатывает без проблем, хотя эта самая функция blinkS() находится сильно после установки этого прерывания по таймеру

 

а если я начинаю двигать объявления туда-сюда, то

void blinkS() {
  // мигаем
  for (int i = 0; i < 4 ; i++) {
    if ((blinkF[i] == 1) && (switchState[i]) == 1) {
      ledsBurn = ledsBurn ^ ledS[i];
    }
  }
}

WaterTimer timer1(300, &blinkS());
timer1.setTimerState(true);
// Прерывание вызывается один раз в миллисекунду,
// ищет любые новые данные, и сохраняет их
SIGNAL(TIMER0_COMPA_vect)
{
  unsigned long currentMillis = millis();

  if (timer1.getTimerState(currentMillis)) {
    blinkS();
  }
}

приводит к

waterSystem_with_timers:193: error: lvalue required as unary '&' operand
waterSystem_with_timers:194: error: 'timer1' does not name a type

exit status 1
lvalue required as unary '&' operand

может я просто не так передаю указатель на функцию?
 

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

Короче, вот что получилось. Это работает. Но порассуждаю, потому что не понимаю в полной мере, почему это работает, а это не гуд.

    void (*ptrBlinkSinClass)();
    WaterTimer(long tLength, void f())
    {
      ptrBlinkSinClass = f;
      timerLength = tLength;
      timerState = false;
      previousMillis = 0;
    }

т.е. я объявил указатель для функции, возвращабщей void и не принимающей параметры, потом в конструктор добавил второй параметр void f()

в конструкторе указателю присвоил то, что получил из аргумента

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

далее

    bool getTimerState(unsigned long currentMillis)
    {
      if ((timerState) && (currentMillis - previousMillis >= timerLength))
      {
        ptrBlinkSinClass();
        previousMillis = currentMillis;
        return true;
      }
      else {
        return false;
      }
    }

ну тут просто добавил  запуска функции через указатель на нее

// прототипы функций
void blinkS();
void (*ptrBlinkS)() = blinkS;
// -----------------------------
WaterTimer timer1(300, ptrBlinkS);

добавил прототип функции, установил указатель на "блинк" и передал в параметр.

т.е. основной вопрос, объявив функцию в которой аргумент выглядит как void f() - я передаю указатель? если да, то почему это так?

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

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

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

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

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

// -----------------------------
WaterTimer timer1(300, blinkS);

вместо того как было (с у казателем)

void (*ptrBlinkS)() = blinkS;
// -----------------------------
WaterTimer timer1(300, ptrBlinkS);

и получил: waterSystem_with_timers:71: error: invalid use of void expression

 

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016
void blinkS();
void (*ptrBlinkS)() = blinkS;
// -----------------------------
timer1(300, ptrBlinkS);
timer1(300, (void) blinkS);

Оба вызова timer1 равнозначны

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

на второе объвление компилятор все равно ругается :(

error: invalid use of void expression

 

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

А так не ругается: timer1(300, blinkS);

Разные компиляторы по разному компиляют.

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

ругань наступает на любой из четырех вариантов :(

 timer1(300, (void) blinkS);
 timer1(300, blinkS);
 timer1(300, (void) blinkS());
 timer1(300, blinkS());

мне кажется, что это не только из-за объявления экземпляра класса, а скорее из-за конструктора

WaterTimer(long tLength, void f())

видимо для такого варианта компилятор требует именно формат указателя.

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

при этом компилятором принимается только такой вариант объявления аргумента для указателя на функцию в конструкторе

если указывать любые другие варианты типа (void) (*f)() и любые другие комбинации - не робит
я думаю, это уже не принципиально, как и сложности с PROGMEM, пусть так, а то кучу время потрачу на  разбирательства с глючным ARDUINO IDE :)

проект закончу, посмотрю в сторону Code::Blocks

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Специально скачал ардуино IDE, что бы проверить

void timer1(int i, void f())
{
}

void blinkS(){};
void (*ptrBlinkS)() = blinkS;
// -----------------------------
void loop() 
{
 timer1(300, ptrBlinkS);
 timer1(300, blinkS);
}

компиляется без ошибок (Arduino 1.6.7)

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

да, если размещать объявление экземпляра класса в loop

но если поместить это до loop и до setup то возникают проблемы

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Не пойму в чем проблема, так тоже компиляется.

void timer1(int i, void f());
void (*ptrBlinkS)() = blinkS;
void myfunc()
{
 timer1(300, ptrBlinkS);
 timer1(300, blinkS);
}

void timer1(int i, void f())
{
}
void blinkS(){};
// -----------------------------
void setup()
{
}
void loop() 
{
}

Выложи проблемный код.

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015
class WaterTimer
{
  private:
    long timerLength; // длительность таймера
    bool timerState;  // Текущее состояние On или Off
    unsigned long previousMillis;

  public:
    String timerName;
    void (*ptrOnFuniction)();
    WaterTimer(long tLength, void f())
    {
      ptrOnFuniction = f;
      timerLength = tLength;
      timerState = false;
      previousMillis = 0;
    }
    void timerOn() {
      timerState = true;
    }

    void timerOff() {
      timerState = false;
      previousMillis = 0;
    }

    bool getTimerState(unsigned long currentMillis)
    {
      if ((timerState) && (currentMillis - previousMillis >= timerLength))
      {
        ptrOnFuniction();
        previousMillis = currentMillis;
        return true;
      }
      else {
        return false;
      }
    }
};

#define READ_PIN_DELAY 100
#define REGISTR_DELAY  150
#define BLINK_DELAY    300

void blinkS();
void pinReading();
void regWork();
void (*ptrBlinkS)() = blinkS;
void (*ptrPinReading)() = pinReading;
void (*ptrRegWork)() = regWork;

WaterTimer timerBlink(BLINK_DELAY, ptrBlinkS);
WaterTimer timerPinRead(READ_PIN_DELAY, ptrPinReading);
WaterTimer timerRegistrDelay(REGISTR_DELAY, ptrRegWork);

void setup() {
  {
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
  }
  timerBlink.timerOn();
  timerPinRead.timerOn();
  timerRegistrDelay.timerOn();
}

SIGNAL(TIMER0_COMPA_vect)
{
  unsigned long currentMillis = millis();

  timerBlink.getTimerState(currentMillis);
  timerPinRead.getTimerState(currentMillis);
  timerRegistrDelay.getTimerState(currentMillis);
}

void loop() {
}

я опустил не относящиеся моменты и сами функции. Оставил только прототипы. Но в коде, который я запускаю, понятное дело, это все присутствует.

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

первое отличие, которое я вижу между твоим вариантом и моим, у меня это коструктор без void, у тебя же это функция, возвращающая void и находящаяся вне класса.

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Взял твой код и в строках 52-54 заменил указатели на имена функций.

class WaterTimer
{
  private:
    long timerLength; // длительность таймера
    bool timerState;  // Текущее состояние On или Off
    unsigned long previousMillis;

  public:
    String timerName;
    void (*ptrOnFuniction)();
    WaterTimer(long tLength, void f())
    {
      ptrOnFuniction = f;
      timerLength = tLength;
      timerState = false;
      previousMillis = 0;
    }
    void timerOn() {
      timerState = true;
    }

    void timerOff() {
      timerState = false;
      previousMillis = 0;
    }

    bool getTimerState(unsigned long currentMillis)
    {
      if ((timerState) && (currentMillis - previousMillis >= timerLength))
      {
        ptrOnFuniction();
        previousMillis = currentMillis;
        return true;
      }
      else {
        return false;
      }
    }
};

#define READ_PIN_DELAY 100
#define REGISTR_DELAY  150
#define BLINK_DELAY    300

void blinkS();
void pinReading();
void regWork();
void (*ptrBlinkS)() = blinkS;
void (*ptrPinReading)() = pinReading;
void (*ptrRegWork)() = regWork;

//WaterTimer timerBlink(BLINK_DELAY, ptrBlinkS);
WaterTimer timerBlink(BLINK_DELAY, blinkS);
//WaterTimer timerPinRead(READ_PIN_DELAY, ptrPinReading);
WaterTimer timerPinRead(READ_PIN_DELAY, pinReading);
//WaterTimer timerRegistrDelay(REGISTR_DELAY, ptrRegWork);
WaterTimer timerRegistrDelay(REGISTR_DELAY, regWork);

void setup() {
  {
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
  }
  timerBlink.timerOn();
  timerPinRead.timerOn();
  timerRegistrDelay.timerOn();
}

SIGNAL(TIMER0_COMPA_vect)
{
  unsigned long currentMillis = millis();

  timerBlink.getTimerState(currentMillis);
  timerPinRead.getTimerState(currentMillis);
  timerRegistrDelay.getTimerState(currentMillis);
}

void loop() {
}

void blinkS(){};
void pinReading(){};
void regWork(){};

 

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

и работает?

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

На счет работает не знаю, но компиляется без ошибок.

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

загадка...

спасибо за участие в процессе, очень поддерживает

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

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

То ли пропробовать доделать в первичной логике, то ли оставить и начать по-другому...

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Давай распишем твою диаграмму, а то мне не все понятно.

Состояние 0: выключены все реле.

Состояние 1: включено реле насоса.

Состояние 2: включение трехходового клапана.

Состояние 3: включен трехходовый клапан и реле насоса.

Состояние 4А: ошибка неверная комбинация датчиков.

Состояние 4Б: ошибка перелив воды.

Условия переходов между состояниями и действия:

0->1 уровень воды ниже среднего датчика и таймер 3ч не истек - включить реле насоса.

0->2 уровень воды ниже среднего датчика и истек таймер 3ч - подать напряжение на трехходовый клапан, запустить таймер 1 мин.

1->0 уровень воды достиг верхнего датчика - отключить насос, запустить таймер 3ч.

2->3 истек таймер 1 мин - включить реле насоса, запустить таймер 15 мин.

3->1 истек таймер 15 мин - снять напряжение с трехходового клапана.

из всех состояний ->4A неверная комбинация датчиков - выключить всё, индикация ошибки.

2->4Б перелив воды - выключить всё, индикация ошибки.

4А->0 ручное вмешательство? - выкл индикатор ошибки.

4Б->0 ручное вмешательство? - выкл индикатор ошибки.

При переходе в состояния 4А и 4Б по идее надо запускать 3 часовой таймер.

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

Ну вроде все так, я вот переписал сам, вроде аналогично

СОСТОЯНИЯ
0: выключены все реле.
1: включено реле насоса.
2: включен трехходовой клапан
3: включены все реле
4А: ошибка неверная комбинация датчиков.
4Б: ошибка перелив воды.

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

Датчики размещены соответственно схеме:

датчик перелива

датчик полного бака

датчик половины бака

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

0–>1  (бак пустой полностью || уровень ниже половины) && 3ч не истек
           останавливаем 3ч таймер
           включаем насос

0–>2  (бак пустой полностью || уровень ниже половины) && 3ч истек
           останавливаем 3ч таймер
           открываем клапан
           запускам таймер 1м

2–>3 истек таймер 1м
            остановить таймер 1м
            запустить таймер 15м
            включаем насос

3–>1 истек таймер 15м
            остановить таймер 15м
            закрываем клапан

1–>0 бак полный
            останавливаем насос
            включаем таймер 3ч

0,1,2,3–>4А  неверная комбинация датчиков
           включаем таймер 3ч
           все выключить
           индикация ошибки

0,1,2,3–>4Б  перелив
           включаем таймер 3ч
           все выключить
           индикация ошибки

Переход из 4А и 4Б по идее произойдет автоматически после устранения неполадок датчиков и/или перелива.

Описать какая схема действует сейчас?

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

замечательно, теперь перекладываем в код

byte st=0;//состояние 
switch (st)
{
case 0: if ((бак пустой полностью || уровень ниже половины) && 3ч не истек)
        {st=1; останавливаем 3ч таймер; включаем насос;}
        if ((бак пустой полностью || уровень ниже половины) && 3ч истек)
        {st=2; останавливаем 3ч таймер;  открываем клапан; запускам таймер 1м;}
        if(неверная комбинация датчиков){st=4A;}
        if( перелив){st=4Б;}
        break;
case 1: if(бак полный){st=0; останавливаем насос;  включаем таймер 3ч;} break;
case 2:.....
}

хотя неверную комбинацию датчиков и перелив можно вынести из switch

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

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

Так что сохранил все это дело и начал с чистого листа :) Сейчас поробую с твоим вариантом с case

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

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

/*
  команды для case
  1 - пустой бак и таймер простоя не превышен
  2 - пустой бак и таймер простоя превышен
  3 - полный бак
  4 - истек таймер 1М
  5 - истек 15М

  8 - неправильные комбинации
  9 - переполнение
  0 - сброс
*/

void setup() {
  Serial.begin(9600);
  while (!Serial) {
  }
}

void test(byte inInt) {
  static byte st = 0;
  switch (st)
  {
    case 0: if (inInt == 1/*(бак пустой полностью || уровень ниже половины) && 3ч не истек*/)
      {
        st = 1;
        Serial.print("PUMP. st = ");
        Serial.println (st);
        //останавливаем 3ч таймер;
        //включаем насос;
      }
      if (inInt == 2/*(бак пустой полностью || уровень ниже половины) && 3ч истек*/)
      {
        st = 2;
        Serial.print("3W opening. st = ");
        Serial.println (st);
        // останавливаем 3ч таймер;
        // открываем клапан;
        // запускам таймер 1м;
      }
      if (inInt == 8/*неверная комбинация датчиков*/) {
        st = 4;
        Serial.print("WRONG COMB. st = ");
        Serial.println (st);
        // останавливаем таймеры 1М и 15М
        // останавливаем насос
        // закрываем клапан
      }
      if (inInt == 9/* перелив*/) {
        st = 5;
        Serial.print("OVERFLOW. st = ");
        Serial.println (st);
        // останавливаем таймеры 1М и 15М
        // останавливаем насос
        // закрываем клапан
      }
      break;
    //==============================================
    //==============================================
    case 1: if (inInt == 3/*бак полный*/) {
        st = 0;
        Serial.print("FULL. st = ");
        Serial.println (st);
        // останавливаем насос;
        // включаем таймер 3ч;
      }
      if (inInt == 8/*неверная комбинация датчиков*/) {
        st = 4;
        Serial.print("WRONG COMB. st = ");
        Serial.println (st);
        // останавливаем таймеры 1М и 15М
        // останавливаем насос
        // закрываем клапан
      }
      if (inInt == 9/* перелив*/) {
        st = 5;
        Serial.print("OVERFLOW. st = ");
        Serial.println (st);
        // останавливаем таймеры 1М и 15М
        // останавливаем насос
        // закрываем клапан
      }
      break;
    //==============================================
    //==============================================
    case 2: if (inInt == 4/*таймер 1М истек*/) {
        st = 3;
        Serial.print("3w is open start PUMP OUT. st = ");
        Serial.println (st);
        // останавливаем таймер 1М
        // включаем таймер 15М
        // включаем насос
      }
      if (inInt == 8/*неверная комбинация датчиков*/) {
        st = 4;
        Serial.print("WRONG COMB. st = ");
        Serial.println (st);
        // останавливаем таймеры 1М и 15М
        // останавливаем насос
        // закрываем клапан
      }
      if (inInt == 9/* перелив*/) {
        st = 5;
        Serial.print("OVERFLOW. st = ");
        Serial.println (st);
        // останавливаем таймеры 1М и 15М
        // останавливаем насос
        // закрываем клапан
      }
      break;
    //==============================================
    //==============================================
    case 3: if (inInt == 5/*таймер 15м истек*/) {
        st = 1;
        Serial.print("3w close start pump IN. st = ");
        Serial.println (st);
        // закрываем клапан
        // включаем таймер 3ч;
      }
      if (inInt == 8/*неверная комбинация датчиков*/) {
        st = 4;
        Serial.print("WRONG COMB. st = ");
        Serial.println (st);
        // останавливаем таймеры 1М и 15М
        // останавливаем насос
        // закрываем клапан
      }
      if (inInt == 9/* перелив*/) {
        st = 5;
        Serial.print("OVERFLOW. st = ");
        Serial.println (st);
        // останавливаем таймеры 1М и 15М
        // останавливаем насос
        // закрываем клапан
      }
      break;
    //==============================================
    //==============================================
    case 4: if (inInt == 0/*сброс*/) {
        st = 0;
        Serial.print("DROP. st = ");
        Serial.println (st);
        // просто переход в обчныое состояние
      }
      break;
    //==============================================
    //==============================================
    case 5: if (inInt == 0/*сброс*/) {
        st = 0;
        Serial.print("DROP. st = ");
        Serial.println (st);
        // просто переход в обчныое состояние
      }
      break;
  }
}

void loop() {
  while (Serial.available() > 0) {
    byte sensors = Serial.parseInt();
    if (Serial.read() == '\n') {
      test(sensors);
    }
  }
}

действительно, обработка аварийных ситуаций напрашивается куда-нибудь отдельно, ну или в функцию вынести.

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

столкнулся с неожиданым для "абстрактного" программера на Delphi казусом.

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

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

упс.. таки-я их (4N35) пожег... При 5 вольт надо 370 Ом.

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

что-то я в тупике очередном :)

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

При этом я не могу никак отловить место, где и кто таймер 3-х часовой выключает...