Еще раз мигаем светодиодом без Delay

leshak
Offline
Зарегистрирован: 29.09.2011

Я уж и не припомню сколько раз тут на форуме отсылал новичков к примеру Мигаем светодиодом без delay()

Но... не нравится мне он. Зато нравится процесс "критиковать чужое" :)

Кратко что не нравится:

1. Типы переменных выбранны "от балды"
2. Используются переменные там где можно было оботись без них.
3. Излишне засоряется глобальное простраство имен

Если интерестно подробней мои стоны "почему тут все плохо" - можно почитать в моем блоге  МИГАЕМ СВЕТОДИОДОМ БЕЗ DELAY() или всегда ли ...

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

А потом форум пестрит скетчами где переменные объявляются по принципу "если не знаем что писать - напишем int".

Вообщем вот моя версия:

/* Blink without Delay
 2013
 by alxarduino@gmail.com
 http://alxarduino.blogspot.com/2013/08/delay.html
 */
 
#define LED_PIN  13      // номер выхода,подключенного к светодиоду
#define  INTERVAL  1000UL           // интервал между включение/выключением светодиода (1 секунда)
 
void setup() {
  // задаем режим выхода для порта, подключенного к светодиоду
  pinMode(LED_PIN, OUTPUT);      
}
 
void loop()
{
  // здесь будет код, который будет работать постоянно
  // и который не должен останавливаться на время между переключениями свето

  // обратите внимание на слово static   
  static unsigned long previousMillis = 0;        // храним время последнего переключения светодиода


  
  //проверяем не прошел ли нужный интервал, если прошел то
  if(millis() - previousMillis > INTERVAL) {
    // сохраняем время последнего переключения
    previousMillis = millis();  
     
    // меняем состояние выхода светодиода на противоположное текущему. 
    // если горит - тушим, не горит - зажигаем.
    digitalWrite(LED_PIN,!digitalRead(LED_PIN));
  }
}

 

Araris
Araris аватар
Offline
Зарегистрирован: 09.11.2012

#define INTERVAL 1000UL

Алексей, почему 1000UL, а не просто 1000 ?

Спасибо.

maksim
Offline
Зарегистрирован: 12.02.2012

Потому что INTERVAL сравнивается с выражением millis() - previousMillis результат которого имеет тип unsigned long. Если вы напишите просто 1000 (int), то сравнение будет производиться с переменными разного типа.

Araris
Araris аватар
Offline
Зарегистрирован: 09.11.2012

Спасибо.

leshak
Offline
Зарегистрирован: 29.09.2011

Araris пишет:

#define INTERVAL 1000UL

Алексей, почему 1000UL, а не просто 1000 ?

Спасибо.

Давайте чуть-чуть конкретизируем, что именно вызывает непонимание

1. Что означают буквы "UL", какой они дают эффект или
2. Смысл букв понятен, но непонятно "зачем их тут употребили"

Что именно требует пояснения?

P.S. Я понимаю что вы старались проявить вежливость, но тут я сам назвался leshak :)

Andrey_Y_Ostanovsky
Offline
Зарегистрирован: 03.12.2012

Вот только "тушим" - это как-то по-кухарски. :) Лучше уж тогда "гасим", или "выключаем"...

Araris
Araris аватар
Offline
Зарегистрирован: 09.11.2012

leshak пишет:

Давайте чуть-чуть конкретизируем, что именно вызывает непонимание

1. Что означают буквы "UL", какой они дают эффект или
2. Смысл букв понятен, но непонятно "зачем их тут употребили"

Что именно требует пояснения?

P.S. Я понимаю что вы старались проявить вежливость, но тут я сам назвался leshak :)

Я из Делфи/Паскаля, потому буквы "UL" удивили. Впрочем, maksim уже все объяснил.

P.S. OK, больше никакой вежливости :)

leshak
Offline
Зарегистрирован: 29.09.2011

Araris пишет:

Я из Делфи/Паскаля, потому буквы "UL" удивили. Впрочем, maksim уже все объяснил.

Ну, что-бы понимать это не с чужих слов, а "из первоисточника" - оставлю ссылочку Целочисленные константы

На самом деле, вообще-то, в данном КОНКРЕТНОМ случае пример будет корректно работать и без этих букв.

Но написал их специально: 

1. В надежде что кто-то задатся вопросом "что это за буквы" и погуглит. Ну не погуглит так спросит :) (еще такая же надежда на слово static :)
2. Что-бы "сразу привыкали" явно указывать, что-бы потом не ловит нежданчики..Что-бы если кому-то прийдет в голову поправить интервал на один час. Причем не в уме умножить 60*60*1000, а прямо так и написать #define INVERVAL 60*60*1000   - вот тут уже без явного указания типа с помощью UL - уже фигня выйдет. В четком соотвествии с объяснением Максима.

vdk
Offline
Зарегистрирован: 14.04.2013

ммм...

А почему static unsigned long previousMillis внутри loop?

toc
Offline
Зарегистрирован: 09.02.2013

leshak пишет:

//проверяем не прошел ли нужный интервал, если прошел то
if(millis() - previousMillis > INTERVAL) {
   // сохраняем время последнего переключения
   previousMillis = millis(); 

ещё можно чуть уменьшить количество вычислений в каждой итерации. Как то так ...

//проверяем не пора ли переключить
if(millis() > nextMillis ) {
   // сохраняем время следующего переключения
   nextMillis = millis() + INTERVAL; 
leshak
Offline
Зарегистрирован: 29.09.2011

vdk пишет:

ммм...

А почему static unsigned long previousMillis внутри loop?

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

Плюс у меня может быть несколько функций и в каждой будет свой prevMilis и не будут друг-другу мешать. Вообщем чем меньше глобальных переменных - тем лучше. Каждый кусок кода - независим.

Скажем что-то такое:

loop(){
   blinkLed();
   printReport();
}

void blinkLed(){
static unsigned long prevMillis=0;
  if( (millis()-prevMillis)>1000){
    prevMillis=millis();
     digitalWrite(LED,!digitalRead(LED));
  } 
}


void printReport(){
static unsigned long prevMillis=0;
  if( (millis()-prevMillis)>3000){
     prevMillis=millis();
     Serial.print("Current time=");
     Serial.println(millis();
  }    
}

Вообщем "по месту назначения" могу использовать имена переменных "как мне удобно" не переживая что они "пересекутся". И не нужно городить prevMillis1, prevMilis2, prevMillis3 , где то "ошибся цифру не поправил"...

leshak
Offline
Зарегистрирован: 29.09.2011

toc пишет:

ещё можно чуть уменьшить количество вычислений в каждой итерации. Как то так ...

//проверяем не пора ли переключить
if(millis() > nextMillis ) {
   // сохраняем время следующего переключения
   nextMillis = millis() + INTERVAL; 

Нельзя. Что в вашем варианте произойдет когда millis() пойдет к значению, скажем 4,294,967,000?

Ваше условие - начнет постоянно срабатывать. И "будет глючит" 295 миллисекунд подряд . Не дай бог по этому условию SMS-ска отправляется :) Вернее ровно за секунду до переполнения начнет глючит. А если интервал, скажем "раз в час", то целый час кусок кода будет выполняться при каждом проходе loop

Это в математике у нас "a-b>c" эквивалетно "a>b+c". И то не всегда, а когда a,b,c являются вещественным или натуральными числами. За счет того что числвая  ось - бесконечна. А у нас числовые оси - конечны. Да еще "закольцованы". Поэтому "a-b>c" не эквивалетно "a>b+c". На больших значениях - мы получаемм переполнение.

 

vdk
Offline
Зарегистрирован: 14.04.2013

leshak пишет:

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

Допустим, я новичок, и понял, что loop() - это такой аналог while (1)... но зачем мы тогда в цикле каждый раз инициализируем переменную?

 

leshak
Offline
Зарегистрирован: 29.09.2011

vdk пишет:

Допустим, я новичок, и понял, что loop() - это такой аналог while (1)...

Это не аналог а "он и есть". Если бы вы писали программу на "чистом C", то знаете что у вас есть  "главная фунция main()" которая вызвается при старте програмы.

На самом деле она и тут есть. Просто при компиляции ArduinoIDE, незаметно, за кулисами, к каждому скетчу добавляет примерно такую функцию main()

 

void main(){
   setup();
   while(1)loop();
}

Попробуйте не объявить фунцию setup() или loop() 

и вы увидите, в ошибках, что-то типа т"в функции main() была вызвана неизвестная функция loop()". Хотя сами вы, никакой main() не объявляли, а в ошибках - ее видно.

vdk пишет:

но зачем мы тогда в цикле каждый раз инициализируем переменную?

А мы не иницализируем ее каждый раз. Для этого у нас есть ключевое слово static. Причем в коментах предлагается на него обратить внимание. Если новичок не знает что оно делает - это тонкий намек "погуглить". Ну или - если не интерестно разбиратся, то можно просто копипастать этот код.

Ведь он же работает, правда? Преверили? Если работает - значит никаких "каждый раз инициализируем переменную" - нет :)  Логично? Или не работает?

staitc нам как раз и дает эффект что переменная ведет себя как глобальная (инициализируется один раз, сохраняет свое значение при выходи из фунции), при этом не обладает минусами "глобальности" - видна только внутри функции в которой ее объявили. И в других функциях - тоже могут быть переменные с таким же именем.

vdk
Offline
Зарегистрирован: 14.04.2013

leshak пишет:

На самом деле она и тут есть. Просто при компиляции ArduinoIDE, незаметно, за кулисами, к каждому скетчу добавляет примерно такую функцию main()

void main(){
   setup();
   while(1)loop();
}

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

leshak пишет:

А мы не иницализируем ее каждый раз. Для этого у нас есть ключевое слово static. Причем в коментах предлагается на него обратить внимание.

Если мы будем вставлять спереди ключевое слово static, то конструкция будет выполняться только один раз, да? И в середине loop тоже? И необязательно переменная?

leshak пишет:

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

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

leshak
Offline
Зарегистрирован: 29.09.2011

vdk пишет:

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

 Во первых - вы явно так и не погуглили static. Иначе вы знали бы что применить его можно только к ОБЪЯВЛЕНИЮ переменной, функции, класса... а не к любой произвольной конструкции.

Во вторых - это можно было не гуглить. А взять и поробовать. Набросать что-то типа

void loop(){
   static someFunction();
}

void someFunction(){
  Serial.println("Executed");
}

Зачем спрашивать кого-то, если можно спросить сам компилятор? Вдруг этот кто-то ошибется/соврет? Лично я - постоянно пишу вот такие маленькие скетчи-проверки, на 3-5 строк, когда у меня возникает какой-то пробел в понимании вида "а что будет если..." или "а как оно там себя ведет".

В третьих - не с того конца зашли к static. Его смысловая нагрузка не в "будет выполнятся один раз", а скорее в "переменная не будет уничтожена при выходе из функции". Поэтому при следующем заходе в функцию произойдет что-то типа "о... объявления переменной... а она у нас уже есть, память под нее уже выделена, значит второй раз выделять память под нее не нужно пользуемся тем что уже есть. раз память мы под нее не выделяем, значит и инициализацию переменной делать не нужно". В результате - в переменной сохранилось то значение которое было в нее записанно при прошлом вызове loop()

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

Если говорить совсем строго, то смысл слова static в том что-бы указать компилятору "память под эту переменную нужно выделить не в стеке вызова, а в сегменте данных программы". Результатом того что переменная разместилась не в стеке - и является ее "неуничтожимость", что в свою очередь приводит к однократному вызову инициализации.

Но вникание в то что такое "стек вызовы", "сегмент данных" - в данном случае уже слегка избыточно. Это уже разбирательство "как компилятор устроен под капотом" (вернее в какой ASM код все это будет странслированно). На бытовом уровне можно воспринимать static именно как "защиту от уничтожения локальной переменной". По крайней мере я пользуюсь именно такой мысленной конструкцией :)

vdk пишет:

И в середине loop тоже? 

Мы же вроде выше разобрались что loop() это обычная функция. Следовательно к ней применимо все что применимо к любой функции. В том числе и объявлять переменные в любом месте функции. Главное - объявление должно быть до того как переменная использована.

vdk пишет:

И необязательно переменная?

А попробовать-проверить?

leshak]</p> <p>Если новичок не знает что оно делает - это тонкий намек "погуглить". Ну или - если не интерестно разбиратся, то можно просто копипастать этот код.</p> <p>[quote=vdk пишет:

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

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

Может кого-нибудь и устраивает постоянно попадать в такую ситуацию... это его дело. Лично мне - такие люди не интересны, я пишу не для них. У меня же тоже есть свобода выбора для кого писать, правда? ;)

