Странная и не логичная работа простейшей программки для управления светодиодами!!!

ivan_vasilevich
Offline
Зарегистрирован: 16.12.2012

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

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

Я пробовал вот такой код (см. ниже) но он работает почему-то неправильно! Обьясните ПОЖАЛУЙСТА, что в нём не так!!! 



int brightness = 0;    // уставливаем начально значение яркости
int fadeAmount = 5;    // шаг приращения/убывания яркости
 
void setup()  {
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
}
 
void loop()  {
  analogWrite(9, brightness);
  analogWrite(10, brightness);
  analogWrite(11, brightness);  
 
  // измением значение в переменной для яркости
  brightness = brightness + fadeAmount;
 
  // при достижение крайних значений для яркости
  // меняем знак переменной шага приращения/убывания яркости 
  if (brightness == 0 || brightness == 255) {
    fadeAmount = -fadeAmount ;
  }    
  // делаем паузу для достижения плавного наращивания/убывания яркости   
  delay(10);                            
}

 

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

В этом примере все нормально и логично - все светодиоды синхронно разгораются и синхронно тухнут.
Вам нужен пример 3.Analog -> Faiding и на базе него написать для нескольких светодиодов.

ivan_vasilevich
Offline
Зарегистрирован: 16.12.2012

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

P.S.:Честно говоря, я не совсем понимаю, как это будет выглядеть, поскольку только начал осваивать С++, и что называется могу "заблудиться в трех соснах"  Подскажите ПОЖАЛУЙСТА верное направление!

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

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

К примеру, если есть два светодиода, можно расписать алгоритм так:

1. Включить LED1

2. Ждать 500 мс

3. Погасить LED1

4. Включить LED2

5. Ждать 500 мс

6. Погасить LED2

7. Ждать 500 мс

8. Повторить с п.1.

Сложно? По этому алгоритму написать даже в тупом варианте не составит труда, подумайте.

Если светодиодов больше, соответственно будет больше пунктов.

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

 

ivan_vasilevich
Offline
Зарегистрирован: 16.12.2012

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

1.Вначале плавно зажигается первый светодиод

2.Затем он плавно гаснет и ПОЧЕМУ-то зажигается не следующий светодиод, а ВСЕ светодиоды! 

Подскажите пожалуйста, что нужно изменить в коде, чтобы исправить эту ошибку ! 













#define LED1 9
#define LED2 10
#define LED3 11
int brightness = 0;
int fadeAmount = 5;

boolean max_prev_br = false;
boolean min_prev_br = false;
void setup()  
{ 
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(LED3, OUTPUT);
} 
void loop()  
{ 
Fadding(LED1, brightness);

if (max_prev_br == true && min_prev_br == true) 
 Fadding(LED2, brightness);

if (max_prev_br == true && min_prev_br == true)
 Fadding(LED3, brightness);

if (max_prev_br == true && min_prev_br == true)return;
}

void Fadding(int LED, int &brightness)
{
analogWrite(LED, brightness);
brightness += fadeAmount;
delay (50);

if (brightness == 255)  {fadeAmount = -fadeAmount; max_prev_br = true; }
if (brightness == 0) {fadeAmount = -fadeAmount; min_prev_br = true; }

}

 

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

Толи вы игнорируете сообщения #1 и #3, толи пример Fading так сложно найти... 

В Arduino IDE меню File(Файл) -> Exemples(Примеры) -> 3.Analog -> Faiding - этот пример использует циклы, собственно говоря то что вам и нужно. Смотрите его и плодите для каждого светодиода:

#define LED1 9
#define LED2 10
#define LED3 11

void setup()
{ 
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(LED3, OUTPUT);
} 

void loop()
{
 for(int fade = 0; fade <= 255; fade++) // зажигаем LED1

  { 
    analogWrite(LED1, fade);            
    delay(10);                            
  } 
  for(int fade = 255 ; fade >= 0; fade--) // тушим LED1

  { 
    analogWrite(LED1, fade);          
    delay(10);                            
  } 
  
   for(int fade = 0; fade <= 255; fade++) // зажигаем LED2

  { 
    analogWrite(LED2, fade);            
    delay(10);                            
  } 
  for(int fade = 255 ; fade >= 0; fade--) // тушим LED2

  { 
    analogWrite(LED2, fade);          
    delay(10);                            
  } 
  
  ........
  
}

Можно не плодить, а вынести в функцию, а в качестве аргумента принимать номер вывода светодиода:

#define LED1 9
#define LED2 10
#define LED3 11

void setup()
{ 
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(LED3, OUTPUT);
} 

void loop()
{
  Fadding(LED1);
  .....
  .....
}

void Fadding(int LED)
{
  for(int fade = 0; fade <= 255; fade++) 
  { 
    analogWrite(LED, fade);            
    delay(10);                            
  } 
  for(int fade = 255 ; fade >= 0; fade--) 
  { 
    analogWrite(LED, fade);          
    delay(10);                            
  } 
}

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

step962
Offline
Зарегистрирован: 23.05.2011

Можно попробовать вот так:



int brightness1 = 0;    // уставливаем начально значение яркости диод1
int brightness2 = 127;    // уставливаем начально значение яркости диод2
int brightness3 = 255;    // уставливаем начально значение яркости диод3
int fadeAmount = 5;    // шаг приращения/убывания яркости
 
void setup()  {
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
}
 
