Ах, эти страшные, сложные, милые, простые классы.

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

Клапауций 089 пишет:

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

Ты, в своей категоричности, тоже традиционен, и порой - без смысла ;) Отказ чего-либо принимать/понимать - не значит бессмысленность этого ;) Про первичный смысл разделения на заголовочные файлы и файлы с реализацией - я уже писал, если ты не заметил. Первичный смысл - это удобный и быстрый доступ к API, которое предоставляет тот или иной набор исходников: заметь, на несколько *.cpp-файлов может быть всего один *.h-файл ;)

Для меня (да и не для меня одного) - гораздо удобнее взглянуть на объявления, чтобы понять, как работать с этим добром. А уже при необходимости - в исходнички залезать. С#, кстати, отсутствием объявлений и вымораживает - в большой простыне хер соберёшь в кучку описание функционала, предоставленного исходником.

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

DIYMan пишет:

Ты, в своей категоричности, тоже традиционен, и порой - без смысла ;) Отказ чего-либо принимать/понимать - не значит бессмысленность этого ;)

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

*пример из жизни: чел - отличник по физике, победитель республиканских олимпиад по физике купил аудиокабеля для своей аппаратуры по 100$ за погонный метр. когда я ему сказал, что он-дыбил и у него по физике в школе была твёрдая тупая тройка - оказалось, что я жестоко неправ. когда ему на на его глазах за полчаса спаяли кабель себестоимостью 10$, то на слепом аудио-тесте - он оказался жестоко не прав. и, мы оба задумались.

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

Клапауций 089 пишет:

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

Вот тут-то как раз я с тобой согласен на 100500%.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

DetSimen пишет:

... но от случайных помех или флюктуаций самого датчика, считанные и отданные значения могут колебаться. Бывает, датчик читает-читает значение 200, а потом ба-бах, в момент чтения приходит по шине случайная иголка, значение подскакивает до 800, сирена включается и наступает время брать под мышку сонного кота и лихорадочно думать "Куды бечь?". Чтоб этого не случалось, я всегда читаю пин несколько раз и усредняю полученные значения. Читаю ровно степеньдвойки раз, конкретно 8 или 16, чтоб среднее значение получалось простым сдвигом вправо на 3 или 4 бита.
Увы, предложенный метод не решает поставленной проблемы. От слова совсем.
Ну прочитали мы пин 8 раз, ну получили 7 раз 200 и один раз 800, ну вычислили среднее, в данном случае 275, разве это то, что нам нужно?
В условиях возможности возникновения явно "левых" значений, нужно не усреднять, а брать медиану: прочитали порт 3, 5 или 7 раз, отсортировали и вывели результат из серединки.
PS. Сортировать - пузырьком.
Logik
Offline
Зарегистрирован: 05.08.2014

andriano пишет:

В условиях возможности возникновения явно "левых" значений, нужно не усреднять, а брать медиану: 

Очень не всегда так. Зависит от распределения вероятностей. Общеприменимое (когда думать не хочется и исходных данных нет;) скорей статобработка - считаем СКО, три сигмы, отбрасываем что невписалось и снова считаем. Это для нормального распределения.  Для каждого  конкретного случая свой подход. Иногда и по первому большому значению нужно хватать кота и бежать, т.к. до конца цикла обработки уже некому будет решение принимать. (Пример - контролируем напряжение в цепи постоянного тока на предмет аварии при превышении порога допустим 24В, решили брать некое среднее, но в цепь попало 220В 50Гц, среднее 0, защита не сработала. А работали бы по мгновенному напряжении, то сработала бы.).  Медианное среднее - один из вариантов, а их вобще много. Добавлю для расширения кругозора еще и геометрическое среднее и фильтр Кальмана.

ПС. Когда данные поступают поочередно и с интервалами, лучше сортировать их по мере поступления, поддерживая массив отсортированым все время.

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

Ну вот как с вами трезвому жить?  

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

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

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

DetSimen пишет:

Ну вот как с вами трезвому жить?  

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

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

так, нафига ты написал про инклюды кпп и ха, если должен был писать про классы?, классы, Карл!

классы для дуино и детей - это вот это:

class test {};

void setup() {}

void loop() {}

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

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

Клапа, не злись, мы тя сиравно любим.  Описание класса мне куда-то пхать надо?  В проект? Дак нет, я щитаю надо учится сызмальства парадигме, что каждому классу - свое укрытие.  Иначе, несчастный .ino файл догонит и перегонит Войну и Мир по размерам. 

 

В след. серии будем размахивать Ржавой Секирой Ужоса, то есть millis()-а над головами незадачливых пользователей. 

 

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

DetSimen пишет:

В проект?

шозанах - проект?

DetSimen пишет:

каждому классу - свое укрытие.

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

DetSimen пишет:

Иначе, несчастный .ino файл догонит и перегонит Войну и Мир по размерам. 

приехали. а, что, по твоему, инклюд файла класса делает, если не дописывает текст кода в ino перед компилляцией? ты кого собрался обмануть малым объёмом кода ino - детей или компиллятор?

дети будут понимать, почему 10 строк в ino не помещаются в контроллер.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017
Пока на улице мороз, работать уже лень, а квасить еще рано, продолжим. 
 
В этом безумном мире всегда найдутся очень простые люди, которых 3 раза из ПТУ выгоняли за неуспеваемость и которые прям в loop() напишут:
 
void loop()
{
   int i = MyPhoto.Read();
}
И будут читать значение с этого несчастного датчика несколько тысяч раз в секунду. В какой-то мере и данный подход оправдан, чо париться-то лишний раз в этом сложном мире, да вот (бида!, бида!), не все датчики допускают такое вольное с собой обращение. Хорошо, если он просто будет отдавать данные с потолка, а если возьмет да и подохнет? И что мы потом скажем над гробом безвременно почившего даччика? Что покойный жил в лупе, вел беспорядочную жизнь и часто болел? Потому и молодым и помер, не успев толком обжиться в доме?  Да что не скажи, а перспектива безрадостная.  Не хочется закапывать наши деньги в помойке, надо придумать механизм, чтоб не опрашивать датчик слишком часто. Люди которых не выгоняли из ПТУ скажут, а давайте читать датчик только если прошло достаточное количество времени, ну и они-таки будут правы. А чем нам померить время? А нам на помощь придёт функция millis().  Эта функция, как знатный бухгалтер, скурпулёзно подсчитывает, сколько миллисекунд проработал процессор с момента включения (наерна, чтоб знать, когда настанет пора начислять ему пенсию). Мы можем вызвать millis() один раз, потом другой и по разности полученных значений примерно-точно сказать, сколько миллисекунд программа проработала между вызовами. Значение, которое отдает millis() имеет тип unsigned long int, занимает целых 4 байта в памяти, зато может хранить значения от 0 до чуть больше 4 миллиардов. 4 лярда миллисекунд, в переводе на русский, это почти 50 суток, у кого папа калькулятор не отбрал могут сами проверить. Кто собирается читать датчик реже чем раз в 50 суток - дальше не читайте, там для вас ничего не написано. 
 