Я же тоже не родился с этими знаниями. Более того - когда я купил ардуину опыта C/C++ у меня тоже небыло. Просто когда видел что-то не понятное в чужом коде/примере - лез гуглить и разбиратся. Даже если прямо сейчас оно мне было не нужно :)  И сейчас поступаю так же. В железе я более слаб. Вон в соседней ветке увидел что человек использовал компараторы, начал выяснять "нафига и зачем". Когда понял - попытался решить задачу без них. Узнал что есть встроенные в камень, полез в даташит почитать, там еще сделал для себя парочку "открытий" :) Только так и перестают быть новичками ;)

А програмировать микроконтроллеры не вникая в "тонкости C/C++", "ардуиновские нюансы", "специфику железа" - все равно не выйдет. Все эти "не нужно знать програмирование" - это маркетинговый булшит. C/C++ - изначально создавались как "язык максимально близкий к железу и дающий полный контроль" (разве что ASM "еще ближе к железу"). Не хотите вникать как устроена память - C#/Java - вот ваши языки. Там знакомство с этим - можно "оттянуть" намного дольше. Не хотите разбиратся с железом - забудте про микроконтроллеры.

А если "разбиратся" не пугает, то.... на самом деле в микроконтролле/ардуине все намного проще, на самом деле, чем в "большом компьютере". "Мир" - намного меньше размера.

leshak
Offline
Зарегистрирован: 29.09.2011

По мотивам кода из сообщения #10 написал следующую статью

Леший в мире Ардуины: Делаем несколько дел одновременно-переодически

Вот как этот код выглядит "целиком"

/* Blink And Print Without Delay
 2013
 by alxarduino @gmail.com
  http:/ /alxarduino.blogspot.com/2013/09/BlinkAndPrintWithoutDelay.html
 */
  
#define LED_PIN  13      // номер выхода,подключенного к светодиоду
#define  BLINK_INTERVAL  5000UL  // интервал между включение/выключением светодиода (5 секунд)
#define PRINT_INTERVAL 1000UL  // периодичность вывода времени в Serial (1 cекунда)
#define SERIAL_SPEED 9600 // скорость работы Serial
  
void setup() {
  // задаем режим выхода для порта, подключенного к светодиоду
  pinMode(LED_PIN, OUTPUT);      
   
  // задаем скорость работы ком-порта
  Serial.begin(SERIAL_SPEED);
   
   
}
  