void loop()  {
  analogWrite(9, brightness1);
  analogWrite(10, brightness2);
  analogWrite(11, brightness3);  
 
  // изменяем значение в переменных для яркости. Плавное увеличение яркости,
  // резкое уменьшение, для плавности уменьшения придется и fadeAmount множить.
  // Это выполнимо - оставляю вам в качестве упражнения
  brightness1 = (brightness1 + fadeAmount) & 0xFF;
  brightness2 = (brightness2 + fadeAmount) & 0xFF;
  brightness3 = (brightness3 + fadeAmount) & 0xFF;
 
  // делаем паузу для достижения плавного наращивания/убывания яркости   
  delay(10);                            
}

 

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

& 0xFF; не дает выйти за пределы младшего байта, то есть все равно что объявить byte brightness1 .

ivan_vasilevich
Offline
Зарегистрирован: 16.12.2012




#define LED1 9
#define LED2 10
#define LED3 11
int brightness = 0;
int fadeAmount = 5;
int counter = 1;
boolean max_prev_br = false;
boolean min_br_after_max = false;
void setup()

{ 
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(LED3, OUTPUT);
} 

void loop()
{
  if (counter ==1){Fadding(LED1, brightness);}
  if (counter ==2){Fadding(LED2, brightness);}
  if (counter ==3){Fadding(LED2, brightness);}
  counter =-2;
}

void Fadding(int LED, int &brightness)

  {
analogWrite(LED, brightness);
brightness += fadeAmount;
delay (10);

if (brightness == 255)  {fadeAmount = -fadeAmount; max_prev_br = true; }
if (brightness == 0) {fadeAmount = -fadeAmount; min_br_after_max = true; }
if (max_prev_br == true && min_br_after_max == true) {counter++;}
  }

Большое спасибо, maksim за пример кода с готовой функцией регулировки яркости! К сожалению, я сам не додумался, как её написать и "прикрутить" к управлению несколькими светодиодами.

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

 

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

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

ivan_vasilevich
Offline
Зарегистрирован: 16.12.2012

& 0xFF  Это побитное умножение на "1111 1111" ? Какой в нём смысл, ведь на выходе получиться то же число, что и на входе? Не так ли? Объясните пожалуйста!

 

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

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

Ivan_vasilevich, это зависит от размера переменной, он может быть больше байта.

step962
Offline
Зарегистрирован: 23.05.2011

ivan_vasilevich пишет:

 Какой в нём смысл, 

[...]

kisoft, в общем то, уже ответил на этот вопрос.

Конкретнее, у меня переменные brightnessX были объявлены как integer, то есть занимающими в памяти 2 байта и хранящими значения в диапазоне [-32768...32767].

Инструкция 

brightness3 = (brightness3 + fadeAmount) & 0xFF;

для них равнозначна инструкции

brightness3 = (brightness3 + fadeAmount) & 0x00FF;

В конкретной задаче смысл этой операции - ограничение диапазона изменения яркости: вместо [-32768...32767] получаем [0...255], что, как справедливо заметил maksim, можно реализовать, используя вместо int тип byte.

В практическом смысле так оно и есть. Но не в дидактическом. Ибо освоив на простом примере операции битовой арифметики, можно, немного изменив тип (взявши char), маску (взявши 0x7F), ну и логику вычисления яркости, написать-таки компактный код плавного загорания-затухания диодов.

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

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

 


int brightness = 0;    // уставливаем начально значение яркости
int fadeAmount = 5;    // шаг приращения/убывания яркости
 
byte led=9; // начинаем с первого диода 
void setup()  {
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
}
 
void loop()  {
  analogWrite(led, brightness);
 
  // измением значение в переменной для яркости
  brightness = brightness + fadeAmount;
 
  // при достижение крайних значений для яркости
  // меняем знак переменной шага приращения/убывания яркости 
  if (brightness == 0 || brightness == 255) {
    fadeAmount = -fadeAmount ;
    
    if(brightness==0){ // пора переходить к следующему диоду
        analogWrite(led, 0); // полностью гасим текущий
        led++; // переходим к следующему
        if(led>11)led=9; // если нужно опять начинаем с первого
    }
  }    
  // делаем паузу для достижения плавного наращивания/убывания яркости   
  delay(10);                            
}

 

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

А вот версия не ограниченная поличеством светиков и не требующая что-бы "они были рядом". Просто перечисляем сколько нам нужно в объявлении массива leds.


int brightness = 0;    // уставливаем начально значение яркости
int fadeAmount = 5;    // шаг приращения/убывания яркости

byte leds[]={10,9,11,13} ; // список пинов диодов, будет зажигать именно в этом порядке, добавил еще один диод на 13
byte index=0; // индекс в массиве leds текущего диода

#define TOTAL sizeof(leds)/sizeof(byte)  // вычисляем сколько всего у нас светиков
 

void setup()  {
  for(byte i=0;i<TOTAL;i++)pinMode(leds[i],OUTPUT); // все на выход
}
 
void loop()  {
  analogWrite(leds[index], brightness);
 
  // измением значение в переменной для яркости
  brightness = brightness + fadeAmount;
 
  // при достижение крайних значений для яркости
  // меняем знак переменной шага приращения/убывания яркости 
  if (brightness == 0 || brightness == 255) {
    fadeAmount = -fadeAmount ;
    
    if(brightness==0){ // пора переходить к следующему диоду
        analogWrite(leds[index], 0); // полностью гасим текущий
        index++; // переходим к следующему
        if(index>=TOTAL)index=0; // если нужно опять начинаем с первого
    }
  }    
  // делаем паузу для достижения плавного наращивания/убывания яркости   
  delay(10);                            
}

 

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

