Сложные арифметические выражения

Тема в разделе "Arduino & Shields", создана пользователем barmaley_74, 4 ноя 2018.

  1. barmaley_74

    barmaley_74 Нуб

    Задача простая. Необходимо при достижении граничного уровня освещения включить ночник, причем включать его тем ярче чемь темнее становится. К примеру на вход А0 подключен датчик уровня света, то есть имеем значение от 0 до 1023 уровня освещения. Установим уровень включения ночника равным к примеру 800, то есть при падении уровня освещения ниже 800 ночник должен включаться. Причем с падением освещения от 800 до 0, яркость ночника должна подниматься от 0 до 255.

    Хотел решить данную задачу вот так:
    Код (C++):
      int lightness = analogRead(A0);       // уровень освещённости
      int level_1   = analogRead(A1);       // потенциометр задающий уровень включения ночника
      int level_11=0;                                  // уровень свечения светильника
      if (lightness < level_1) level_11 = (((level_1-lightness))/(level_1))*255;
    выражение чисто арифметически написано правильно, но на выходе не получаю значение от 0 до 255, а какую то аххниею! Почему так?

    Вот полностью программа:

    Код (C++):
    #include <Wire.h>
    #include <LiquidCrystal_I2C.h>
    LiquidCrystal_I2C lcd(0x27, 16, 2); // Устанавливаем дисплей
    #define LED_PIN1 9
    #define LED_PIN2 10
    #define LED_PIN3 11
    #define LDR_PIN A0
    #define POT_PIN A1
    void setup()
    {
      lcd.init();
      lcd.backlight();// Включаем подсветку дисплея
      pinMode(LED_PIN1, OUTPUT);pinMode(LED_PIN2, OUTPUT);pinMode(LED_PIN3, OUTPUT);
    }

    void loop()
    { int level_11=0;int level_21=0;int level_31=0;int temp=0;
      int lightness = analogRead(LDR_PIN);       // уровень освещённости
      int level_1   = analogRead(POT_PIN);       // потенциометр
      int level_2   = (level_1/3)*2;
      int level_3   =  level_1/3;
      if (lightness < level_1) { temp=level_1*255; level_11 = (((level_1-lightness)*255)/(level_1*255));   // level_11 = (((level_1-lightness)*255)/(level_1*255));
         if (level_11>255) level_11 = 255; }
      if (lightness < level_2) { level_21 = level_2-lightness; if (level_21>255) level_21 = 255; }
      if (lightness < level_3) { level_31 = level_3-lightness; if (level_31>255) level_31 = 255; }

      if (lightness < level_3)   {analogWrite(LED_PIN1, level_11);analogWrite(LED_PIN2, level_21);analogWrite(LED_PIN3, level_31);}
      else    
      { if (lightness < level_2) {analogWrite(LED_PIN1, level_11);analogWrite(LED_PIN2, level_21);digitalWrite(LED_PIN3, LOW);}
        else
        { if (lightness < level_1) {analogWrite(LED_PIN1, level_11);digitalWrite(LED_PIN2, LOW);digitalWrite(LED_PIN3, LOW);}
          else {digitalWrite(LED_PIN1, LOW);digitalWrite(LED_PIN2, LOW);digitalWrite(LED_PIN3, LOW);}    
        }
      }

    // Блок вывода на дисплей
      if (lightness < 10)                     {lcd.setCursor(0, 1);lcd.print(" ");lcd.setCursor(1, 1);lcd.print(" ");lcd.setCursor(2, 1);lcd.print(" ");lcd.setCursor(3, 1);}
      if (lightness > 9  && lightness < 100 ) {lcd.setCursor(0, 1);lcd.print(" ");lcd.setCursor(1, 1);lcd.print(" ");lcd.setCursor(2, 1);}
      if (lightness > 99 && lightness < 1000) {lcd.setCursor(0, 1);lcd.print(" ");lcd.setCursor(1, 1);}
      if (lightness > 999)                     lcd.setCursor(0, 1);lcd.print(lightness);
      if (level_1   < 10)                     {lcd.setCursor(5, 1);lcd.print(" ");lcd.setCursor(6, 1);lcd.print(" ");lcd.setCursor(7, 1);lcd.print(" ");lcd.setCursor(8, 1);}
      if (level_1   > 9  && level_1   <  100) {lcd.setCursor(5, 1);lcd.print(" ");lcd.setCursor(6, 1);lcd.print(" ");lcd.setCursor(7, 1);}
      if (level_1   > 99 && level_1   < 1000) {lcd.setCursor(5, 1);lcd.print(" ");lcd.setCursor(6, 1);}
      if (level_1   > 999)                     lcd.setCursor(5, 1);lcd.print(level_1);

      if (level_11  < 10)                     {lcd.setCursor(0, 0);lcd.print(" ");lcd.setCursor(1, 0);lcd.print(" ");lcd.setCursor(2, 0);lcd.print(" ");lcd.setCursor(3, 0);}
      if (level_11  > 9  && level_11  < 100 ) {lcd.setCursor(0, 0);lcd.print(" ");lcd.setCursor(1, 0);lcd.print(" ");lcd.setCursor(2, 0);}
      if (level_11  > 99 && level_11  < 1000) {lcd.setCursor(0, 0);lcd.print(" ");lcd.setCursor(1, 0);}
      if (level_11  > 999)                     lcd.setCursor(0, 0);lcd.print(level_11);
      if (level_21  < 10)                     {lcd.setCursor(5, 0);lcd.print(" ");lcd.setCursor(6, 0);lcd.print(" ");lcd.setCursor(7, 0);lcd.print(" ");lcd.setCursor(8, 0);}
      if (level_21  > 9  && level_21  <  100) {lcd.setCursor(5, 0);lcd.print(" ");lcd.setCursor(6, 0);lcd.print(" ");lcd.setCursor(7, 0);}
      if (level_21  > 99 && level_21  < 1000) {lcd.setCursor(5, 0);lcd.print(" ");lcd.setCursor(6, 0);}
      if (level_21  > 999)                     lcd.setCursor(5, 0);lcd.print(level_21);

      delay (100);


    }
     
    Последнее редактирование: 5 ноя 2018
  2. b707

    b707 Гуру

    Вы должны не "ахинею" получать, а всегда ноль
    У вас все переменные - целые. Поэтому и выражение вычисляется в целых числах.
    Предположим, level_1 = 800; lightness = 600;
    (level_1-lightness)/level_1 = (800-600)/800 = 200/800 = 0
    И так будет всегда, пока (lightness < level_1)
     
    arkadyf нравится это.
  3. Вы делите целые числа, результат тоже будет округлен до целого. Т.к. левая часть всегда меньше правой, результат будет либо 1, либо 0.
    Пишите так
    (long)(level_1-lightness)*255/level_1
     
    arkadyf нравится это.
  4. parovoZZ

    parovoZZ Гуру

    А зачем так много? В практической реализации хватает и 255, т.е. байта.
     
  5. Airbus

    Airbus Радиохулиган Модератор

    Когда ж вы блин научитесь?
    [​IMG]
     
    barmaley_74 нравится это.
  6. parovoZZ

    parovoZZ Гуру

    С АЦП можно сразу диапазон в 8 бит снимать и не надо ничего делить. Тем более, что в авр нет ни блока умножения, ни деления.
     
  7. barmaley_74

    barmaley_74 Нуб

    В итоге здесь мне никто не помог. Помогли здесь - http://arduino.ru/forum/programmirovanie/slozhnye-arifmeticheskie-vyrazheniya#comment-402824 за что и благодарен. Действительно, я оказался очень не внимателен, так как в предыдущем уроке "Эксперимент 4. Терменвокс" функция map(value, fromLow, fromHigh, toLow, toHigh) рассматривалась. Так что решение выглядит очень просто -
    Код (Text):

      if (lightness < level_1) level_11 = 255 - map(lightness, 0, level_1, 0, 255);
     
     
  8. barmaley_74

    barmaley_74 Нуб

    Благодарю за наставления. Такой кнопки у меня нет, но есть в Меню "Правка" такой пункт "Копировать для форума", вроде правильно воспользовался?
     
  9. parovoZZ

    parovoZZ Гуру

    Так мы когда увидим
    ?
     
  10. b707

    b707 Гуру

    Это не помощь, а скорее наоборот. И это не решение проблемы, а банальный "костыль".В итоге Вы так и не поняли, в чем была ошибка. и наверняка довольно скоро сделаете ее снова.
    Если хотите разобраться - суть ошибки описана в ответах #2 и #3 в этой ветке и в ответе #2 в ветке на другом форуме.
     
    vvr и DetSimen нравится это.
  11. barmaley_74

    barmaley_74 Нуб

    Тогда давайте разберемся. Я Лет 20 тому назад программировал в Паскале очень не скверно, писал на листочках бумаги программу в точности до запятых, так что компилятор не выдавал ошибок. В общем давайте разбираться...

    Итак у нас имеется выражение level_11 = (((level_1-lightness))/(level_1))*255
    где в итоге мы хотим получить на выходе число от 0 до 255 (переменная level_11).

    Рассмотрим вариант когда level_1 = 300, lightness = 200. На любом калькуляторе, в т.ч. и Windows, получается 85, но так как мы имеем дело с целочисленными переменными в ардуино получется совершенно не то... Что бы понять это разложим выражение.
    level_11 = (((level_1-lightness))/(level_1))*255 = level_11 = (((300-200))/(300))*255 = (100/300)*255 = (100/300)*255 = 0*255 (100/300 = 0 так как целочисленное округляется до ближайшего числа, соотвественно 100/300 = 0,33 = 0. И кстати говоря если в калькуляторе Windows выбрать режим "программист" вы получите 100/300 = 0).

    Следовательно надо раскрыть выражение так что бы деление было в конце. Итак раскроем выражение.
    level_11 = (((level_1-lightness))/(level_1))*255 = ((level_1-lightness)*255)/level_1 = ((300-200)*255)/(300) = (100*255)/(300) = 25500 / 300 = 85.

    Отлично, выясним какое максимальное число мы можем получить в выражении. Если lightness = 1022 и level_1 = 1023, получим 1022 х 1023 = 1045506. Но даже тип WORD максимально может быть 65535, получается надо объявить level_11 как тип UNSIGNED LONG для корректного исчисления или всё же будучи даже типом INT всё будет считаться корректно?
     
  12. Нужно привести одно промежуточное значение к типу long, как я вам сразу же написал.
     
    barmaley_74 нравится это.
  13. barmaley_74

    barmaley_74 Нуб

    В Паскале даже при проведении сложрынх арифметических операций, на сколько я помню, последовательные вычесления производились без округления на каждом этапе до целочисленного значения, а округлялся только окончательный результат.
    В общем я сделал для себя определенные выводы, о том что при сложных выражениях можно попасть в просак потому как на каждом этапе вычисления ардуино округляет промежуточное значение до того типа которы стоит слева от равенства. В общем то плохо конечно. А можно ли действительно как то объявить level_11 типом INT, а выражение написать как level_11 = (long)(level_1-lightness)*255/level_1? Где про это почитать то можно? Это преобразование переменных, типов данных или чего то ещё?
     
  14. barmaley_74

    barmaley_74 Нуб

    А что такое привести, что это означает? Это означает что в процессе последовательного вычисления будет использоваться тип UNSIGNED LONG, а по окончании полученное значение будет приведено в тип INT?
     
  15. DetSimen

    DetSimen Гуру

    С этими привидениями типов, ты еще не один пузырь с горя хряпнешь
     
    parovoZZ нравится это.
  16. barmaley_74

    barmaley_74 Нуб

    Почему же? Если нормально где то объясняется что это и как оно происходит. Или это тайна за семью печатями? У меня ещё вопрос по сложным арифметическим выражениям - где нибудь описано ли правило последовательности выполнения арифметических операций ардуино без скобок?
     
  17. Приведение означает, что на стеке будет выделена память под новый тип, и туда будет записано значение, переведенное в этот тип по стандартным правилам.
    В вашем случае результат всего выражения будет автоматически приведен обратно к int, т.к такой тип указан для результирующей переменной.
     
  18. DetSimen

    DetSimen Гуру

    В конце концов открой же наконец книгу Кернигана и Ритчи.
     
  19. Все есть в учебнике. Классика - Страуструп язык с++.
    На форуме описывать никто не будет, это слишком много информации.
     
    barmaley_74 нравится это.
  20. barmaley_74

    barmaley_74 Нуб

    То есть я правильно понял, что "= (long)..." означает, что всё выражение будет вычисляться используя стек с типом LONG, а полученный результат будет приведен в тот тип переменной которая стоит слева от знака =?