Можно, канеш, и самим в loop() проверять интервал чтения датчика, как то так
 
unsigned long previous = 0;
unsigned long interval = 2000;  // 2 секунды


void loop()
{
   unsigned long now = millis(); 
   int value;

   if (now - previous > interval)
   {
    value = MyPhoto.Read();
    previous = now;
   }
}
работать будет и так, мы добились того, чтоб наш датчик не истёрся до дыр от частого использования, но как то это некрасиво. Для чего тогда класс городить? Мы и в if-е тогда analogRead() можем вызвать. Класс должен же ведь упрощать жизнь, а не делать ее сложнее. Мошт, пусть он сам решает, часто его читают или нет? Ну а почему бы и нет?  Надо сделать так, чтобы класс помнил, когда он опрашивал датчик последний раз, и если время между вызовами Read() меньше заданного интервала, то можно просто отдать последнее, прочитанное ранее значение, а если времени прошло больше - то снова опросить датчик и запомнить. Для этого нам и понадобится-то совсем немного. 
 
Поле для запоминания последнего значения, считанного с pin-а, оно должно быть скрыто, нехрен пользователю в него лезть, мы сами его отдавать будем, когда надо. Назовем его int fvalue. 
 
Поле для запоминания времени последнего реального чтения датчика. Оно тоже должно быть скрыто, ведь никому, кроме самого класса это неинтересно. Назовем его unsigned long flasttime.
 
Не бойтесь, кстати, давать переменным длинные осмысленные имена (хотя, если с английским туго, о какой осмысленности речь?) в эпоху intellisence набирать их ну чуть помедленнее, канешна, чем i, но код читается потом вами же не в пример легче. А если придется через год переделывать прошивку? Немногие вспомнят, что означает переменная v1 или s2 в своей же собственной программе, посмотрят-посмотрят на текст, как новые ворота на стаю баранов, почешут репу "Какой идиот это писал?", да пойдут с друзьями пиво пить. А с осмысленными именами даже через пять лет жизнь будет существенно проще. (там будет другая проблема, вспомнить лагаритм и поржать над собой, неучем, пятилетней давности).
 
Кстати. В конструкторе можно присвоить начальные нулевые значения этим полям. Можно этого и не делать, нулями они и сами заполнятся при создании класса, это гарантировано стандартом, но мой вам совет, не полагайтесь на стандарт, присваивайте начальные значения полям сами.  Стандарты С++ придумывают, как мне кажется, не очень адекватные люди, и я даже не удивлюсь, если узнаю, что все они содержатся в одной клинике на попечении государства(слава богу, не нашего). А завтра из этой психушки выйдет другой стандарт, предписывающий заполнять все поля единицами при создании класса, или пхать туда случайные числа, и что тогда?  Вы должны чётко понимать в каком состоянии находится ваш класс после создания, поэтому вся инициализация - только сами, и не лениться.  Не нравица - вот лопата, вот обьявление "Требуется кочегар", вам туда.
 
// --------------------------
// .h файл

#pragma once
#include "Arduino.h"

class TSensor 
{
 protected:
  byte fpin;    // поле "номер пина" типа байт, скрытый от пользователя
  int  fvalue; // последнее считанное с pin-а значение
  unsigned long flasttime; // время последнего обращения к датчику 

 public:
  TSensor(byte apin);               // конструктор, принимающий только номер пина
  TSensor(byte apin, bool apullup); // а этот еще и включает или нет подтягивающий резистор на pin

  int Read();     // читать данные с сенсора 
}

// --------------------------
// капец .h файла


//-------------------------------------
// срр файл

#include "TSensor.h"    // перво-наперво подключаем свой же файл определений
                        // чтоб знать, реализацию чего мы пишем
                        // срр файл мы не включаем в проект через #include, 
                        // поэтому #pragma once писать не надо

// Реализация нашего класса. 

// Конструктор. В конструкторе мы просто вызываем другой конструктор, более общий

TSensor::TSensor(byte apin): TSensor(apin, false) {}


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

TSensor::TSensor(byte apin, bool apullup)
{
 fpin = apin;
 if (apullup) pinMode(fpin,INPUT_PULLUP); else pinMode(fpin, INPUT);
 fvalue = 0;    // инициализация ручками
 flasttime = 0;   
}

int TSensor::Read(void)
{

  unsigned long now = millis();  // прочитаем время на сейчас

// Если с момента последнего обращения прошло меньше 2 секунд, просто отдадим
// наружу, то что прочитали последний раз, не дергая пин лишний раз

  if (now - flasttime < 2000) return fvalue;

// Ну а уж если времени прошло больше, куда деваца, надо читать

  int result = 0;

  for (byte i=0; i<16; i++) result += analogRead(fpin);

  flasttime = now; // запомним время, когда мы последний раз читали 
  fvalue    = result >> 4; // и запомним, что прочитали при этом

  return fvalue; // ну и то, что прочитали, то и отдадим.
}

// капец cpp файла
//--------------------------------------

 

 
Теперь можно в loop() вставлять чтение нашего датчика хоть как и хоть куда, все равно реально читаться значение с pin-а будет раз в 2 секунды, это намайна, нас не посадят за жестокое обращение с даччиками, да и сам он не устанет и не будет просица на пенсию по здоровью. 
Кстати, а откуда взялись эти 2 секунды? Нам чо, судом присудили опрашивать датчик именно через 2 секунды?  А если надо читать быстрее? А если по даташиту датчик нада опрашивать не чаще раза в 5 секунд, а у нас жёска настроено на 2? Что же делать-то? Да просто надо завести еще одно поле, обозвать его ReadInterval и дать возможность пользователю класса менять его значение. Так как оно должно быть видимо снаружи, то обьявить его надо в секции public, ну а в конструкторе присвоить ему некое начальное значение по умолчанию, ни большое, ни маленькое, да те же 2 секунды. Для большинства датчиков нет никакой необходимости читаться чаще раза в две секунды, а те, кто хочут, легко могут интервал изменить прямо после создания класса и лезть в исходник для этого не придётся. Платой за это будут лишних 4 байта на каждый датчик, которые придётся со слезами выкраивать из оперативки. Но, слава богу у Дуни всего 6 аналоговых пинов, много датчиков не подключишь, поэтому 24 байта за возможность индивидуальной настройки чтения каждого датчика, я думаю, это нормально. Падытожым. 
 
// --------------------------
// .h файл

#pragma once
#include "Arduino.h"

class TSensor 
{
 protected:
  byte fpin;    // поле "номер пина" типа байт, скрытый от пользователя
  int  fvalue; // последнее считанное с pin-а значение
  unsigned long flasttime; // время последнего обращения к датчику 

