Плавное мигание светодиода с изменяемым вручную интервалом мигания. Помогите с кодом.

Efremoff
Offline
Зарегистрирован: 11.02.2015

Приветсвую. Есть задача, которую я как-то не могу сообразить как решить. 

Исходное - ардуино нано, светодиодик, кнопка. 

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

 

Поехали:

Кнопка - измеряет время между двумя нажатиями, и кладет значение в переменную.

void button_work(){

if (digitalRead(button) == HIGH && button_flag ==0) {
  time_counter = millis();
  button_flag = 1;
  }
if (digitalRead(button) == LOW && button_flag == 1){
  button_flag = 2;
  }
if (digitalRead(button) == HIGH && button_flag ==2) {
  int current_millis =  millis();
  interval = current_millis - time_counter;
  button_flag = 3;
   }
if (digitalRead(button) == LOW && button_flag == 3){
  button_flag = 0;
 }
}

Поскольку мне нужно замерять время всегда строго между двумя нажатиями - для этого такая несколько дурацкая конструкция if..else. Но - тем не менее она работает, и переменная interval получает необходимое значение в милисекундах.

Вторая часть - светодиод. Он должен мигать плавно (для этого подключен к выводу PWM), цикл мигания - 0-255-0. Функция должна брать значение переменной interval, и в зависимости от ее значения, длительность цикла мигания (0-255-0) должна быть равна длительности в мс в значении переменной interval. т.е. если у нас значение переменной interval = 1000 - то цикл мигания светодиодиком должен составлять 1с (1000мс). если интервал был посчитан больше или меньше - соответсвенно цикл мигания становится либо короче либо длиннее. Попрбоовал сначала написать 2 цикла for  для мигания, основываясь на родном примере. 

void led_fade(){
  int timeliess = interval/100;
  for (int brightness = 0 ; brightness <= 255; brightness += 5) {
    // sets the value (range from 0 to 255):
    analogWrite(led, brightness);
    // wait some time to see the dimming effect
     delay(timeless);
  }

  // fade out from max to min in increments of 5 points:
  for (int brightness = 255 ; brightness >= 0; brightness -= 5) {
    // sets the value (range from 0 to 255):
    analogWrite(led, brightness);
    // wait some tim to see the dimming effect
    delay(timeless)
  }
}

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

Но за счет использования цикла for и функций delay() - я не имею возможности "нащелкать" нужный интервал с помощью кнопки (в главном лупе стоят 2, выше описаных функции, одна за другой). т.к. когда играет функция с светодиодом - на кнопку нет реакции, и у меня есть только очень короткое время на нажатие кнопки, до того как запустится функция с миганием. соотвесвтенно нужно избавится от циклов (for) и от делеев. А я чот не соображу как.. 

Заранее спасибо за помощь. 

 

UPD: bwn любезно предложил идею

"Идея такая:

void cikl() {

static unsigned long prevTime;
prevTime = millis();

while(flag) {
  if (millis()-prevTime > interval) {
    flag = 0;
  }
  if (digitalRead(knop) == LOW) {
    flag = 0;
    delay(500);
  }
  //Здесь можете вызывать еще какие нибудь функции
 }
}

 

flag и interval глобальные переменные. Чтобы пошел цикл надо сделать флаг в 1 и присвоить значение интервалу. Кнопка на пине через внутренний резистор.

при каждом проходе loop вызываете функцию cikl(), пока флаг равен 0 тут же из нее возвращаетесь. Как только флаг стал 1 (где это произойдет, вам решать), начал работать цикл while и работает до тех пор, пока не достигнет интервала или не нажмется кнопка. Из while можете вызывать функцию вашего светодиода (там по аналогичному таймеру делаете требуемое приращение уровня), в ней for и бесконечных циклов быть не должно. Посчитали, записали новое значение, конец функции. И этот круговорот будет происходить, пока флаг не станет 0.

 

Efremoff
Offline
Зарегистрирован: 11.02.2015

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

 

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

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