leshak, изящно, но ТС придумал неплохой алгоритм, для него было бы лучше допилить его в плане обучения, а потом взять Ваш. ;)
IMHO, разумеется

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

 

kisoft пишет:
leshak, изящно, но ТС придумал неплохой алгоритм,

Так ведь и я взял за базу алгоритм ТС, только из стартового поста

kisoft пишет:
для него было бы лучше допилить его в плане обучения, а потом взять Ваш. ;) IMHO, разумеется

А разобратся почему стартовый не заработал как нужно это не обучение? :)

А похорошему, если обучение, то нужно перебрать и заставить работать все варианты.

По поводу варианта из #8

Строка строка "counter=-2;" явно просит какого-то условия. Вот давайте в уме прокрутим первый же проход loop-а.

counter=1; Вызвали fadding, начали плавно разгоратся. Каутер не менялся. И тут - трах-бабах.counter =-2;

Теперь counter=-1, на следующем проходе -3 - что есть нонсенс. Явно уменьшать каунтер нужно только "в определенный момент", а не всегда.

Далее. Зачем на копипасты

if (counter ==1){Fadding(LED1, brightness);}
if (counter ==2){Fadding(LED2, brightness);}
if (counter ==3){Fadding(LED2, brightness);}

?  Как источник потенциальных ошибок? (кстати уже очепятнулись. LED2 - дважды, а LED3 - потеряли).

Раз у нас диоды по порядку, то можно сделать Fadding(LED1+counter-1, brightness);  Без всяких if-и проч.

А если counter базировать на нуле, а не единцице, то Fadding(LED1+counter, brightness);

Next: крайне плохая идея использовать для глобальной переменной и локальной одно и тоже имя.  Я про brightness. Это череповато часами проведенными в отладке в попытке понять "ну почему". Меняю переменную, а она не меняется :) Так что внутри функции можно ее до bright сократить. Тогда четко будете видеть что вы меняете. 

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

Next: пинов и яркостей с номерами больше 255 - не бывает (как и отрицательных). Поэтому стоит заглянуть в http://arduino.ru/Reference Найти раздел "типы данных" и прочитать их все. И применять тот который "минимально подходит". В данном случае это не int, а byte

ivan_vasilevich
Offline
Зарегистрирован: 16.12.2012

leshak пишет:

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

Извините за возможно глупый вопрос, но я не понял что значит "передавать параметром". Напишите ПОЖАЛУЙСТА!!! Если конечно Вас это не затруднит.

maksim
Offline
Зарегистрирован: 12.02.2012
Fadding(LED1, brightness);
Функция(параметр, параметр);

 

ivan_vasilevich
Offline
Зарегистрирован: 16.12.2012

А если написать вот так (имею ввиду с закорючкой &),  в чём будет разница? Объясните пожалуйста!





void Fadding(int LED, int &brightness)

 

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

leshak пишет:


int brightness = 0;    // уставливаем начально значение яркости
int fadeAmount = 5;    // шаг приращения/убывания яркости
 
byte led=9; // начинаем с первого диода 
void setup()  {
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
}
 
void loop()  {
  analogWrite(led, brightness);
 
  // измением значение в переменной для яркости
  brightness = brightness + fadeAmount;
 
  // при достижение крайних значений для яркости
  // меняем знак переменной шага приращения/убывания яркости 
  if (brightness == 0 || brightness == 255) {
    fadeAmount = -fadeAmount ;
    
    if(brightness==0){ // пора переходить к следующему диоду
        analogWrite(led, 0); // полностью гасим текущий
        led++; // переходим к следующему
        if(led>11)led=9; // если нужно опять начинаем с первого
    }
  }    
  // делаем паузу для достижения плавного наращивания/убывания яркости   
  delay(10);                            
}

 

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

if(brightness==0){ // пора переходить к следующему диоду
        analogWrite(led, 0); // полностью гасим текущий
        led++; // переходим к следующему
        if(led>11) led --; //  ????? начинаем обратный отсчет???

По идее, огонек ведь должен побежать назад! Но почему-то он останавливается  (LED3 мигает)! Не понимаю, почему? Объясните ПОЖАЛУЙСТА!

 

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

Самое быстрое - взять пример из сообщения #14, там где я пины запихнул в массив, и просто заполнитье его так:

byte leds[]={9,10,11,10} 

 

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

ivan_vasilevich пишет:

По идее, огонек ведь должен побежать назад!

С какого испуга? led>11, выполняет при led==12. Сделали вы ему led--. Он стал опять 11. Проше свой цикл разгорания, стал 12, вы опять уменьшили на единицу, стал 11 :)

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

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

Ведь вы, фактически тоже самое делается с яркостью (brightness). Сначала увеличиваете до какого-то предела, потом уменьшаете в ноль. И опять заново.

ivan_vasilevich
Offline
Зарегистрирован: 16.12.2012

leshak пишет:

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

Да, Вы абсолютно правы. Вот этот фрагмент кода:

if (brightness == 0 || brightness == 255) {fadeAmount = -fadeAmount ;

По аналогии с этим я пробовал  написать и так

if (led ==11 || led ==9) !led++;
 

и так:

if (led ==11 || led ==9) -led++;

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

int brightness = 0;    // уставливаем начально значение яркости
int fadeAmount = 5;    // шаг приращения/убывания яркости
int ledAmount = 1; 
byte led=9; // начинаем с первого диода 
void setup()  {
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
}
 
void loop()  {
  analogWrite(led, brightness);
 
  // измением значение в переменной для яркости
  brightness = brightness + fadeAmount;
 
  // при достижение крайних значений для яркости
  // меняем знак переменной шага приращения/убывания яркости 
  if (brightness == 0 || brightness == 255) {
    fadeAmount = -fadeAmount ;
    
    if(brightness==0){ // пора переходить к следующему диоду
        analogWrite(led, 0); // полностью гасим текущий
        led = led + ledAmount; // переходим к следующему
        if (led ==11 || led ==9) ledAmount=-ledAmount; // меняем знак переменной
    }
  }    
  // делаем паузу для достижения плавного наращивания/убывания яркости   
  delay(10);                            
}

 

Только, что попробовал и удивился - наконец-то заработало! Но всё-таки мне интересно, можно ли было здесь обойтись без ввода ещё одной переменной? имею ввиду ledAmount

step962
Offline
Зарегистрирован: 23.05.2011

ivan_vasilevich пишет:

Но всё-таки мне интересно, можно ли было здесь обойтись без ввода ещё одной переменной? имею ввиду ledAmount

А почему бы и нет - вынесите все то, что сейчас в loop'е, в отдельную функцию, выбросив все лишнее (логику определения  направления движения волны) и адаптировав оставшиеся переменные/константы под параметры функции, а в loop'е поставьте два последовательных вывода этой функции - один раз с прямым ходом, один раз с обратным. Это может выглядеть, например, так:

doWave(9,11,1);   // гоним волну вправо
doWave(11,9,-1);  // а теперь влево

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

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

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

ivan_vasilevich пишет:
leshak пишет:
Кстати пример как сделать что-бы переменная led сначала увеличивалась, а потом уменьшалась (а не прыгала в ноль) - у вас перед глазами.
Да, Вы абсолютно правы. Вот этот фрагмент кода: if (brightness == 0 || brightness == 255) {fadeAmount = -fadeAmount ; По аналогии с этим я пробовал написать и так if (led ==11 || led ==9) !led++; и так: if (led ==11 || led ==9) -led++;
И где аналогия? Там у нас две переменных: сама яркость и шаг. А тут одна. Аналогии нет. Поэтому ничего и не получается. Ну не говоря о том что не очень понятно что вы ждали от конструкции вида вида !led++ Как ни крути а конструкция if (led ==11 || led ==9) будет срабатывать только на крайних диодах, а вам нужно сдвигать led в правильную сторону на среднем. То есть на крайних вам нужно ЗАПОМНИТЬ куда led будет двигатся дальше. Заведите себе привычку: пытайтесь сами, в голове выполнить вначале этот код. Представте какие значения имеют переменны при первом проходе, при втором. Какое значение примет led, если оно равно 11 и выполнится -led++ Что будет в самом led после этого? Куда пойдет значение этого выражения (или не пойдет?). Или от обратного: представте себя "тупым подчиненным" и вам нужна пошаговая инструкция что-бы правильно зажигать светики. Начинайте составлять такую инструкцию и смотрите, на каждом шаге, какия информация вам требуется что-бы правильного его выполнить. Нужен номер текущего светика? Нужно знать куда мы двигаемся вверх или вниз? Нужно знать сколько всего светиков? Нужно знать максимальный минимальный номер? Нужно знать подряд они или нет? и т.п. Если "вроде представил, написал, а работает не так как представил", то начинаем натыкивать Serial.println(led); Например перед "if (led ==11 || led ==9) !led++;" и сразу после. Смотрим что эта строка делает с нашим led в зависимости от его начального значения. Смотреть где и как он меняется со временем. Ищем расхождения между реальностью и нашей ментальной моделью. И исправляем что-то одно из двух :)
ivan_vasilevich пишет:
Но всё равно ничего не получается! Пришлось создать ещё одну переменную ledAmount для того, чтобы можно было переворачивать её значение! Только, что попробовал и удивился - наконец-то заработало!
А что удивительного? Теперь аналогия имеется. Если работает для яркости, почему же должно не работать для нумерации.
ivan_vasilevich пишет:
Но всё-таки мне интересно, можно ли было здесь обойтись без ввода ещё одной переменной? имею ввиду ledAmount
Учебный ответ: нельзя. Более того - не нужно. Если у вас в описании алгоритма/рассуждений/объяснений/размышлений поведения присутсвует понятие "шаг приращения"/"направление волны"/"фаза выполнения", то оно должно быть отражено в коде. Его можно как-то хитро "замаскировать" (в неявном виде оно все равно будет присутствовать, раз алгоритм его включает), но ничего кроме "вонючего кода" это вам не даст. Он будет менее читабелен и трудно-поддерживаемым (кем-то другими или вами-же через пол-года, когда забудете "что вы тут имели ввиду"). Более того, частенько их нужно вводить искуственно! Потратить пару байт памяти, но сделать код более понятным. Например у нас есть какие-то значения датчиков a1,a2,a3,a4,a5 и мы вычисляем, с помощью перемножения между собой и на какие коэфициенты кинетическую энергию тела. Код типа

m=1.23*(a1+a3);
v=(a2*a4+15)/sqrt(a5);
e= m*sq(v)/2 ; // sq() - возведение в квадрат

будет гораздо лучше чем более "экономный" вариант, без лишних переменных и вызова функций:

e= 1.23*(a1+a3) * ((a2*a4+15)/sqrt(a5)) * ((a2*a4+15)/sqrt(a5)) / 2; 

В первом случае беглым взглядом видно "где, что", да банально сама формула e=(m*v^2)/2 легко узнаваема любым текхническим экспертом который работает с вами и даже не знает програмирования. Теоретически ответ: можно попытатся извратится без переменной. Воспользоватся каким-то свойствами чисел, тем как они хранятся в памяти. Как-то заюзать переполнения, отрицательные числа и т.п. Но... это будут "чистые выебосы". Ког будет из серии "фиг поймешь" и экономии памяти/шагов все равно не будет (даже наоборот). Отказатся от переменной можно и поменяв сам алгоритм. Если мы возьмем подход не содержащий понятия "шаг приращения"/"направления". Собственно выше я как раз и дал пример - поместить номера led-дов в массив. И зажигать их по очереде. Если мы составим очередь 9,10,11,10, то визуально оно нам даст "туда и обратно" (за счет повторения 10-тки), хотя в реальности массив проходится только в одну сторону. В переменных мы тут "выиграли", а вот в памяти - нет (еще один байт на дополнительную десятку. а если пинов будет больше, то даже проиграем). Но, подход с хранением пинов в массиве, а бегание "индексом массива" - IMHO более кошерный в любом случае. Чем изменение самой переменной led. Уходит привязка к нумерации пинов. Код становится более переносим. Можно задавать не только линейное поведение. Да банально если у будет 20-ть пинов, то включить их на выход можно будет обыкновенным for, а не выписывать 20-ть pinMode Так что я рекомендовал бы вам, в учебных целях. Взять пример из #14 и допилить его до "туда-обратно", без дублирования 10-тки, в массиве leds.

P.S. Но вы молодец!!! Потому то что дочитали мою "простыню"  до конца :)  И "трижды молодец" что дожали свой пример.