 public:
  unsigned long ReadInterval;       // частота реального чтения даччика в миллисекундах

  TSensor(byte apin);               // конструктор, принимающий только номер пина
  TSensor(byte apin, bool apullup); // а этот еще и включает или нет подтягивающий резистор на pin

  int Read();     // читать данные с сенсора 
}

// --------------------------
// капец .h файла

//-------------------------------------
// срр файл

#include "TSensor.h"    // перво-наперво подключаем свой же файл определений
                // чтоб знать, реализацию чего мы пишем
                 // срр файл мы не включаем в проект через #include, 
                 // поэтому #pragma once писать не надо

// Реализация нашего класса. 

// Конструктор. В конструкторе мы просто вызываем другой конструктор, более общий

TSensor::TSensor(byte apin): TSensor(apin, false) {}


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

TSensor::TSensor(byte apin, bool apullup)
{
 fpin = apin;
 if (apullup) pinMode(fpin,INPUT_PULLUP); else pinMode(fpin, INPUT);
 fvalue = 0;
 flasttime = 0;   
 ReadInterval = 2000;     // по умолчанию чтение с pin-а раз в две секунды
}

int TSensor::Read(void)
{

  unsigned long now = millis();  // прочитаем время на сейчас

// Если с момента последнего обращения прошло меньше ReadInterval миллисекунд, просто отдадим
// наружу, то что прочитали последний раз, не дергая пин лишний раз

  if (now - flasttime < ReadInterval) return fvalue;

// Ну а уж если времени прошло больше, куда деваца, надо читать

  int result = 0;

  for (byte i=0; i<16; i++) result += analogRead(fpin);

  flasttime = now; // запомним время, когда мы последний раз читали 
  fvalue    = result >> 4; // и запомним, что прочитали при этом

  return fvalue; // ну и то, что прочитали, то и отдадим.
}

// капец cpp файла
//--------------------------------------

 

 
в основной программе 
 
//--------------------------------------
//  Файл Test.ino

#include "TSensor.h"

//  ОБЪЯВЛЕНИЕ
TSensor MyPhoto(A0, true);   // обьявление и вызов конструктора для пина А0 с подтяжкой

void setup()
{
// НАСТРОЙКА
  MyPhoto.ReadInterval = 5000;   // читать не чаще раза в 5 сек.
}

void loop()
{
// ИСПОЛЬЗОВАНИЕ
  int value = MyPhoto.Read();
  Serial.print("Value = "); Serial.println(value);
  delay(1000);
}

//  капец файла Test.ino
//--------------------------------------

 

 
Пробоваем, пока Клапа не запретил...
 
andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

правильно, красиво, спасибо, но для небольших проектов сложно и много писать :)

ЗЫ. С молодости не люблю классы....бр.....бяка :)

ЗЫЫ. Вспоминаю соседа по общаге в середине 90х, попросили его на Turbo Pascal написать какую то примитивную лабу из пары действий, так он умудрился из программы длиной максимум строк 20 написать целый проект через классы строк на 100 минимум, делающий то же самое. Естественно вопрошающий не сдал, т.к. не смог объяснить как это все работает.

 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

andycat пишет:

правильно, красиво, спасибо, но для небольших проектов сложно и много писать :)

ЗЫ. С молодости не люблю классы....бр.....бяка :)

ЗЫЫ. Вспоминаю соседа по общаге в середине 90х, попросили его на Turbo Pascal написать какую то примитивную лабу из пары действий, так он умудрился из программы длиной максимум строк 20 написать целый проект через классы строк на 100 минимум, делающий то же самое. Естественно вопрошающий не сдал, т.к. не смог объяснить как это все работает.

Ну так вот - еще одна польза от применения классов.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

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

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

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

andriano пишет:

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

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

естесственно, это будет написано дальше, в секции про устойчивость класса. 

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

DetSimen пишет:

 
int TSensor::Read(void)
{

  unsigned long now = millis();  // прочитаем время на сейчас
  if (now - flasttime < 2000) return fvalue;
...
  flasttime = now; // запомним время, когда мы последний раз читали 
..
}
 
Теперь можно в loop() вставлять чтение нашего датчика хоть как и хоть куда, все равно реально читаться значение с pin-а будет раз в 2 секунды, это намайна, нас не посадят за жестокое обращение с даччиками, да и сам он не устанет и не будет просица на пенсию по здоровью. 

Семен, тебя квон покусал что ли - на millis() экономишь...

В данном случае обращение к датчику будет происходить не раз в 2000 мс, а раз в 2000-<время на всякую фигню внутри if> мс. А если учесть, что внутри if() ты читаешь датчик, то он всяко будет опрашиваться чаще, чем раз в заданное время. Конечно это, скорее всего, будет быть снивелированно другими операциям вне процедуры чтения, но ржавое ружьё иногда и палит посреди ночи.

 

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

16 вызовов analogRead() на 328 Меге 16 Мгц выполняются ~2 мс.  Критичненько? Я же не звездолеты строю и не осциллографы реального времени, где за каждый такт надабароца, я такого пока и не осилю.  Мои поделки простые, как я. А в них и классы простые. 

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

Да што ты заводишься сразу. Датчики-то разные бывают. Просто нужно запоминать время после окончания чтения, а не до него. Один лишний вызов millis(), а пользы от него - как от банки рассола на утро.

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

Я не завожусь, не подумай плахова. :)

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

Logik
Offline
Зарегистрирован: 05.08.2014

sadman41 пишет:

DetSimen пишет:

 
int TSensor::Read(void)
{

  unsigned long now = millis();  // прочитаем время на сейчас
  if (now - flasttime < 2000) return fvalue;
...
  flasttime = now; // запомним время, когда мы последний раз читали 
..
}
 
Теперь можно в loop() вставлять чтение нашего датчика хоть как и хоть куда, все равно реально читаться значение с pin-а будет раз в 2 секунды, это намайна, нас не посадят за жестокое обращение с даччиками, да и сам он не устанет и не будет просица на пенсию по здоровью. 

Семен, тебя квон покусал что ли - на millis() экономишь...

В данном случае обращение к датчику будет происходить не раз в 2000 мс, а раз в 2000-<время на всякую фигню внутри if> мс. А если учесть, что внутри if() ты читаешь датчик, то он всяко будет опрашиваться чаще, чем раз в заданное время. Конечно это, скорее всего, будет быть снивелированно другими операциям вне процедуры чтения, но ржавое ружьё иногда и палит посреди ночи.

 

Вы фигню писать изволили. Как раз  приведеный код обеспечит время между чтениями не менее 2000мс. Т.е. равно и более 2 сек.  Для обеспечения времени в среднем 2000 пишем flasttime += 2000;.  И уж совершенно пофиг до или после чтения. Про экономию: а чё не так?! Экономить - не транжирить, к разорению не ведет. Я бы для экономии еще лонгинт убрал везде, там и инта с головой. И миллис бы один раз в начале лопа вызывал, сохранял бы и передавал бы всем заинтересованым.

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