Я вам показал простой принцип, а не писал требуемый код.
Имеем задачи:

Отловить нажатие кнопки
Отловить отпускание кнопки
Измерить время.  Это как я понял вы решили.
Изначально писал исходя из того, что по истечении интервала мигание останавливаем. У вас оказаывается иначе.
Значит: 1. Нужна функция фиксирующая время нажатия, расчитывающая приращение и интервалы между приращениями.
2. Функция отрабатывающая общий интервал, контролирующая кнопку и вызывающая функцию светодиода.
3. Функция светодиода контролирующая интервалы между приращениями, изменяющая переменную на значение приращения и записывающая это значение в порт (+контролировать граничные значения).

Получается: loop долбит беспрерывно функцию кнопки - поймали HIGH, подняли флаг, начали счет. Поймали LOW и поднятый флаг, сбросили флаг, посчитали общее время, рассчитали приращение и интервал между приращениями, занесли в переменные.
loop долбит непрерывно функцию светодиода, проверяем интервал между приращениями, по достижении увеличиваем переменную, заносим значение в порт. Достигнув максимального значения начинаем уменьшение до минимального, достигли минимального, начали увеличивать. Понадобится флаг операции - увеличение или уменьшение.
Будет получаться небольшая погрешность. Если недопустимо, то еще отсчитывать общий интервал и обнулять переменную яркости. Как то так. Все реализуется на if, millis и i++

Efremoff
Offline
Зарегистрирован: 11.02.2015

Кнопки, если их вынести отдельно - отлавливаются в loop на ура. все считает и прочее. 

Точно так же функция мигания - тоже отдельно работает как надо. 

Проблема, что когда они в одном лупе - то это выглядит так - "cпрашиваем кнопки"-"Мигаем"-"cпрашиваем кнопки"-"Мигаем"... до бесконечности. Только время отведенное на опрос кнопки составляет 0,01с, а функция мигания (к примеру) 1с. И когда играет функция мигания - я не могу начать нажимать кнопки, т.к. они просто не реагируют. 

Хорошо получается когда я ввожу еще одну кнопку - и тогда делаю обычное условие - 