void loop()
{
  blinkLed(BLINK_INTERVAL);  // мигаем
  printTime(PRINT_INTERVAL); // выводим время
}
 
// мигает диодом с периодичностью interval
void blinkLed(unsigned long interval ){
  static unsigned long prevTime = 0; // время когда последний раз переключали диод
  if(millis() - prevTime > interval ) {
    prevTime = millis();  // 
    digitalWrite(LED_PIN,!digitalRead(LED_PIN)); 
  }
}
 
// выводит в Serial время с периодичностью interval
void printTime(unsigned long interval){
  static unsigned long prevTime=0;
  if(millis()-prevTime>interval){
    prevTime=millis();
     
    Serial.print("Current time:");
    Serial.println(millis());
 
  }
}

Чуть чуть больше примеров и намного больше воды - по ссылке выше :)

P.S. Буду рад вопросам :) Еще больше - критике/замечаниям (хотя, конечно буду огрызатся ;)

UPD: blinkLed поправлен (BLINK_INTERVAL --> interval) благодаря замечанию trembo в сообщении #25

alex_r61
alex_r61 аватар
Offline
Зарегистрирован: 20.06.2012

А что будет с этим

if(if(millis() - prevTime > BLINK_INTERVAL)

когда millis перейдёт в 0 и начнёт новый период???

leshak
Offline
Зарегистрирован: 29.09.2011

alex_r61 пишет:

когда millis перейдёт в 0 и начнёт новый период???

А давайте спросим арудину что будет в этом случае:

void setup(){
  Serial.begin(9600);
  
  unsigned long prevMillist= 4294967290;
  unsigned long psevdoMillis=5;
  
  unsigned long diff=psevdoMillis-prevMillist;
  
  Serial.println(diff);
  Serial.println(diff>1000?"True":"False");
  
}

void loop(){
}

 

alex_r61
alex_r61 аватар
Offline
Зарегистрирован: 20.06.2012

А она ответит, что не учли переход через 0.

leshak
Offline
Зарегистрирован: 29.09.2011

alex_r61 пишет:

А она ответит, что не учли переход через 0.

А она ответит что вы не запускали этот скетч. Чему получилась равна разница millis()-previousMillis когда previousMillis подходит к границе переполнения, а milllis() уже перешла (имеет малые значения)?

alex_r61
alex_r61 аватар
Offline
Зарегистрирован: 20.06.2012

>Чему получилась равна разница millis()-previousMillis

Чему и должна.

leshak
Offline
Зарегистрирован: 29.09.2011

alex_r61 пишет:

>Чему получилась равна разница millis()-previousMillis

Чему и должна.

Ну ведь никто же не знает чему она, по вашему, должна быть равна. Что скетч в логе написал? Какая цифра получилась? Сработало условие (то есть "проблема имеется") или False написало (значит лишних сработок не будет)?

Понятно "почему именно так получается" или нужно объяснить? 

vdk
Offline
Зарегистрирован: 14.04.2013

leshak пишет:

 Во первых - вы явно так и не погуглили static. Иначе вы знали бы что применить его можно только к ОБЪЯВЛЕНИЮ переменной, функции, класса... а не к любой произвольной конструкции.

Я, как новичок не умею такие штуки гуглить :(

leshak пишет:

Зачем спрашивать кого-то, если можно спросить сам компилятор? Вдруг этот кто-то ошибется/соврет? Лично я - постоянно пишу вот такие маленькие скетчи-проверки, на 3-5 строк, когда у меня возникает какой-то пробел в понимании вида "а что будет если..." или "а как оно там себя ведет".

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

 

leshak пишет:

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

Как все запутано в этой вашей ардуине... "оно как бы и работает, но так странно..."

leshak пишет:

Если говорить совсем строго, то смысл слова static в том что-бы указать компилятору "память под эту переменную нужно выделить не в стеке вызова, а в сегменте данных программы". Результатом того что переменная разместилась не в стеке - и является ее "неуничтожимость", что в свою очередь приводит к однократному вызову инициализации.

Ничччего не понятно. Какие-то стеки, сегменты, статики... а ведь мне всего лишь простой скетч немного доработать...

leshak пишет:

Мы же вроде выше разобрались что loop() это обычная функция.

Как это обычная, если она работает иначе, чем обычные функции?

leshak пишет:

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

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

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

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

leshak
Offline
Зарегистрирован: 29.09.2011

>Я, как новичок не умею
>
Как все запутано в этой вашей ардуине..
>
а ведь мне всего лишь 
>
 достаточно достигнуть
>
то сами и разбирайтесь

Эх... сел я тут писать по каждому вашему тезису опровержение писать... вы же не сомневаетесь что "мне есть что сказать"? :)

Но....

Что-то не понятно объяснил?  Ну я не идеален. Стараюсь объяснить получше что сразу "донести не удалось". Не всегда с первого раза выходит. А спорить "зачем это нужно?" с тем кому это не нужно - не вижу смысла.

Такое впечатление что я вас чему-то уговариваю. Кто кто хочет разобратся - разбирается, пробует, задает вопросы. Не хотите - ну так зачем вы тратите мое (и свое) время?  У вас есть официальный пример. Вам его достаточно, не волнует что типы перепутаны, не хотите узнать что-то новое? Да ради бога. Мы не в школе, я вам оценки не ставлю, успеваемость ваша - меня не волнует. Значит не для вас я писал. Только и всего. 

 

trembo
trembo аватар
Offline
Зарегистрирован: 08.04.2011

Супер!

Долго соображал как работает static и почему при втором входе prevTime не "0", а предыдущее значение,

даже после  " static unsigned long prevTime = 0; " принт вставлял....

А то я всё время SimpleTimer использовал,

хотя в нём есть и кое-какие примочки типа: количество повторов таймера и однократный таймер.

Для полного порядка надо исправить 

if(millis() - prevTime > BLINK_INTERVAL)

на  if(millis()-prevTime>interval)

А если ещё исправить  > на  >=

if(millis()-prevTime>interval)

на if(millis()-prevTime>=interval)

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

Спасибо Leshak!

vdk
Offline
Зарегистрирован: 14.04.2013

leshak пишет:

Такое впечатление что я вас чему-то уговариваю.

Да мне-то не надо ;) А вот новичкам придется разжевывать этот момент. (и, подозреваю, не один раз)

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Так новичкам об этом лучше не говорить :) Пока разберутся уже будут знать