Logik пишет:

Вы фигню писать изволили. Как раз  приведеный код обеспечит время между чтениями не менее 2000мс. Т.е. равно и более 2 сек.  Для обеспечения времени в среднем 2000 пишем flasttime += 2000;

Прогоните:

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

void loop() {
  uint8_t processSomething = 0;
  uint32_t now, flasttime, sensorPrevReadingTime, sensorRestTime;
  flasttime = sensorPrevReadingTime = millis();

  while (true)
  {
    now = millis();  // прочитаем время на сейчас
    if (now - flasttime < 2000) continue; // return fvalue;

    processSomething++;
    if (3 <= processSomething) {
      delay(1000);
      //Serial.print("Process something");
      processSomething = 0;
    }

    // int result = 0;

    sensorRestTime = millis() - sensorPrevReadingTime;
    Serial.print("Sensor polled after: "); Serial.print(sensorRestTime); Serial.println(" ms");
    sensorPrevReadingTime  = millis();

    flasttime = now; // запомним время, когда мы последний раз читали
    //flasttime = millis();

    // fvalue    = result >> 4; // и запомним, что прочитали при этом
    // return fvalue; // ну и то, что прочитали, то и отдадим.
    
  }
}

 

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

sadman, я то понимаю, что ты прав, я же запоминаю now ДО чтения, а у Логика с логикой проблемы. Но он это никада не признает. 

Logik
Offline
Зарегистрирован: 05.08.2014

sadman41 пишет:

Прогоните:

Люблю конструктивный подход !

Прогнал.

Sensor polled after: 2000 ms
Sensor polled after: 2000 ms
Sensor polled after: 3000 ms
Sensor polled after: 999 ms
Sensor polled after: 2000 ms
Sensor polled after: 2999 ms
Sensor polled after: 1000 ms
 
Божеж как нихороше!  Нихороше делеи ставить и после них выводить время опроса! Поправил.
void setup() {
  Serial.begin(9600);
}

void loop() {
  uint8_t processSomething = 0;
  uint32_t now, sensorPrevReadingTime, sensorRestTime;
  int flasttime;
  flasttime=sensorPrevReadingTime = millis();

  while (true)
  {
    int now = millis();  // прочитаем время на сейчас
    if (now - flasttime < 2000) continue; // return fvalue;
Serial.print("now="); Serial.print(now);
Serial.print("delta="); Serial.println(now - flasttime);
    processSomething++;
    if (3 <= processSomething) {
      delay(1000);
      //Serial.print("Process something");
      processSomething = 0;
    }

    // int result = 0;

    sensorRestTime = millis() - sensorPrevReadingTime;
    Serial.print("Sensor polled after: "); Serial.print(sensorRestTime); Serial.println(" ms");
    sensorPrevReadingTime  = millis();

    flasttime = now; // запомним время, когда мы последний раз читали
    //flasttime = millis();

    // fvalue    = result >> 4; // и запомним, что прочитали при этом
    // return fvalue; // ну и то, что прочитали, то и отдадим.
    
  }
}

 

прогнал. Спецом переполнения дождался ;)
now=2000delta=2000
Sensor polled after: 2000 ms
now=4000delta=2000
Sensor polled after: 1999 ms
now=6000delta=2000
Sensor polled after: 3000 ms
now=8000delta=2000
Sensor polled after: 999 ms
now=10000delta=2000
Sensor polled after: 2000 ms
now=12000delta=2000
Sensor polled after: 2999 ms
now=14000delta=2000
Sensor polled after: 1000 ms
now=16000delta=2000
Sensor polled after: 1999 ms
now=18000delta=2000
Sensor polled after: 3000 ms
now=20000delta=2000
Sensor polled after: 999 ms
now=22000delta=2000
Sensor polled after: 1999 ms
now=24000delta=2000
Sensor polled after: 2999 ms
now=26000delta=2000
Sensor polled after: 1000 ms
now=28000delta=2000
Sensor polled after: 1999 ms
now=30000delta=2000
Sensor polled after: 2999 ms
now=32000delta=2000
Sensor polled after: 1000 ms
now=-31536delta=2000
Sensor polled after: 1999 ms
now=-29536delta=2000
Sensor polled after: 3000 ms
now=-27536delta=2000
Sensor polled after: 999 ms
now=-25536delta=2000
 
Как видите везде отмерено строго 2000 мсек.
 
Теперь Вы прогоняйте.
 
Logik
Offline
Зарегистрирован: 05.08.2014

DetSimen пишет:

sadman, я то понимаю, что ты прав, я же запоминаю now ДО чтения, а у Логика с логикой проблемы. Но он это никада не признает. 

Не чувак, у меня проблема с тупыми мнящими из себя фигзнает что, вместо того чтоб учится. См пост выше.

ПС. Неужели трудно осилить что

A=B

и

некоторый код не изменяющий А и B и не передающий управления куда либо
A=B

обеспечат на выходе одинаковое А при том же В на входе. 

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

Время чтения 16 раз analogRead() и взятия среднего - 2 мс.  значить, время опроса самого датчика

2000 мс - 2 мс. = 1998 мс.  

Logik
Offline
Зарегистрирован: 05.08.2014

DetSimen пишет:

Время чтения 16 раз analogRead() и взятия среднего - 2 мс.  значить, время опроса самого датчика

2000 мс - 2 мс. = 1998 мс.  

Нет конечно. Просто взятие среднего завершится на 2мс позже чем сработало условие завершения интервала. И так каждый цикл. Сработало условие ровно через 2сек, пошол опрос рсачет среднего и завершился через 2мс. И между этими завершениями сново будет 2 сек. В первый коде время скачет т.к. делей еще и под ифом, иногда  в цикле он есть, иногда нет. Вы конечно дико удивитесь, но даже в таком случае среднее время опроса всеравно останется 2 сек )))

Коментируем  if (3 <= processSomething) и о чудо!

now=2000delta=2000
Sensor polled after: 3001 ms
now=4000delta=2000
Sensor polled after: 2000 ms
now=6000delta=2000
Sensor polled after: 2000 ms
now=8000delta=2000
Sensor polled after: 1999 ms
now=10000delta=2000
Sensor polled after: 2000 ms
now=12000delta=2000
Sensor polled after: 2000 ms
now=14000delta=2000
Sensor polled after: 2000 ms
now=16000delta=2000
Sensor polled after: 2000 ms
now=18000delta=2000
Sensor polled after: 2001 ms
now=20000delta=2000
Sensor polled after: 2000 ms
now=22000delta=2000
Sensor polled after: 2000 ms
now=24000delta=2000
Sensor polled after: 1999 ms
now=26000delta=2000
Sensor polled after: 2000 ms
now=28000delta=2000
 
DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Я и он про то, что время между последним analogRead() прошлого цикла и первым analogRead() текущего цикла = 2000 - 2. Значит пин начинает читаться чаще, чем 2000 мс.  Только и всего. 

Если сделать так 

53 int TSensor::Read(void)
54 {
55  
56   unsigned long now = millis();  // прочитаем время на сейчас
57  
58 // Если с момента последнего обращения прошло меньше ReadInterval миллисекунд, просто отдадим
59 // наружу, то что прочитали последний раз, не дергая пин лишний раз
60  
61   if (now - flasttime < ReadInterval) return fvalue;
62  
63 // Ну а уж если времени прошло больше, куда деваца, надо читать
64  
65   int result = 0;
66  
67   for (byte i=0; i<16; i++) result += analogRead(fpin);
68  
69   flasttime = millis(); // запомним время, когда мы последний раз читали   ИЗМЕНЕНО ЗДЕСЬ
70   fvalue    = result >> 4; // и запомним, что прочитали при этом
71  
72   return fvalue; // ну и то, что прочитали, то и отдадим.
73 }
 

То точно пройдет время НЕ МЕНЬШЕ 2000 мс  между реальными обращением к пину. 

И это.  Извини, если обидел, я так больше не буду.  Сонный был. 

Logik
Offline
Зарегистрирован: 05.08.2014

DetSimen пишет:

Я и он про то, что время между последним analogRead() прошлого цикла и первым analogRead() текущего цикла = 2000 - 2. Значит пин начинает читаться чаще, чем 2000 мс.  Только и всего. 

.....

То точно пройдет время НЕ МЕНЬШЕ 2000 мс  между реальными обращением к пину. 

И это.  Извини, если обидел, я так больше не буду.  Сонный был. 

Та какие там обиды, проехали.

А я в #69 писал про два случая: НЕ МЕНЬШЕ и В СРЕДНЕМ. Это уже кому когда где что надо.  Еще обращу таки внимание что в примерах выще int вместо long int прописан. Экономия по мелочам, тут 100гр, там 100гр, а в итоге скетч влазит в память. 

А на вашем месте я бы про эти 2мсек чтения обработки вобще бы не вспоминал на фонее периода 2000. Дело в том что в большом скетче с прерываниями и крупными кусками кода обеспечить прокрутку лопа не реже 1мсек, чтоб сформировать точно 2000, очень сложно. Период будет плясать +-5мсек (и это еще очень хороше, может и 10 и 20 а с кривыми руками и 200 и более). На этом фоне те 2мсек не заметны. Если нужна реально высокая точность - прерывания таймера помогут.

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

Я на этот счёт вапще не парилса, я не Громозека, мне пофиг, 400 капель у меня налито, или 402

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

DetSimen пишет:

Я на этот счёт вапще не парилса, я не Громозека, мне пофиг, 400 капель у меня налито, или 402

Ну это зависит кто из этого стакана будет пить. Если это предстоит Вам, то Вы сами себе злобный Громозека. 

/**/
unsigned long mill; // переменная под millis()
typedef void (*pDo)() ;// тип -функция обработчик
//--------------------------------
// класс сенсор
class Cl_sensor {
  protected:
    const byte pin;
    const unsigned long inteval = 2000;
    unsigned long past;
    int val;
  public:
    /*конструктор*/
    Cl_sensor(byte p):  pin(p)  {}
    /*инициализация-вставить в setup()*/
    void init() {
      val = analogRead(pin);
      past = mill;
    }
    /*работа-вставить в loop()*/
    void run() {
    }
    /*прочитать значение с сенсора*/
    int read() {
      if (mill - past < inteval) return val;
      past = mill;
      return val = analogRead(pin);
    }
};
//--------------------------------
// класс вьювер
class Cl_viev {
  protected:
    pDo Do;//обработчик
    const unsigned long interval = 500;
    unsigned long past;
  public:
    /*конструктор*/
    Cl_viev(pDo D):  Do(D)  {}
    /*инициализация-вставить в setup()*/
    void init() {
      Do();
    }
    /*работа-вставить в loop()*/
    void run() {
      if (mill - past < interval) return;
      past = mill;
      Do();
    }
};
//---Компоновка-----------------------------
Cl_sensor Sensor(/*пин*/A0);
void DoViev() {
  Serial.println(Sensor.read());
}
Cl_viev Viev(/*обработчик*/DoViev);
//---main-----------------------------
void setup() {
  Serial.begin(9600);
  Viev.init();
  Sensor.init();
}

void loop() {
  mill = millis();
  Viev.run();
  Sensor.run();
}
/*Скетч использует 2104 байт (6%) памяти устройства. Всего доступно 32256 байт.
  Глобальные переменные используют 211 байт (10%) динамической памяти, оставляя 1837 байт для локальных переменных. Максимум: 2048 байт.
*/

 

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

DetSimen пишет:

Я и он про то, что время между последним analogRead() прошлого цикла и первым analogRead() текущего цикла = 2000 - 2. Значит пин начинает читаться чаще, чем 2000 мс.  Только и всего.  

Я понял, что увидел в посте Logic - симптомы дислексии. Все слова он узнал, а смысла написанного не понял.

Кстати, мне ведь 2мс не жалко. Обидно будет, когда люди по такому алгоритму будут читать что-то типа DHT или датчика с долгим временем конверсии. Вроде все работает, потому что остальные операции нивелируют, затем какой-нибудь деб... новичок ставит рядом два вызова функции readSensor() - один для чтения в переменную, а второй - для вывода на дисплей, а в if() появляется что-то с непредсказуемым временем выполнения - к примеру невовремя происходит вызов прерывания с каким-нить delayMicroseconds() внутри и готово дело. Несчастный жрет водку, плачет, попутно засерая окрестные форумы и тщетно пытается понять, почему температура 10 дней отдается нормальная, а потом хренак - и прыгает до небес. И, если у него ардуина не запустила газовый котел в экспедицию на Марс, то начинается замена сенсоров, перепайка, проверка неделями... Стоит эта круговерть одного лишнего вызова millis()?

 

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

sadman, я тебя понял, исправлю завтра. 

Справедливости ради надо отметить, что DHT и прочие Далласы к классу аналоговых датчиков нихрена не относятся, но дальше, когда я буду виртуалить Read(), это может быть критично.  Thanx. 

Тока срачица не нада. 

Logik
Offline
Зарегистрирован: 05.08.2014

Какие страсти у простейших! Обилие описаной хрени в коде означает наличие ошибок в ДНК, там миллис не поможет. И не на вредит тоже.  По причине неспособности к высшей нервной деятельности.

Читай свой пост:

sadman41 пишет:

 данном случае обращение к датчику будет происходить не раз в 2000 мс, а раз в 2000-<время на всякую фигню внутри if> мс. А если учесть, что внутри if() ты читаешь датчик, то он всяко будет опрашиваться чаще, чем раз в заданное время. 

Выделено мной. Но ты требование "раз в 2000 мс" сам озвучил. 

И смотри вывод скетча

now=2000delta=2000

Sensor polled after: 2000 ms
now=4000delta=2000
Sensor polled after: 1999 ms
now=6000delta=2000
Sensor polled after: 3000 ms
now=8000delta=2000
Sensor polled after: 999 ms
now=10000delta=2000
Sensor polled after: 2000 ms
now=12000delta=2000
Sensor polled after: 2999 ms
now=14000delta=2000
Sensor polled after: 1000 ms
now=16000delta=2000
Sensor polled after: 1999 ms
 
Это и называется опрос  раз в 2000мсек, как ты и хотел. Период опроса такой 2000мсек. (если уж совсем точно то период не менее 2000, для в среднем 2000 пишем flasttime +=2000;) . Заметь, если разговор идет про "раз в заданное время", то это абсолютно не зависит от времени конверси, хоть 2, хоть 1000, хоть непредсказуемо (и даже дольше периода опроса, хоть эта мысль безумно сложна для тя). Период меряют от начала опроса до начала следующего опроса (или от конца - до конца, или от получения результата - до следующего результата,  но между одинаковыми фазами процесса).  А если нужен интервал времени от конца опроса до начала следующего равный  2000мсек то при длительности опроса 2 мс как у Семена период будет уже 2002 мсек. Тут уж просто надо определятся ему что он хочет, но при прочих равных место расположения flasttime = now; до или после опроса вобще ни на что не вличет. Возможно предполагается что её надо заменить на flasttime = millis(), тогда интервал времени от конца опроса до начала следующего будет конечно формироватся верно. Но вот беда! Период опроса будет опять таки не 2000 ;) Мало того, в случае "а  if() появляется что-то с непредсказуемым временем выполнения" период опроса вобще станет непредсказуемым (да вобщем то и не периодическим, а с случайным). Потому так не делают. Делают опрос с фиксированым периодом, что очень упрощает последующую обработку.
 
ПС. Разберись с различием между "раз в 2000мсек" и "интервал времени от конца опроса до начала следующего". Почему второе вобще не важно, а первое должно быть константой позже поймеш, при матобработке введеного. Пока поверь так проще.

 

Logik
Offline
Зарегистрирован: 05.08.2014

DetSimen пишет:

Справедливости ради надо отметить, что DHT 

Он про него вспомнил т.к. време конверсии большое, до 750мсек. Некоторые идиоты его делеем формируют. Вобщем обсуждение кода слабоумных там.

Logik
Offline
Зарегистрирован: 05.08.2014

qwone пишет:

DetSimen пишет:

Я на этот счёт вапще не парилса, я не Громозека, мне пофиг, 400 капель у меня налито, или 402

Ну это зависит кто из этого стакана будет пить. Если это предстоит Вам, то Вы сами себе злобный Громозека. 

Не дурно. указатель на функцию - правильно. Хотя наследованием в ООП правильней, но я чаще указатель на функцию пользую. Только зачем его вюеру передавать? Лучше у сенсора вместо/вместе с сохранением введеного сразу вызывать некую функцию-потребитель данных, переданую указателем в конструктор совершенно аналогично. И пусть она там дальше думает что делать с данными. Это уже не сенсора проблема.  Мможет выводить или еще чего. Там тоже свои классы и указатели будут;) 

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

у мня будет и указатель на функцию, и не одну.  Но - завтра. Када я плюшевый, к компу не лезу.  Я стакан я уже накатил.  Чего и вам желаю, вместо срача. 

Logik
Offline
Зарегистрирован: 05.08.2014

Если завтра - то только через абстрактный метод принимаю ;)

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

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Я написал в свой традиционной системе событийного программирования. Но если бы к своему новому пониманию, то текст был бы таким

/**/
unsigned long mill; // переменная под millis()
typedef void (*pDo)() ;// тип -событие
//--------------------------------
// класс сенсор
class Cl_sensor {
  protected:
    const byte pin;
    const unsigned long inteval = 2000;
    unsigned long past = 0;
    int val;
  public:
    /*конструктор*/
    Cl_sensor(byte p):  pin(p)  {}
    /*прочитать значение с сенсора*/
    int read() {
      if (mill - past < inteval) return val;
      past = mill;
      return val = analogRead(pin);
    }
};
//--------------------------------
// класс вьювер
class Cl_display {
  protected:
    pDo Do;//событие
    const unsigned long interval = 500;
    unsigned long past;
  public:
    /*конструктор*/
    Cl_display(pDo D):  Do(D)  {}
    /*вывести на экран)*/
    void viev() {
      if (mill - past < interval) return;
      past = mill;
      Do();
    }
};
//---Компоновка-----------------------------
Cl_sensor Sensor(/*пин*/A0);
void eventA() {
  Serial.println(Sensor.read());
}
Cl_display display(/*событие*/eventA);
//---main-----------------------------
void setup() {
  Serial.begin(9600);
}

void loop() {
  mill = millis();
  display.viev();
}
/*Скетч использует 2048 байт (6%) памяти устройства. Всего доступно 32256 байт.
  Глобальные переменные используют 211 байт (10%) динамической памяти, оставляя 1837 байт для локальных переменных. Максимум: 2048 байт.
*/

 

Logik
Offline
Зарегистрирован: 05.08.2014

Можна конечно и так, но я имел в виду не это. В конструктор сенсора добавте pDo D, но pDo с параметром как  typedef void (*pDo)(int v).  А из сенсоровского read() сразу и Do(analogRead(pin)); Вызов конструктора станет Cl_sensor Sensor(/*пин*/A0, eventA); Это оч. символично - в конструкторе первый параметр "откуда взять", а второй - "куда отправить".

А можна и еще интересней! ;) 

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017
Ну вот, теперь, когда ручка зажила (а не ходите, дети, грущиками работать, учитесь хорошо), морозяки кончились и зарядила снежная круговерть, пора наливать мне дешевого рома, в тоске о жарких странах, золотому песку и крутобёдрых мулатках, да продолжать наше повествование дальше... 
 
Сейчас наш класс состоит из единственной функции, которая читает сенсор и нескольких полей, задающих признаки и состояние этого сенсора.  Но, спросите вы, разве мы не можем просто написать эту функцию, без всяких классов, которая просто читает сенсор и отдает его значение не чаще определенного интервала времени?  Зачем целый класс городить?  Конечно же можем, щас мы даже так и сделаем. Обьявим, для начала, глобально, несколько переменных.
 
 
  byte  SensorPin;  // номер пина, на котором "сидит" наш сенсор
  unsigned long SensorLastTime; // время последнего к нему обращения
  int  SensorLastValue  // последнее считанное с сенсора значение
  unsigned long SensorReadInterval;     // интервал, чаще которого, его  не нужно читать

 