ivan_vasilevich
Offline
Зарегистрирован: 16.12.2012

step962 пишет:

ivan_vasilevich пишет:

Но всё-таки мне интересно, можно ли было здесь обойтись без ввода ещё одной переменной? имею ввиду ledAmount

А почему бы и нет - вынесите все то, что сейчас в loop'е, в отдельную функцию, выбросив все лишнее (логику определения  направления движения волны) и адаптировав оставшиеся переменные/константы под параметры функции, а в loop'е поставьте два последовательных вывода этой функции - один раз с прямым ходом, один раз с обратным. Это может выглядеть, например, так:

doWave(9,11,1);   // гоним волну вправо
doWave(11,9,-1);  // а теперь влево

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

Это весьма неплохая идея! Оцените пожалуйста код, написанный  на её основе!

int brightness = 0;    // уставливаем начально значение яркости
int fadeAmount = 5;    // шаг приращения/убывания яркости
int ledAmount;

void setup()  {
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
}

void loop()
{
  doWave(9,11,1);   // гоним волну вправо
  doWave(11,9,-1);  // а теперь влево
}


void doWave(int Start_led, int End_Led, int ledAmount) 
{
  for(Start_led; Start_led != End_Led; Start_led + ledAmount)
  {
    analogWrite(Start_led, brightness);
    brightness = brightness + fadeAmount;
    delay(10); //делаем паузу для достижения плавного наращивания/убывания яркости  
    if (brightness == 0 || brightness == 255)fadeAmount = -fadeAmount;

    if(brightness==0) { // пора переходить к следующему диоду
      analogWrite(Start_led, 0); // полностью гасим текущий
      Start_led +=ledAmount; // переходим к следующему
    }
  }

 Мне вот не нравится, что здесь в 20 и 29 строках Start_led + ledAmount фактически повторяется! Можно ли избавиться от такой записи (имею ввиду используя тот де цикл for, что я и использовал)???

 

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

20 строка потрясает :)



for(;Start_led != End_Led;)

Это точно тоже самое, только без "мишуры".

 

step962
Offline
Зарегистрирован: 23.05.2011

Позволю себе чуть модифицировать текст вашей функции:

void doWave(int Start_led, int End_Led, int ledAmount) {
  int cur_led = Start_led;
  do {
    for(int brightness=0;brightness<=255;brightness += brightnessamount) {
      analogWrite(cur_led, brightness);
      delay(10); //делаем паузу для достижения плавного наращивания/убывания яркости
    }
    for(int brightness=255-brightnessamount;brightness>=0;brightness -= brightnessamount) {
      analogWrite(cur_led, brightness);
      delay(10); //делаем паузу для достижения плавного наращивания/убывания яркости
    }
    cur_led += ledAmount;
  } while(​cur_led != End_Led);
}

Ну и

1) Крутить цикл по светодиодам с помощью for не получится - последний светодиод не будет отрабатываться (как только переменная цикла станет равной end_led, сразу же выполнится условие выхода из цикла). Можно было бы попытаться извратиться и придумать какое-либо более сложное условие окончания цикла, но зачем, если есть конструкция do-while - с проверкой после прохождения тела цикла? (не уверен, что синтаксически правильно употребил ее - редко приходится использовать)

2) brightnessamount должна быть объявлена глобальной переменной. А еще лучше - передана параметром