toc
Offline
Зарегистрирован: 09.02.2013

leshak пишет:

toc пишет:

ещё можно чуть уменьшить количество вычислений в каждой итерации. Как то так ...

//проверяем не пора ли переключить
if(millis() > nextMillis ) {
   // сохраняем время следующего переключения
   nextMillis = millis() + INTERVAL; 

Нельзя. Что в вашем варианте произойдет когда millis() пойдет к значению, скажем 4,294,967,000?

oops, i did it again https://www.youtube.com/watch?v=CduA0TULnow

leshak
Offline
Зарегистрирован: 29.09.2011

trembo пишет:

даже после  " static unsigned long prevTime = 0; " принт вставлял....

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

trembo пишет:

А то я всё время SimpleTimer использовал,

хотя в нём есть и кое-какие примочки типа: количество повторов таймера и однократный таймер.

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

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

Я тоже, в итоге, все это планирую сделать в ввиде "библиотеки" (1001-вой, наверное, для этих задач, SimpleTimer - не единственная). Только не в виде классов и функций, а в виде макроса. Плюс хелперы что-бы задавать вреям в секундах, часах, минутах.

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

DO_EVERY(1000,digitalWrite(LED,!digitalRead(LED));

И все. А на этапе компиляции само "развернется" в объявление переменной prevMillis, if и т.п. (то есть, с точки зрения размера - будет в точности то-же самое как если бы писали руками).

Так что библиотеками можно и нужно пользоватся. Но это не отменяет "понимания". Часто "готовое решение" чуть-чуть не подходит и нужно уметь его "подпилить напильником".

trembo пишет:

Для полного порядка надо исправить 

if(millis() - prevTime > BLINK_INTERVAL)

на  if(millis()-prevTime>interval)

Ох... спасибо. Запутался с версиями, пошаговым "приходом к чему-то". Как оказалось писать в "виде статьи" намного труднее чем думал. Со всеми этими форматированиями, проставить ссылки и т.п. Рабочий код пишется 20-ть минут, а "оформить/расписать" - часы или даже дни :( 

Щас поправлю.

trembo пишет:

А если ещё исправить  > на  >=

....

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

В принципе тоже думал об этом. Но... почему-то захотелось это оставить как в оригинальном скетче. Тем более что "круглые числа" все равно у нас будут только до тех пор, пока остальной loop() выполняется быстрее чем за одну миллисекунду. Когда Loop() станет побольше/реальней - все равно не будет точности. Это все "примерно". Если нужно "совсем точно" - тут уже апаратные таймеры/прерывания нужно. Но это уже "другая история". Вообщем пока оставлю как есть, будем считать что ваш комментарий - уже достаточно для того кто захотел вникнуть :)

P.S. "Ты,дух, - правильный военный" (C) ДМБ. :)

leshak
Offline
Зарегистрирован: 29.09.2011

leshak пишет:

Я тоже, в итоге, все это планирую сделать в ввиде "библиотеки" (1001-вой, наверное, для этих задач, SimpleTimer - не единственная). Только не в виде классов и функций, а в виде макроса. Плюс хелперы что-бы задавать вреям в секундах, часах, минутах.

Билиотеку брать тут: https://bitbucket.org/alxarduino/leshakutils
Подробность/графомания тут: http://alxarduino.blogspot.com/2013/09/ComfortablyBlinkWithoutDelay.html

Пример использования:

/* Blink without Delay
 2013
 by alxarduino @ gmail.com
 http: //alxarduino.blogspot.com/2013/09/ComfortablyBlinkWithoutDelay.html
 */
  
#include <TimeHelpers.h>  
  
#define LED_PIN  13      // номер выхода,подключенного к светодиоду
#define  BLINK_INTERVAL  _SEC_(5)  // интервал между включение/выключением светодиода (5 секунд)
#define PRINT_INTERVAL _SEC_(1)  // переодичность вывода времени в Serial (1 cекунда)
#define SERIAL_SPEED 9600 // скорость работы Serial
 
 
  
void setup() {
  // задаем режим выхода для порта, подключенного к светодиоду
  pinMode(LED_PIN, OUTPUT);      
   
  // задаем скорость работы ком-порта
  Serial.begin(SERIAL_SPEED);
}
  
void loop()
{
  // мигаем диодом   (периодически переключаем его состояние)
  DO_EVERY(BLINK_INTERVAL,digitalWrite(LED_PIN,!digitalRead(LED_PIN))); 
   
  // периодически выводим время в Serial
  DO_EVERY(PRINT_INTERVAL,{
    Serial.print("Current time:");
    Serial.println(millis());
  });
   
}

 

sp34
Offline
Зарегистрирован: 05.03.2013

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

Не планируете ли вы сделать что то подобное в своей библиотеке ?

leshak
Offline
Зарегистрирован: 29.09.2011

sp34 пишет:

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

Ну так "стандартные вариации" предназначены для того что-бы понять принцип. И уж "напильником" подгонять под конкретную ситуацию. Ничего же не мешает взять, "заготовку" из Делаем несколько дел одновременно-периодически и чуть-чуть чуть подпилить ее под свои нужды:

#define LED 13

#define LOW_INTERVAL 1000 // сколько времени выдавать LOW
#define HIGH_INTERVAL 3000 // сколько времени выдавать HIGH

void setup(){
   pinMode(LED,OUTPUT);
}


void loop(){
   blinkLed();

}


void blinkLed(){
static unsigned long period= LOW_INTERVAL; // текущий интервал мигания
static unsigned long prevMillis=0;

  if( (millis()-prevMillis)>=period){
    prevMillis=millis();
     digitalWrite(LED,!digitalRead(LED));
     
     // меняем интервал через сколько нужно сработать в следующий раз
     period=(period==LOW_INTERVAL)?HIGH_INTERVAL: LOW_INTERVAL;  // чередуем LOW_INTERVAL/HIGH_INTERVAL
  } 
}

Или использовать использовать DO_EVERY, тут чуток другой принцип можно применить. Берем наши два интервала, находим наибольший общий делитель, заводим какую-то переменную счетчик и в зависимости от ее значения - выводим LOW или HIGH.

То есть, если как в коде выше нужно гореть 3 сек, а "не гореть" 1 сек, то это означает что нам нужен один DO_EVERY. Просто будем каждую секунду решать "гореть нам или нет".

#include "TimeHelpers.h"
#define LED 13

void setup(){
   pinMode(LED,OUTPUT);
}


void loop(){
   DO_EVERY(_SEC_(1),{
     static byte cnt=0; // счетчик сколько раз было вызвано действие
     digitalWrite(LED,cnt++>0); // LOW при cnt=0, HIGH при cnt=1,2,3, тут мы задаем сколько времени будет LOW
     // так как вызывается раз в секунду выходит LOW - 1 сек. HIGH 3 сек.
     if(cnt>3)cnt=0;// сбрасываем счетчик что бы огранчить время HIGH
     

  });

}

sp34 пишет:

Не планируете ли вы сделать что то подобное в своей библиотеке ?

Нет, пока  не планирую.

Во первых честно говоря даже попытавшись вообразить "зачем это нужно" - не смог. Вы погорячилсь с "часто требуется". Приходит в голову только генерация каких-то PWM, шим-ов и проч. Но - это плохой метод для подобных задач. Не та точность и "гарантированость" времени срабатывания. Тут лучше вешатся на прерывание таймера или апаратным ШИМ-мом пользоватся. millis() - не самый удачный вариант для этого.

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

Возможно  добавлю что-то типа "выполнить дейсвие чере какое-то время" (вот это действительно чаще требуется). Что-то типа DO_AFTER(1000,digitalWrite(HIGH)); Из комбинации двух таких DO_AFTER - возможно и получится слепить что-то типа того что вы хотели.  Но на макросах это уже трудней будет реализовать. Потребуется с помощью классов. Боюсь в итоге это приведет к написанию еще одно SimpleTimer. А зачем если он уже написан?

По крайней мере мне так видится на данный момент. Возможно вы сможете убедить меня изменить мнение :)  Често говоря даже заинтересовало где же такое может "часто требоватся" :)

 