if (кнопка 2 == HIGH и новый_флаг ==0{

новый_флаг = 1;

button_work((в ней после второго нажатия сбрасываем новый_флаг в 0));

esle {led_fade()}

Но тогда нужно перед тем как начинать считать время нажимать кнопку2, и все равно - когда играет led_fade() - не вклинишься никак... 

Как мне сделать так, чтобы первое нажатие кнопки отлавливало независимо от состояния лупа led_fade()? для этого хорошо бы убрать в принципе for и delay() из кода. Но тогда - как мне мигать плавно(!) светодиодом в заданном промежутке времени?

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

Вы меня похоже не поняли. For-ов  и delay в коде быть не должно. Поймали HIGH запустили счетчик и подняли флаг. Флаг поднят, пока не поймали LOW. Здесь вычислили интервал, рассчитали ваши приращения и интервалы между приращениями (этими значениями будем пользоваться для светодиода), флаг сбросили и ждем следующего HIGH. За это время loop вызовет вашу функцию кнопок миллион раз.
Аналогично вызывается функция светодиода, в нее передаем наши значения интервала и приращения яркости. Как только интервал достигнут, приравняли prevTime  миллису и изменили яркость светодиода и так по кругу. Задержка составляет - время проверки условий и пара операций присвоения. Ни о каких секундах речь не идет. Микросекунды. Почитайте блинк без делей

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

Вот здесь у Лешака мне больше нравится.

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

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

Efremoff, напиши этот код

void led_fade(){
  int timeliess = interval/100;
  for (int brightness = 0 ; brightness <= 255; brightness += 5) {
    // sets the value (range from 0 to 255):
    analogWrite(led, brightness);
    // wait some time to see the dimming effect
     delay(timeless);
  }

без for и без delay, примерно так:

void led_fade()
{
  if (millis()-time>=timeless)
  {
    time = millis();
    analogWrite(led, brightness);
    switch (fade) 
    {
      case 0: if ((brightness+=5) >= 255) fade=1; break;
      case 1: if ((brightness-=5) <= 0) fade=0; break;
    }
  }
}

 

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

По кнопкам:

if (but == HIGH && flag == 0) {
    flag = 1;
    timeCounter = millis()
    }
if (but == LOW && flag ==1) {
   flag = 0;
   interval = millis() - timeCounter;

   Здесь формула расчета интервалов и приращений.
}

Если воспользуетесь тем, как предложил Andy, то получится, timeless = interval/51 (фиксированное приращение 5 единиц)

Efremoff
Offline
Зарегистрирован: 11.02.2015

Спасибо всем. Нажал-начал считать, отпустил-закончил считать - не подходит. должно быть 2 отдельных нажатия. 

попробовал намудрить такой вот код (целиком все):

int button = 12;
int button_flag = 0;
unsigned long interval = 500;
unsigned long time_counter = 0;
int led = 9;           // the PWM pin the LED is attached to
float brightness = 0;    // how bright the LED is
float fadeAmount = 5;    // how many points to fade the LED by
 
// the setup routine runs once when you press reset:
void setup() {
    // declare pin 9 to be an output:
    pinMode(led, OUTPUT);
    pinMode(button,INPUT);
}
 
// the loop routine runs over and over again forever:
void loop() {
    button_work();
    led_fade();
}
 
void led_fade(){
 long time_count = millis();
 brightness = 128+127*cos(2*PI/interval*time_count);
 analogWrite(led, int(brightness));           // sets the value (range from 0 to 255) 
}

void button_work(){
if (digitalRead(button) == HIGH && button_flag ==0) {
  time_counter = millis();
  button_flag = 1;
  }
if (digitalRead(button) == LOW && button_flag == 1){
  button_flag = 2;
  }
if (digitalRead(button) == HIGH && button_flag ==2) {
  int current_millis =  millis();
  interval = current_millis - time_counter;
  button_flag = 3;
   }
if (digitalRead(button) == LOW && button_flag == 3){
  button_flag = 0;
 }
}

очень похоже что работает правильно. еще нужно потестить. 

И интерсный вариант с кейсами - тоже попробую, может будет лучше. 

 

Efremoff
Offline
Зарегистрирован: 11.02.2015

при тестировании такая проблема - 3-5 установкок интервала отрабатывает ок. а вот потом - диод просто начинает светить непрерывно, не реагируя на нажатия в принципе. 

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

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

Efremoff
Offline
Зарегистрирован: 11.02.2015

второй раз не забудут.

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

Efremoff
Offline
Зарегистрирован: 11.02.2015

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

Минус - по истечении какого-то времени происходит переполнение какой-то из переменных, и значение brightness всегда становится 254-255. По крайней мере так рисует монитор порта. 

Efremoff
Offline
Зарегистрирован: 11.02.2015

Кажется я нашел. в функции кнопок во втором нажатии с millis(); работает переменная, которой я по ошибке назначил тип int. и соответсвенно через ~32 секунды она переполняется. 

Efremoff
Offline
Зарегистрирован: 11.02.2015

финальный код:

int button = 12;
int button_flag = 0;
unsigned long interval = 1000;
unsigned long time_counter = 0;
int led = 9;           // the PWM pin the LED is attached to
float brightness = 0; 
int timeless = 0;
int fade;
 
// the setup routine runs once when you press reset:
void setup() {
    // declare pin 9 to be an output:
    pinMode(led, OUTPUT);
    pinMode(button,INPUT);
}
 
// the loop routine runs over and over again forever:
void loop() {
    button_work();
    led_fade();
}
 
void led_fade(){
 
brightness = 128+127*cos(2*3.1415/interval*millis());
brightness = int(brightness);
analogWrite(led, brightness);           // sets the value (range from 0 to 255) 
 
}



void button_work(){

if (digitalRead(button) == HIGH && button_flag ==0) {
  time_counter = millis();
  button_flag = 1;
  }
if (digitalRead(button) == LOW && button_flag == 1){
  button_flag = 2;
  }
if (digitalRead(button) == HIGH && button_flag ==2) {
  unsigned long current_millis =  millis();
  interval = current_millis - time_counter;
  button_flag = 3;
   }
if (digitalRead(button) == LOW && button_flag == 3){
  button_flag = 0;
 }
}

работает корректно. 

Всем спасибо за идеи и направления. 

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

Честно говоря, led_fade все таки сделал бы как у Andy. Формула и короче и понятней получается, ну да дело ваше.

Efremoff
Offline
Зарегистрирован: 11.02.2015

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

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

Для кода, который у вас разницы особо не какой (если только формула верная), если будет большой и нагруженный код, то у Andy отрабатывается только по совпадению условия, иначе сразу возврат. У вас отрабатывается всегда в полном объеме и использует операции с плавающей точкой. Это весьма затратно по времени, но не в данном случае.
И еще смущает int в 26 строке, что он там делает? Эта переменная не должна превышать byte.

Efremoff
Offline
Зарегистрирован: 11.02.2015

Да, Byte там будет куда логичнее. 

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

Скорее всего добавится еще одна кнопка с фиксацией которая будет переключать HIGH-LOW между двуся свободными входами но это уже вопрос условия if-else. Т.е. нажимаем кнопку с фиксацией (которая переключает HIGH c входа напр. 5 на вход 6 - который становится LOW) - и тогда запускаем выше описаный луп с кнопками и миганием. Если же вход 5 становится LOW а вход 6 становится HIGH - то я уже хочу совершенно другие функции запускать. Но это уже совсем другая история, и думаю этими несколькими функциями все и ограничется в данном проекте. 

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

Специально проверил, все работает:

byte brightness;
int timeless = 500;

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

void loop(){
  led_fade(timeless);
}


void led_fade(int timeless)
{
  static unsigned long time;
  static boolean fade;
  if (millis()-time>=timeless)
  {
    time = millis();
    Serial.println(brightness,DEC);
    //analogWrite(led, brightness);
    switch (fade) 
    {
      case 0: if ((brightness+=5) >= 255) fade=1; break;
      case 1: if ((brightness-=5) <= 0) fade=0; break;
    }
  }
}

 

Efremoff
Offline
Зарегистрирован: 11.02.2015

Попробую. видать я что-то с переменными напутал. 

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

ТС, а что этим вычисляется?

brightness = 128+127*cos(2*PI/interval*time_count);

Efremoff
Offline
Зарегистрирован: 11.02.2015

Значение для PWM порта, на основании данных полученных от переменной интервал, и текущего значения в мс. от 0 до 255.

Т.е. скорость заполнения порта от 0 до 255, исходя из значения переменной inteval и текущего значения мс. Чем значение переменной interval больше - тем медленнее происходит насчитывание значение переменной brightness.

Т.е. каждый цикл лупа происходит изменение значения millis(). Соответсвенно каждый раз значение сos Будут менятся от -1 до 1. на это значение мы умножаем 127 (таким образом получаем значения от -127 до +127), к которому мы прибавляем 128 - и в сумме получаем постоянную пульсацию значения переменной от 0 до 255.

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

Efremoff пишет:

Т.е. каждый цикл лупа происходит изменение значения millis(). Соответсвенно каждый раз значение сos Будут менятся от -1 до 1. на это значение мы умножаем 127 (таким образом получаем значения от -127 до +127), к которому мы прибавляем 128 - и в сумме получаем постоянную пульсацию значения переменной от 0 до 255.

это как-то отличается от interval/256 ?

Efremoff
Offline
Зарегистрирован: 11.02.2015

Да. кардинально. )

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

Efremoff пишет:

Да. кардинально. )

там линейная характеристика или специальное что-то для чего-то?