3) два цикла - продолжая путешествие в дебри структурного программирования - тоже просятся быть оформленными в виде функций

ivan_vasilevich
Offline
Зарегистрирован: 16.12.2012

leshak пишет:

А вот версия не ограниченная поличеством светиков и не требующая что-бы "они были рядом". Просто перечисляем сколько нам нужно в объявлении массива leds.


int brightness = 0;    // уставливаем начально значение яркости
int fadeAmount = 5;    // шаг приращения/убывания яркости

byte leds[]={10,9,11,13} ; // список пинов диодов, будет зажигать именно в этом порядке, добавил еще один диод на 13
byte index=0; // индекс в массиве leds текущего диода

#define TOTAL sizeof(leds)/sizeof(byte)  // вычисляем сколько всего у нас светиков
 

void setup()  {
  for(byte i=0;i<TOTAL;i++)pinMode(leds[i],OUTPUT); // все на выход
}
 
void loop()  {
  analogWrite(leds[index], brightness);
 
  // измением значение в переменной для яркости
  brightness = brightness + fadeAmount;
 
  // при достижение крайних значений для яркости
  // меняем знак переменной шага приращения/убывания яркости 
  if (brightness == 0 || brightness == 255) {
    fadeAmount = -fadeAmount ;
    
    if(brightness==0){ // пора переходить к следующему диоду
        analogWrite(leds[index], 0); // полностью гасим текущий
        index++; // переходим к следующему
        if(index>=TOTAL)index=0; // если нужно опять начинаем с первого
    }
  }    
  // делаем паузу для достижения плавного наращивания/убывания яркости   
  delay(10);                            
}

Объясните пожалуйста, как работает в этом коде вот такая строка

#define TOTAL sizeof(leds)/sizeof(byte)  // вычисляем сколько всего у нас светиков

Я лишь понял, что TOTAL присваивается результат от деления sizeof(leds)/sizeof(byte), а откуда  берутся sizeof(leds) и sizeof(byte) ??? Напишите пожалуйста!

 

ivan_vasilevich
Offline
Зарегистрирован: 16.12.2012

step962 пишет:

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

Да, Вы совершенно правы,  к сожалению я только-что это понял!

step962 пишет:

Можно было бы попытаться извратиться и придумать какое-либо более сложное условие окончания цикла, но зачем, если есть конструкция do-while - с проверкой после прохождения тела цикла?

Задать такое условие для  выхода из этого цикла не так-то просто! Тут есть над чем покумекать! Операторы сравнения <= и => тут наверняка бесполезны, так как отсчет номеров светодиодов при вызовах функции

doWave(9,11,1);   // гоним волну вправо
doWave(11,9,-1);  // а теперь влево

идет в разных направлениях ! Я умудрился прикрутить !=  Но,оказывается, если написать, что-то вроде

for(; Current_Led != End_Led;)

то последний по счету светодиод действительно не попадает в цикл!

Неужели действительно с помощью цикла for  (его туда  не запихнуть) и эта задача нерешаема? Пожалуйста напишите, кто знает!

step962
Offline
Зарегистрирован: 23.05.2011

Ну, извратиться все же можно. И разными способами.

Вот так:

doWave(9,12,1);   // гоним волну вправо (от 9-го до 11-го светодиода)
doWave(11,8,-1);  // а теперь влево (от 11-го до 9-го светодиода)

Или (уже в теле функции и используя ваш стиль представления заголовков циклов) вот так:

for(; Current_Led != End_Led+ledamount;)

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

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

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

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

ivan_vasilevich пишет:

Я лишь понял, что TOTAL присваивается результат от деления sizeof(leds)/sizeof(byte), а откуда  берутся sizeof(leds) и sizeof(byte) ??? Напишите пожалуйста!

Не совсем верно.

Было бы неплохо загуглить что такое ключевое слово #define  (искать скорее в справочниках по C/C++)

TOTAL это не переменная, а "определение". Под нее не выделяется память и проч.

Работает она примерно так: На этапе КОМПИЛЯЦИИ, а не выполнения, везде где попадется TOTAL, в тексте программы, он будет заменен на sizeof(leds)/sizeof(byte)   и лиш потом будет произведена компиляция проги.

То есть елс ибы напишем код вида

#define LED 13
....
digitalWrite(LED,HIGH);

То, с точки зрения компилятора это будет ИДЕНТИЧНО, если бы мы написали сразу digitalWrite(13,HIGH);

Под LED не будет выделятся память, для хранения переменной, при вызове функции не будут уходить такты на "прочитать переменную".

sizeof  (опять-таки по хорошему нужно почитать справочник).

Ничто "ниоткуда" не берется. sizeof - встроенный в язык оператор. Который возвращает размер объекта или типа.

Когда я написал byte leds[] - я не указал компилятору какого размера этот массив. Сказал "смотри сам по тому сколько элементов я перечислю при инициализации. Он подсчитал и выделил соотсвествующие количество памяти.

Но мне самому нужно знать "сколько там элементов" (а пальцем считать не хочется). Поэтому я беру sizeof(leds) и он говорит мне, сколько же памяти компилятор выделил под массив leds  (памяти!, а не "сколько там элементов").