sp34
Offline
Зарегистрирован: 05.03.2013

Да вы не ошиблись именно в PWM модуляции , но не в формировании самого импульса PWM , а в управлении 

режимом Н моста , где на каждую половину моста подается своя PWM 1 , PWM 2

 

leshak
Offline
Зарегистрирован: 29.09.2011

sp34 пишет:

Да вы не ошиблись именно в PWM модуляции , но не в формировании самого импульса PWM , а в управлении 

режимом Н моста , где на каждую половину моста подается своя PWM 1 , PWM 2

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

>режимом Н моста , где на каждую половину моста подается своя PWM 1 , PWM 2

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

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

sp34
Offline
Зарегистрирован: 05.03.2013

Задача не частная ( по крайней мере стала месяц назад , но интерес остался ) - видел во очую немецкий двигатель постоянного тока с двумя выводами управляющийся контролером и вот когда я повесил на один вывод массу осцилогрофа , а на вторую щуп , я увидел ШИМ относительно нулевой линии как положительный так и отрицательный и мне стало интересно как это так ? и при двух одинаковых шим ротор двигателя не свернуть ( как у шаговика ).

Стал размышлять и так как две шим  естественно не должны пересекаться ( приведет к КЗ. Н моста) получается их надо реверсировать постоянно , но с частотой в десятки герц  (ротор при этом , как тенисный мячик) далее начинаем изменять наши шим , одну увеличиваем другую уменьшаем и ротор начинает вращаться , но даже если наш шим достигнет значения 255 - за счет работы реверса до двигателя дойдет только 50% от нее вот с этого момента и нужно менять прямоугольные импульсы на несиметричные в управлении реверсом и двигатель получит все 255 (100%)