да и напишем, сопсно, функцию
 
int ReadSensor(void)
{

  unsigned long now = millis();  // прочитаем время на сейчас

// Если с момента последнего обращения прошло меньше SensorReadInterval секунд, просто отдадим
// то, что прочитали последний раз, не дергая пин лишний раз

  if (now - SensorLastTime < SensorReadInterval) return SensorLastValue;

// Ну а уж если времени прошло больше, куда деваца, надо читать

  int result = 0;

  for (byte i=0; i<16; i++) result += analogRead(SensorPin);

  SensorLastTime  = now; // запомним время, когда мы последний раз читали 
  SensorLastValue    = result >> 4; // и запомним, что прочитали при этом

  return SensorLastValue; // ну и то, что прочитали, то и отдадим.
}

такой подход тоже имеет право на жизнь, но вот не нравятся мне чота 4 переменные на каждый сенсор, болтающиеся в глобальных данных. Запортить их может любая другая функция этого (да и не только этого) модуля и смотри потом до дыр в глазах, кто и в какой момент это сделал. По-хорошему надо бы эти глобальные переменные спрятать. А куда их можно спрятать?  Тут на ум сразу приходят статические переменные функций. Переменные, обьявленные в функциях как static, не теряют (хранят) свои значения между вызовами функции. Непонятно?  Ну вот представим, что нам нужно каждый день ходить в волшебный лес и кормить там тигра. Лес - это у нас функция, а тигр - переменная (пока обычная) в этой функции. Если мы напишем так:

 
void Forest(void)
{
int Tiger;
Serial.println(Tiger);
}

 

то в сериал выведется любое случайное значение, которое осталось валяться на стеке. То есть при входе в лес, тигр может находиться где угодно, местоположение его не определено, и нам придется бродить полдня по лесу с тяжелым рюкзаком корма, искать его и плакать. Мало того, при выходе из леса тигр вапще исчезает (ну лес то волшебный), и появляется в случайном месте только когда мы входим в лес. Поэтому добрые люди сразу же, при обьявлении переменной присваивают ей значение. 
 
int Tiger = 0;
ну слава Богу, теперь, как только мы входим в лес, тигр уже ждет нас возле дерева № 0, по крайней мере бегать по всему лесу уже не надо. Но вот беда, дерево №0 находится в таком заросшем овраге, что можно все штаны порвать, пока до него доберешься.  Поэтому мы берем тигра, и как чумадан, относим его в более удобное место, например к дереву №10 (Tiger = 10).  Со спокойной душой кормим тигра и уходим до завтра. А завтра что?  Правильно, тигр (глупое жывотное, прям как мой кот) ждет нас опять возле дерева №0, скатина такая.  Опять надо продираться через чертополох, оставляя клочки штанов на ветках. Плахая киса, не запомнил, куда мы его в прошлый раз притащили. И в следующий раз мы уже входим в лес вооруженные толстой, брутальной веревкой, к которой блестящими заклепками присобачена ржавая бирка, на которой нацарапано волшебное слово static. В очередной раз обдирая штаны до трусов, извлекаем тигра из чертополоха и веревкой привязываем упирающегося тигра к более удобному нам дереву №10.  
 
static int Tiger = 10;  
всё, галупчег, мы победили.  Теперь при входе в лес мы точно знаем что 
 
1. Там есть тигр, которого скатина леший нам навязал кормить, пока он в отпуске. 
2. Тигр конкретно ждет нас у дерева №10. 
 
И мало того, если вдруг нам покажется, что возле дерева №10 слишком много комаров, мы легко можем привязать тигра к любому другому дереву (присвоить в этой функции переменной, обьявленной как static, любое другое значение), зная, что при следующем входе в лес он будет именно там, куда мы его привязали. Например, написав где-то дальше по тексту Tiger = 20; мы точно знаем, что завтра (при следующем входе в функцию) тигр будет ждать еду конкретно возле дерева №20. А можно при входе написать Tiger++ и перебрать все деревья в лесу, чтоб ему не скучно было в одном и том же месте сидеть. Применим, написанное, попутно немношко подсократив, ведь номер пина мы можем передавать в функцию как параметр.
 

int ReadSensor(byte pin)
{

  static unsigned long  lasttime = 0;   // время последнего чтения
  static int            lastvalue = -1; // сигнал, что pin еще не читали 
  static int     readinterval=2000; // интервал между чтениями 2000 мс (2 секунды) 


  unsigned long now = millis();  // прочитаем время на сейчас

// если датчик уже читали (lastvalue>=0) И время последнего чтения меньше чем readinterval
// то просто отдаём последнее прочитанное значение из lastvalue

  if ((lastvalue>=0) && (now-lasttime<readInterval)) return lastvalue;

// Ну а уж если времени прошло больше, или датчик еще совсем не читали (lastvalue<0), то куда деваца, надо читать

  int result = 0;

  for (byte i=0; i<16; i++) result += analogRead(pin);

  lasttime  = now; // запомним время, когда мы последний раз читали 
  lastvalue    = result >> 4; // и запомним, что прочитали при этом

  return lastvalue; // ну и то, что прочитали, то и отдадим.
}

 

И ведь надо же, и так будет работать правильно.  Можно, кстати, и интервал чтения передавать как параметр, тогда еще от одной переменной можно избавиться, от readinterval. Примерно так
 
#define PHOTOPIN  A0             // пин, на котором сидит фоторезистор 
#define PHOTOREADINTERVAL 2000UL // интервал чтения фоторезистора - 2 секунды

 


int ReadSensor(byte pin, unsigned long interval)
{

  static unsigned long  lasttime = 0;   // время последнего чтения
  static int            lastvalue = -1; // сигнал, что pin еще не читали 


  unsigned long now = millis();  // прочитаем время на сейчас

// если датчик уже читали (lastvalue>=0) И время последнего чтения меньше чем readinterval
// то просто отдаём последнее прочитанное значение из lastvalue

  if ((lastvalue>=0) && (now-lasttime<interval)) return lastvalue;

// Ну а уж если времени прошло больше, или датчик еще не читали, то куда деваца, надо читать

  int result = 0;

  for (byte i=0; i<16; i++) result += analogRead(pin);

  lasttime  = now; // запомним время, когда мы последний раз читали 
  lastvalue    = result >> 4; // и запомним, что прочитали при этом

  return lastvalue; // ну и то, что прочитали, то и отдадим.
}

void loop(void)
{
  ReadSensor(PHOTOPIN,PHOTOREADINTERVAL);
}

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

 
 
#define PHOTOPIN  A0             // пин, на котором сидит фоторезистор 
#define PHOTOREADINTERVAL 2000UL // интервал чтения фоторезистора - 2 секунды