Что бы узнать "сколько там элементов", мне нужно, теперь, количество выделенной памяти, поделить на "сколько памяти занимает один элемент". Про это мне доложит sizeof(byte)   (массив-то байтов был. если бы был массив элементов другого типа, я бы писал sizeof(ТИП_ЭЛЕМЕНТА)

Ну, то есть теоретически деление на sizeof(byte) можн было-бы и опустить. Я и так знаю что байт занимает 1 байт :)

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

А вообщем можно, спокойно написать

#define TOTAL 3

И не морочится, с sizeof. Но, тогда будет нужно не забыть подправлять его если мы меняем количество светиков в leds[].  Я предпочитаю возложить эту рутину на компилятор.

 

ivan_vasilevich
Offline
Зарегистрирован: 16.12.2012

step962 пишет:

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

Полностью согласен! Ваш вариант с циклом "do while" намного лучше! Только что попробовал его заюзать. Получился такой вот код:

int brightness = 0;    // уставливаем начальное значение яркости
int brightnessamount;   // шаг приращения/убывания яркости
int ledAmount = 1; 

void setup()  
{
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
}

void loop()
{
  doWave(9,11,1);   // гоним волну вправо
  doWave(11,9,-1);  // а теперь влево
}

void doWave(int Start_led, int End_Led, int ledAmount) {
  int cur_led = Start_led;
  do {
    for(int brightness=0;brightness<=255;brightness += brightnessamount) {
      analogWrite(cur_led, brightness);
      delay(10); //делаем паузу для достижения плавного наращивания/убывания яркости
    }
    for(int brightness=255-brightnessamount;brightness>=0;brightness -= brightnessamount) {
      analogWrite(cur_led, brightness);
      delay(10); //делаем паузу для достижения плавного наращивания/убывания яркости
    }
    analogWrite(cur_led, brightness); //полностью гасим текущий светодиод 
    cur_led += ledAmount;             //переходим к следующему
  } 
  while(​cur_led != End_Led);
}

Но компилятор "ругается", выделяя желтым цветом 32 строку: while(​cur_led != End_Led);  И выдает надпись:

stray '\' in program

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

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

brightnessamount не проинициализирована, последний светодиод по этому алгоритму работать не будет, об этом уже говорили. Почему ошибка - не вижу, мелкий шрифт на телефоне.
И удалите строку 3.

ivan_vasilevich
Offline
Зарегистрирован: 16.12.2012

kisoft пишет:
brightnessamount не проинициализирована...

Да. Вы правы!

kisoft пишет:
И удалите строку 3.

Да, действительно она здесь лишняя!

kisoft пишет:

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

Посмотрите пожалуйста внимательнее! Здесь использован цикл "do...while" (не "while").

Условие проверяется в конце цикла! http://arduino.ru/Reference/DoWhile

Подправил код, учитывая Ваши замечания. Получилось так:

int brightness = 0;    // уставливаем начальное значение яркости
int brightnessamount = 5;   // шаг приращения/убывания яркости
 
void setup()  
{
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
}

void loop()
{
  doWave(9,11,1);   // гоним волну вправо
  doWave(11,9,-1);  // а теперь влево
}

void doWave(int Start_led, int End_Led, int ledAmount) {
  int cur_led = Start_led;
  do {
    for(int brightness=0;brightness<=255;brightness += brightnessamount) {
      analogWrite(cur_led, brightness);
      delay(10); //делаем паузу для достижения плавного наращивания/убывания яркости
    }
    for(int brightness=255-brightnessamount;brightness>=0;brightness -= brightnessamount) {
      analogWrite(cur_led, brightness);
      delay(10); //делаем паузу для достижения плавного наращивания/убывания яркости
    }
    analogWrite(cur_led, brightness); //полностью гасим текущий светодиод 
    cur_led += ledAmount;             //переходим к следующему
  } 
  while(​cur_led != End_Led);
}

Но компилятор всё равно ругается:

выделяет желтым цветом строку 31: while(​cur_led != End_Led);  И выдает надпись:

stray '\' in program
leshak
Offline
Зарегистрирован: 29.09.2011

Это очень злобная ошибка. Серьезно.