по большому счету нужна таже шим , но с очень маленькой частотой смены периуда 30-40Hz

 

leshak
Offline
Зарегистрирован: 29.09.2011

sp34 пишет:

Задача не частная ( по крайней мере стала месяц назад , но интерес остался ) 

Из вашего описания - это именно "частная" задача. Тут, похоже немного путаница терминов. Похоже вы "частная" поняли как синоним приватная. Вы говорите про public/private задачи, а я имел ввиду generic/concrete. Вообщем IMHO это не generic-задача. У нее не достаточный уровень абстракции что-бы решать ее в рамках библиотеки. По крайней мере этой. Если бы писали библиотеку для управления именно этим конкретным движком - тогда да. Но даже в этом случае IMHO использование подхода loop()/millis() - плохой выбор. Тут нужно смотреть в сторону Arduino - SecretsOfArduinoPWM» Fixing the Arduino’s PWM #2 JeeLabsCreating a variable frequency PWM output on Arduino UNO | OxGadgets и т.п.

Вообщем я бы рекомендовал - отдельную ветку завести :)

sp34
Offline
Зарегистрирован: 05.03.2013
digitalWrite(10,!digitalRead(10));//инвертируем состояние пина

Возможно ли это действие для экономии места в памяти прописать как :        PORTD |=B00  ,  ! PORTD |=B11 ;  -  естественно этот пример не правильный !

maksim
Offline
Зарегистрирован: 12.02.2012
PORTX&(1<<pin) ? PORTX&=~(1<<pin) : PORTX|=1<<pin;

 

sp34
Offline
Зарегистрирован: 05.03.2013

maksim пишет:

PORTX&(1<<pin) ? PORTX&=~(1<<pin) : PORTX|=1<<pin;

Как я понимаю PORTX - где Х является в моем случае портом B

т е пишу PORTB&(1<<ЧТО ТУТ ПРОПИСАТЬ ? PB1 )  и в двух других вместо pin тоже самое - или все вообще не так ?

Все оказалось именно так Спасибо !

И еще вопросик , а как digitalWrite(pin , HIGH) - подключить подтягивающий резистор , также заменить PORT ?

leshak
Offline
Зарегистрирован: 29.09.2011

maksim пишет:

PORTX&(1<<pin) ? PORTX&=~(1<<pin) : PORTX|=1<<pin;

А зачем так сложно?  XOR же есть готовый.

PORTB^=(1<<7) - как-то намного легче понять (работать будет только на MEGA)

sp34

1. Если решили работать с прямой записью в порты не нужно путать их имена. Если я правильно помню, то светик ни на одной плате не висит на PORTD. У леонардо на PORTC, у астальных PORTB (правда на разных выходах у меги и uno/nano и т.п.)
2. Экономите на спичках при этом теряете переносимость. Сможете грамотно условной компиляцией добится что-бы оно работало на uno/mega/leonardo? Когда жестко встает вопрос таймингов, или нужно синхронно несколько выходов переключать - тут понятно ради чего жертва, а в данном случае....
3. Вам, возможно проще будет это "по ардуинистому" делать. Через bitRead()/bitSet()
 

И главное - это как-то очень далеко от темы ветки. Переодические действия, millis() и т.п тут обсуждаются. Тут уместней было отдельную ветку открыть с вопросом, про битовые манипуляции, прямой работой с портами и проч (ну либо просто погуглить/почитать). Ну или доку Arduino - BitwiseAnd почитать.

 

 
sp34
Offline
Зарегистрирован: 05.03.2013

в ATtiny 2313 - пытаюсь скетч залить - здесь и на спичках приходится экономить !

По замечаниям - исправлюсь !

maksim
Offline
Зарегистрирован: 12.02.2012

leshak пишет:

А зачем так сложно?  XOR же есть готовый.

PORTB^=(1<<7) - как-то намного легче понять (работать будет только на MEGA)

Как говорится - "Век живи..."

leshak
Offline
Зарегистрирован: 29.09.2011

maksim пишет:

leshak пишет:

А зачем так сложно?  XOR же есть готовый.

PORTB^=(1<<7) - как-то намного легче понять (работать будет только на MEGA)

Как говорится - "Век живи..."

Я тоже, раза пару наступал на эти грабли. Вначале заимплеменчу, правда не через if, а через not и битовую маску того бита что нужно поменять, потом начинаю упрощать выражение, как в школе :), и в какой-то момент вижу "блин, опять XOR реализовал через &,| и ~  :)

pencraft
Offline
Зарегистрирован: 17.01.2012

leshak пишет:

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

DO_EVERY(1000,digitalWrite(LED,!digitalRead(LED));

Алексей, прочитал Вашу статью на blogspot. Большое спасибо за простое и понятное объяснение. Возможно я задам дурацкий вопрос, но объясните, пожалуйста, подробнее про эту конструкцию:

#define DO_EVERY_V_1(varname,interval,action)  DO_EVERY_V(varname,interval,action)
#define DO_EVERY(interval,action) DO_EVERY_V_1(__LINE__,interval,action)

Почему понадобилось DO_EVERY_V_1? Почему нельзя было сразу написать:

#define DO_EVERY(interval,action) DO_EVERY_V(__LINE__,interval,action)

 
 
 
 
 
 

leshak
Offline
Зарегистрирован: 29.09.2011

pencraft пишет:

Почему понадобилось DO_EVERY_V_1? Почему нельзя было сразу написать:

#define DO_EVERY(interval,action) DO_EVERY_V(__LINE__,interval,action)

 
 

А вы попробуйте так написать :) Не происходит резолвинг  __LINE__ в номер строки

Попробуйте:

#define TEST_V(varname) prefix_##varname=12;
#define TEST_V1(varname) TEST_V(varname)

void setup(){
   TEST_V(__LINE__)
  // TEST_V1(__LINE__)
}

void loop(){
}

Оно не компильнется (специально так сделал) - будет ругатся что "неизвестная переменная". И скажет имя переменной. Так мы сможем увидеть "во что оно развернулось".

TEST_V даст нам ошибку "prefix__LINE__ was not declared in this scope".
TEST_V1 даст нам ошибку "prefix_6" was not declared in this scope".

Как видим, только во втором случае у нас __LINE__ распознался именно как "особое ключевое слово" и мы получили, как и хотели, переменную с уникальным именем. Каждое использование TEST_V1 будем нам генерировать уникальное имя (что-бы можно было DO_EVERY несолько раз использовать).

Почему __LINE__ резолвится/мерджится с префиксом только когда его протаскиваешь через промежуточный макрос - я уже не разбирался. Нашел workaround - и ладно.

pencraft
Offline
Зарегистрирован: 17.01.2012

А вот еще может кому пригодится: выполнение двух действий каждые N миллисекунд, причем с заданным интервалом M миллисекунд  между действиями. Такая задача, в частности, возникает в широко известном примере работы с датчиком DS18B20 когда надо раз в 1 секунду подать команду на измерение температуры, подождать 750мс и дать команду на чтение результата.

В результате нескольких экспериментов было найдено такое решение:

unsigned long prevTime1=0;
unsigned long prevTime2=0;

if(millis()-prevTime1>1000){
prevTime1=millis();
//ДЕЙСТВИЕ 1 выполняется каждые 1000мс
prevTime2=millis();  
}

if(millis()-prevTime2>750){
// ДЕЙСТВИЕ 2 выполняется через 750мс после завершения действия 1
prevTime2=millis();
}

 

gregoryl
Offline
Зарегистрирован: 09.09.2013

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

#define DO_EVERY(interval,action)	\
{									\
	static unsigned long t = 0;		\
	if( millis() - t > interval )	\
	{								\
		{ action; }					\
		t = millis();				\
	}								\
}

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

Если смущает, что имя "t" слишком короткое замените его на что-то заковыристое "__lastTimeOfActionHandling".

leshak
Offline
Зарегистрирован: 29.09.2011

Супер. Спасибо.

Не сообразил что можно сделать "локальную переменную" не объявляя функцию, а просто блоком кода. Привычка от web-разработки, когда частенько приходится в каких-то репитерах генерировать для js-са уникальные ID-хи. Так и тут пошел по пути "генерации уникального имени". 

Но "ваш вариант" - безусловно читабельнее, удобнее в сопровождении и т.д. и т.п. Чуть позже, обязательно, сплагиачу :) его себе и выложу;)

gregoryl
Offline
Зарегистрирован: 09.09.2013

leshak пишет:

Но "ваш вариант" - безусловно читабельнее, удобнее в сопровождении и т.д. и т.п. Чуть позже, обязательно, сплагиачу :) его себе и выложу;)

Этот вариант хорош, только если надо делать что-то маленькое строк на 10 не более.

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

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

Вообщем тут целое непаханное поле для игр :-)

 

leshak
Offline
Зарегистрирован: 29.09.2011

Ну "что-то большое" (для данного контекста) уже есть библиотека SimpleTimer готовая. А тут именно "для мелочей" задумывалось. А "игратся" - согласен можно иного.