#define MQ2PIN  A0              // пин, на котором сидит MQ2 
#define MQ2READINTERVAL 5000UL   // интервал чтения датчика дыма MQ2 - 5 секунд

 

И если счас просто взять и написать:
 
void loop(void)
{
  int photovalue = ReadSensor(PHOTOPIN,PHOTOREADINTERVAL);  // читаем фоторезистор
  int mq2value = ReadSensor(MQ2PIN, MQ2READINTERVAL);
}

 

то давайте представим, а чему будет равно значение mq2value? Ну хотя бы примерно? Дак вот вовсе не тому, что мы прочитали с датчика MQ2, а то что мы прочитали с фотодатчика. Непонятно. А почему?  А потому что в первый раз, когда мы  обратились к ReadSensor, она сохранила в своих static переменных время последнего обращения к фотодатчику и последнее прочитанное из него значение.  А когда мы в следующий раз зашли в эту же функцию, но уже с номером пина MQ2 и интервалом его чтения. то вот это:
 
 if ((lastvalue>=0) && (now-lasttime<interval)) return lastvalue;
сказало нам, что мы читали датчик несколько микросекунд назад и неважно какой это был датчик,  поэтому функция нам и вернула то, что она в последний раз прочитала с фотодатчика.  Вот блин засада. Опять чешем репу. Канеш, тут сразу видится, по крайней мере, 2 простых решения, либо загромождать область глобальных данных  четырьмя переменными для каждого датчика и вызывать 1 функцию, передавая ей их в качестве параметров, либо писать столько функций, сколько у нас в программе будет даччиков, скрывая ихие данные в статические переменные этих функций. 
 
Первый путь - возможен, но некрасив, в конце концов область глобальных переменных вскоре превратится в "Войну и Мир", в которой мы и сами ничего не поймем, где что лежит и к чему относится. А так как переменные осмысленно мы называть не любим, а тщательное написание комментариев нам как серпом по тестикулам, то стабильная работа кода нам может только сница. А особенно если кто-то любит обьявлять глобальные массивы и не проверять выход за границы при обращении к нему, то всё, вэлкам на форум с воплями "ПАМАГИТИ!!! Разберитесь в моём гагнакоде. Яжнепрограммист!!!".    
 
Второй путь приводит к бессмысленному раздуванию кода программы, аптамуш, для каждого датчика компилятор генерирует свою функцию. Она ничем не отличается от функции для другого датчика, кроме как набором исходных данных. Вот тебе и выбор, б-ть! Либо делать свои данные уязвимыми для всех ветров и массивов, либо писать программу, которая из-за избыточного кода даже в Мегу не влезеть, если даччиков станет хотя бы 5. Пойду рома налью от безысходности... Ан нет, пока подожду, я же тут за классы начал говорить, вдруг оне помогут? Так вот.  Классы и были придуманы умными людями (а не все буржуины в психушках стандарты С++ выдумывают) как раз именно для такой концепции, во-первых скрыть свои лишние данные от посторонних, тем самым обезопасив их, а во-вторых не раздувать код одинаковыми функциями.  И если мы напишем в своей программе
 
TSensor Photo(A0);
TSensor MQ2(A1);
во первых, все внутренние данные РАЗНЫХ сенсоров будут РАЗНЫМИ и недоступными ни одному злобному Буратине снаружи. А во-вторых, функция ReadSensor в коде программы будет только одна. Не одна для каждого датчика, а именно одна ДЛЯ ВСЕХ датчиков одного класса TSensor.  Захотите датчик влажности подключить - да пожалуйста, обьявляйте еще одну переменную класса TSensor, настраивайте желаемый пин и интервал чтения и вперед, читайте и его тоже ЭТОЙ ЖЕ САМОЙ функцией, код самого класса не вырастет ни на байт. Вы хотите спросить, а как одна функция знает, для какого даччика её вызвали?  Дак очень просто.  Дело в том, что в функцию класса в качестве неявного параметра (мы про это не знаем и не видим, но это есть) передается указатель на конкретный объект, который ее вызвал. примерно так: 
 
мы пишем 
 
int Value = Photo.ReadSensor();
а компилятор неявно преобразует это в 
 
int Value = Photo.ReadSensor(&Photo);

 

то есть, неявно передает функции адрес обьекта, который ее вызвал.  А так как адреса у всех обьектов разные, то одна единственная функция может работать с размыми наборами данных для каждого этого объекта (то бишь датчика). Налицо сокращение кода, особенно, когда однотипных датчиков Over чем 9000. 
 
Пока делаем выводы, я пойду себе налью себе рома, штоли. Ручка сётаки вава. Не ходите дети в грущики.  
 
А в след. раз будем разбирать брррр... указатели на функции. 
 
DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Модератору, если позволите продолжать,  то просьба перенести эту графоманию в "Отвлеченные темы".

NalisniyRoman
Offline
Зарегистрирован: 17.06.2019

Подскажите какую нибудь литературу, с классами по Ардуине.

В гугле видать забанили меня или искать не умею, все с готовыми скетчами нахожу,читаю Лафоре, но чистый С++ не компилируется

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

NalisniyRoman, заканчивайте искать в твитере и читайте основателя С++  http://8361.ru/6sem/books/Straustrup-Yazyk_programmirovaniya_c.pdf

Ну и мои зарисовки http://arduino.ru/forum/programmirovanie/klassy-arduino-po-qwone-dlya-chainikov

asam
asam аватар
Offline
Зарегистрирован: 12.12.2018

NalisniyRoman пишет:

читаю Лафоре, но чистый С++ не компилируется

 

Это как это?

NalisniyRoman
Offline
Зарегистрирован: 17.06.2019

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

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

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

NalisniyRoman
Offline
Зарегистрирован: 17.06.2019

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

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

NalisniyRoman пишет:

Подскажите какую нибудь литературу, с классами по Ардуине.

Я тебе открою секрет - никаких "классов для ардуино" - нет. В Ардуино используется полноценный C++. Все примеры, что ты находишь в учебнике про классы С++ - должны компилироваться и на ардуино. Если у тебя не компилируется - значит просто у тебя код с ошибками, он не будет компилироваться и на "обычном С++". например в линуксе

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

единственное, в книшках часто любят примеры писАть, используя std::какаянить хня,  дак вот эта вот какаянить хня на Ардуинах не работает, памяти мало. 

NalisniyRoman
Offline
Зарегистрирован: 17.06.2019

Я правильно понял, что по сути на cpp пишем асмовские инструкции для ардуино?

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

NalisniyRoman пишет:
Я правильно понял, что по сути на cpp пишем асмовские инструкции для ардуино?

неправильно, АСМ-овских инструкций в большинстве ардуиновских скетчей и близко нет.

Редко-редко где-нибудь попадется кусок, написанный на асме - обычно не больше 10-20 строк. И только там. где нужна адская производительность и С не справляется.