while([ТУТ ВКРАЛИСЬ НЕВИДИМЫЕ СИМВОЛЫ]cur_led

Встанте так что-бы курсор мигал между c и u d слове cur_led. Нажимите один раз "стерлка влево", далее нажимайте несклько раз Backspace пока не сотрется круглая скобка. Наберите ее опять. Компилируйте.

Ну или, тупо, сотрите все эту строку и наберите заново.

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

Вот поправленный компилирующийся вариант. Глазами отличий не найдете, только HEX редактором.

 

int brightness = 0;    // уставливаем начальное значение яркости
int brightnessamount = 5;   // шаг приращения/убывания яркости
 
void setup()  
{
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
}

void loop()
{
  doWave(9,11,1);   // гоним волну вправо
  doWave(11,9,-1);  // а теперь влево
}

void doWave(int Start_led, int End_Led, int ledAmount) {
  int cur_led = Start_led;
  do {
    for(int brightness=0;brightness<=255;brightness += brightnessamount) {
      analogWrite(cur_led, brightness);
      delay(10); //делаем паузу для достижения плавного наращивания/убывания яркости
    }
    for(int brightness=255-brightnessamount;brightness>=0;brightness -= brightnessamount) {
      analogWrite(cur_led, brightness);
      delay(10); //делаем паузу для достижения плавного наращивания/убывания яркости
    }
    analogWrite(cur_led, brightness); //полностью гасим текущий светодиод 
    cur_led += ledAmount;             //переходим к следующему
  } 
  while(cur_led != End_Led);
}

offtop: ну почему везде тип int? Ну нельзя так привыкать.

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

29 строка, пусть cur_led = 10, прибавляем 1, получаем 11

31 строка while( 11 != 11 ) уже не выполняется, значит 11 светодиод не будет работать, а в обратную сторону - 9-ый.

Это я к тому, что не надо заблуждаться.

Вцелом, конечно же всё будет корректно работать, на первом проходе сработают светодиоды 9 и 10 (doWave(9,11,1)), а на обратном пути 11 и 10 (doWave(11,9,-1)).

Поэтому просьба не говорить, что здесь do while, а не while, разницы не будет, поверьте и проверьте.

 

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

Кстати, хорошо что с do-while для учебы разобрались. Но "бегать по порядку", обычно применяют for.

for(int cur_led = Start_led; cur_led!=End_Led; cur_led+=LedAmound){
  ..... внутри тоже самое....
}

Дела ло бы ровно тоже самое (скорее всего в машинные коды даже скомпилится так же).

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

Чисто из спортивного интереса. Разгораемся и гаснем без используя только однку переменную. Без направления шага.

 

#define LED 13
void setup(){ pinMode(LED,OUTPUT);}
void loop(){ fadeLed(LED);}

void fadeLed(byte ledPin){ // разгорает и гасит ledPin
   for(byte x=0;x<255;x++){
     byte brightness=256*sin(x*3.14/254); // синусойдочка 
     analogWrite(ledPin,brightness);
     delay(5);
   }
}

Это не пример "как нужно делать". Просто "игры ума" :)

ivan_vasilevich
Offline
Зарегистрирован: 16.12.2012

kisoft пишет:

Поэтому просьба не говорить, что здесь do while, а не while, разницы не будет, поверьте и проверьте.

Да действительно! Вы правы! Последний по счету светодиод не срабатывает! А я думал, что используя "do while" вместо "while" он будет работать!

Может быть, я неправильно использовал этот цикл? Или даже с помощью "do while" эта задача нерешаемая?

Подскажите ПОЖАЛУЙСТА!

 

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

cur_led != (End_Led+ledAmount)

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

ivan_vasilevich пишет:

Может быть, я неправильно использовал этот цикл? Или даже с помощью "do while" эта задача нерешаемая?

Все конструкции циклов между собой взаимозаменяемы. А for и do-while так вообще практически эквиваленты. Используются скорее "что привычней" и "что в данном случае легчи читаться будет".

Так что танцевать нужно не от "взять правильный цикл", а от "какое у нас условие выхода из цикла", нужно решить "когда пора прекращать".

 

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

Попробуйте

void setup()  
{
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
}

void loop()
{
  doWave(9,11,1);   // гоним волну вправо
  doWave(11,9,-1);  // а теперь влево
}

void doWave(byte start_led, byte end_led, byte amount) {
  for(byte led=start_led; led!=end_led+amount;led+=amount)fadeLed(led);
}

void fadeLed(byte ledPin){ // разгорает и гасит ledPin
   for(byte x=0;x<255;x++){
     byte brightness=256*sin(x*3.14/254); // синусойдочка 
     analogWrite(ledPin,brightness);
     delay(5);
   }
}

 

ivan_vasilevich
Offline
Зарегистрирован: 16.12.2012

leshak пишет:

Это очень злобная ошибка. Серьезно.

while([ТУТ ВКРАЛИСЬ НЕВИДИМЫЕ СИМВОЛЫ]cur_led

Без Вашей помощи я б точно сам не нашел! Большое спасибо!  Интересно, а КАК нашли эти скрытые символы Вы???  Напишите пожалуйста!

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

С синусойдой я перемудрил. Можно проще (и не так тяжело для проца, как sin и операции с плавающими точками)

void fadeLed(byte ledPin){ // разгорает и гасит ledPin
   for(byte x=0;x<255;x++){
     byte brightness= 254-2*abs(x-127);  // bright будет менятся от 0 до 255 и назад до 0. abs(x) - это модуль числа
     analogWrite(ledPin,brightness);
     delay(5);
   }
}

 

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

ivan_vasilevich пишет:

leshak пишет:

Это очень злобная ошибка. Серьезно.

while([ТУТ ВКРАЛИСЬ НЕВИДИМЫЕ СИМВОЛЫ]cur_led

Без Вашей помощи я б точно сам не нашел! Большое спасибо!  Интересно, а КАК нашли эти скрытые символы Вы???  Напишите пожалуйста!

Просто большой опыт бега по граблям :) Кричало (чуть ниже) что не определена перменная которой явно нет в коде. Ну значит явно "что-то с кодировками". Перенабрал руками строку - заработало. Ну а дальше "просто что-бы убедится" открыл hex редактором и посмотрел побайтово. Искал что-то типа "русскую букву 'Эc' вместо английской 'Си' и т.п.), коды символов меньше 32 и т.п.

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

Применив тот же подход к номеру led-а, можно "ужать-извратится" всю задачу целиком (разгоратся по очереди, туда и обратно), до такого

void setup(){for(byte pin=9;pin<=11;pin++)  pinMode(pin, OUTPUT);}
void loop(){
   for(byte i=0;i<4;i++) for(byte x=0;x<255;x++){
        analogWrite(11-abs(i-2),254-2*abs(x-127));
        delay(5);
    }
}

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