Особенности работы функции constrain

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

Тема траблы. Ограничиваю переменную диапазоном от 5 до 99:

powerState = constrain(powerState, 5, 99);

но энкодер продолжает выдавать при вращении значения от 4 до 100. Пробовал ставить десятичку типа:

powerState = constrain(powerState, 5.0, 99.0);

всё равно проскакивает.

Пробую так:

powerState = constrain(powerState, 5,1, 99.1);

- при прямом ходе всё равно упирается, а при обратном - проскакивет мимо 1 цифру. Никто не сталкивался? В доках ничего не сказано...

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

То, что Вы пишете невозможно потому, что это невозможно никогда, ошибка у Вас в другом месте.

Будьте  любезны, выложите предельно короткий, но полный (чтобы можно было скомпилировать и запустить) скетч, который демонстрирует проблему. Ибо constrain - это тупой как валенок макрос

#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))

и никаких особенностей у него нет и быть не может.

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

Код писать для повторения нереально, много подвязок и нестандартных либ (которые, в принципе, всегда нормально работали. Попробую объяснить без кода, ибо так будет проще.

Имеем функцию опроса энкодера через interrupt. Отлично считает +1 и -1, всё зашибись. Задаю через constrain диапазон, к примеру, от 5 до 95. Имею также кнопку Shift, после нажатия на которую приращение энкодера становится +/-10. И тут, интенсивно крутя энк, я с лёгкостью перелетаю (точнее, не я, а constrain) через заданный диапазон.

-NMi-
Offline
Зарегистрирован: 20.08.2018

Тут сегодня засилие школьнегофф???

Кто мешает разбить всё это на ДВА условия??? Типа если это меньше 5 то равно 5 и больше 99 равно 99. ФСЁ!!!

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

А печатать в сериал до и после ограничения не пробовали?

Duino A.R.
Offline
Зарегистрирован: 25.05.2015

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Dorfman пишет:
без кода, ибо так будет проще
Ну, если Вам так проще, то разбирайтесь. Я отлаживать программы без кода не умею :-(

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

Вот как раз условия-то и работают. А я спрашЭвал про конкретную функцЫю.

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

Увы, она "пролетает" мимо числа 128,  я уже думал об отрицательных... Условия по if работают, а constrain нет, почему вопрос и поднялся. После того как потом шевельнёшь энкодер в любую стороны, constrain "исправляется" и сбрасывается на 128. Хех, ладно, потом ещё на досуге поколдую. Но спасибо за помощь.

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Dorfman пишет:

Увы, она "пролетает" мимо числа 128,  я уже думал об отрицательных... Условия по if работают, а constrain нет, почему вопрос и поднялся. После того как потом шевельнёшь энкодер в любую стороны, constrain "исправляется" и сбрасывается на 128. Хех, ладно, потом ещё на досуге поколдую. Но спасибо за помощь.

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

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Dorfman пишет:
я с лёгкостью перелетаю (точнее, не я, а constrain)
Да, нет, уж. Именно Вы! Текст этого макроса я привёл в посте #1. Никуда он перелетать не может - Бог крыльев не дал.

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

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

ЕвгенийП пишет:

Вы уже столько времени потратили на болтовню здесь, что можно было уже два демонстрационных кода написать.

так ему не надо решать проблему, ему общения не хватает...

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

Сел и переписал с нуля код, упростив и выбросив мишуру. Всё подробно откомментил. Вращая энкодер, мы меняем значение mainMenuPosition - это тот пункт меню, на который наведён фокус (в моём случае стрелка из строк 118-126). В функции mainMenuSelector() стоит ограничение значений меню от 0 до 7 с помощью constrain. При врашении энкодера на приращение значение добегает до 7 и на этом останавливается, что подтверждается и стрелкой, и выводом значения переменной mainMenuPosition. При обратном вращении энкодера происходит декремент, но после значения mainMenuPosition == 0 на контрольном индикаторе возникает на мгновение цифра 255, и потом спрыгивает на 7, то есть нижняя граница перескакивается.

Такая же петрушка проявляется, когда вставляю этот же обработчик энкодера и ограничения с constrain в остальные функции: sceneSelector(), void effectSelector(), speedSelector() и powerSelector().

Вроде как всё работает, но при обратном вращении происходит этот непонятный глюк. Вот так выглядит сам дисплей:

#include <LiquidCrystal_I2C_Menu.h>
LiquidCrystal_I2C_Menu lcd1(0x27, 20, 4); // ИНИЦИАЛИЗАЦИЯ ОСНОВНОГО ДИСПЛЕЯ
LiquidCrystal_I2C_Menu lcd2(0x26, 16, 2); // ИНИЦИАЛИЗАЦИЯ КОНТРОЛЬНОГО ДИСПЛЕЯ
#include <EncoderStepCounter.h>  // БИБЛА ЭНКОДЕРА

// ====== ЭНКОДЕР ====== ПИНЫ ДЛЯ DUE ИЛИ МЕГИ
#define ENCODER_PIN1 47
#define ENCODER_INT1 digitalPinToInterrupt(ENCODER_PIN1)
#define ENCODER_PIN2 49
#define ENCODER_INT2 digitalPinToInterrupt(ENCODER_PIN2)
#define ENCODER_SW 51
EncoderStepCounter encoder(ENCODER_PIN1, ENCODER_PIN2);

// СТАРТОВЫЕ ЗНАЧЕНИЯ ЭЛЕМЕНТОВ МЕНЮ
byte mainMenuPosition = 0; // ЗНАЧЕНИЕ ПУНКТА ГЛАВНОГО МЕНЮ
byte mainMenuState = 9; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ СОСТОЯНИЯ ГЛАВНОГО МЕНЮ (ДЕАКТИВАЦИЯ ВЫБРАННОГО ПУНКТА МЕНЮ)
byte powerState; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ ЯРКОСТИ
byte minPowerState = 6; // МИНИМАЛЬНОЕ ЗНАЧЕНИЕ ЯРКОСТИ
byte maxPowerState = 98; // МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ ЯРКОСТИ
byte sceneNumber = 1; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ СЦЕНЫ
byte minSceneNumber = 1; // МИНИМАЛЬНОЕ ЗНАЧЕНИЕ СЦЕНЫ
byte maxSceneNumber = 16; // МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ СЦЕНЫ
byte effectNumber; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ ЭФФЕКТА
byte minEffectNumber = 1; // МИНИМАЛЬНОЕ ЗНАЧЕНИЕ ЭФФЕКТА
byte maxEffectNumber = 32; // МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ ЭФФЕКТА
byte speedNumber = 10; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ СКОРОСТИ
byte minSpeedNumber = 1; // МИНИМАЛЬНОЕ ЗНАЧЕНИЕ СКОРОСТИ
byte maxSpeedNumber = 64; // МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ СКОРОСТИ
unsigned long speedDelay;  // ЗНАЧЕНИЕ ТЕКУЩЕЙ ЗАДЕРЖКИ
unsigned long minSpeedDelay = 300;  // МИНИМАЛЬНОЕ ЗНАЧЕНИЕ ЗАДЕРЖКИ
unsigned long maxSpeedDelay = 45;  // МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ ЗАДЕРЖКИ
volatile boolean smartState = false;  // СОСТОЯНИЕ СМАРТ-ЛЕНТ ВКЛ-ВЫКЛ
volatile boolean laserState = false;  // СОСТОЯНИЕ ЛАЗЕРОВ ВКЛ-ВЫКЛ
volatile boolean dmxState = false;  // СОСТОЯНИЕ DMX-ДЕВАЙСОВ ВКЛ-ВЫКЛ

byte debounce = 30; // ВРЕМЯ ЗАДЕРЖКИ ДЛЯ УСТРАНЕНИЯ ДРЕБЕЗГА

void setup() {
  Wire.begin();
  encoder.begin();
  lcd1.begin();
  lcd2.begin();

  pinMode(ENCODER_SW, INPUT_PULLUP);

// ======== ПРЕРЫВАНИЯ ДЛЯ ЭНКОДЕРА ПО СОБЫТИЮ ИЗМЕНЕНИЯ
  attachInterrupt(ENCODER_INT1, interrupt, CHANGE);
  attachInterrupt(ENCODER_INT2, interrupt, CHANGE);

// ВЫЗОВ ФУНКЦИИ ПЕРВИЧНОЙ ОТРИСОВКИ НАЧАЛЬНОГО ЭКРАНА
  mainScreen();
}

void interrupt() {
  encoder.tick();
}

void loop() {

  // МАПИМ ЗНАЧЕНИЯ С МЕНЮ В ЗНАЧЕНИЯ ДЛЯ ОБРАБОТКИ И ОГРАНИЧИВАЕМ ==============
  speedNumber = constrain(speedNumber, minSpeedNumber, maxSpeedNumber);
  speedDelay = map(speedNumber, minSpeedNumber, maxSpeedNumber, minSpeedDelay, maxSpeedDelay);
  sceneNumber = constrain(sceneNumber, minSceneNumber, maxSceneNumber);
  effectNumber = constrain(effectNumber, minEffectNumber, maxEffectNumber);
  powerState = constrain(powerState, minPowerState, maxPowerState);
  mainMenuPosition = constrain(mainMenuPosition, 0, 7); // ВОТ КОНКРЕТНО ЭТО ОГРАНИЧЕНИЕ "ПРОСКАКИВАЕТ"

// ПРОВЕРЯЕМ, НЕ ВЫБРАН ЛИ КАКОЙ-ТО ПУНКТ МЕНЮ, ЕСЛИ ВЫБРАН - УХОДИМ В НУЖНУЮ ФУНКЦИЮ
  if (mainMenuState == 9) mainMenuSelector();
  else if (mainMenuState == 0) sceneSelector();
  else if (mainMenuState == 1) effectSelector();
  else if (mainMenuState == 2) speedSelector();
  else if (mainMenuState == 3) powerSelector();
  else if (mainMenuState == 4) smartSubmenu();
  else if (mainMenuState == 5) dmxSubmenu();
  else if (mainMenuState == 6) laserSubmenu();
  else if (mainMenuState == 7) setupSubmenu();

}


// РИСУЕМ ГЛАВНЫЙ ЭКРАН =======================================
void mainScreen() {
  // ********** ГЛАВНОЕ МЕНЮ **********
  lcd1.printAt(1, 0, "SCENE:");
  lcd1.printAt(9, 0, sceneNumber);
  lcd1.printAt(1, 1, "EFFECT:");
  lcd1.printAt(9, 1, effectNumber);
  lcd1.printAt(1, 2, "SPEED:");
  lcd1.printAt(9, 2, speedNumber);
  lcd1.printAt(1, 3, "POWER:");
  lcd1.printAt(9, 3, powerState);
  lcd1.printAt(15, 0, "SMART");
  if (smartState == 0) lcd1.printAt(14, 0, "\xDB");
  if (smartState == 1) lcd1.printAt(14, 0, "\xFF");
  lcd1.printAt(15, 1, "DMX");
  if (dmxState == 0) lcd1.printAt(14, 1, "\xDB");
  if (dmxState == 1) lcd1.printAt(14, 1, "\xFF");
  lcd1.printAt(15, 2, "LASER");
  if (laserState == 0) lcd1.printAt(14, 2, "\xDB");
  if (laserState == 1) lcd1.printAt(14, 2, "\xFF");
  lcd1.printAt(15, 3, "SETUP");
}


// ФУНКЦИЯ ОБРАБОТКИ ПУНКТОВ ГЛАВНОГО МЕНЮ
void mainMenuSelector() {
  signed char mm = encoder.getPosition();
  if (mm != 0) {
    sceneNumber = constrain(sceneNumber, minSceneNumber, maxSceneNumber);
    if ((sceneNumber <= 0) || (sceneNumber > 7)) sceneNumber = 0;
    mainMenuPosition = mainMenuPosition + mm;
    // ЗАЧИЩАЕМ СТАРЫЕ СТРЕЛКИ СЛЕВА
    for (byte i = 0; i <= 3; i++) lcd1.printAt(0, i, " ");
    for (byte i = 0; i <= 3; i++) lcd1.printAt(13, i, " ");
    encoder.reset();
  }
  // СТРЕЛКА НА ЛЕВУЮ ЧАСТЬ МЕНЮХИ
  if (mainMenuPosition == 0) lcd1.printAt(0, 0, "\x7E");
  if (mainMenuPosition == 1) lcd1.printAt(0, 1, "\x7E");
  if (mainMenuPosition == 2) lcd1.printAt(0, 2, "\x7E");
  if (mainMenuPosition == 3) lcd1.printAt(0, 3, "\x7E");
  if (mainMenuPosition == 4) lcd1.printAt(13, 0, "\x7E");
  if (mainMenuPosition == 5) lcd1.printAt(13, 1, "\x7E");
  if (mainMenuPosition == 6) lcd1.printAt(13, 2, "\x7E");
  if (mainMenuPosition == 7) lcd1.printAt(13, 3, "\x7E");
  // ЗАКРЕПЛЯЕМ ВЫБРАННЫЙ ПУНКТ МЕНЮ
  if (digitalRead(ENCODER_SW) == LOW) {
    mainMenuState = mainMenuPosition;
    delay(debounce);
  }
}

/* ФУНКЦИЯ ВЫБОРА СЦЕНЫ ЭНКОДЕРОМ, ТАКАЯ ЖЕ КАК ВЫБОР МЕНЮ 
** (ДЛЯ ОБЛЕГЧЕНИЯ КОДА НЕ ВПИСАНА ПОЛНОСТЬЮ).
** ПРИ НАЖАТИИ КНОПКИ ЭНКОДЕРА ВЫВАЛИВАЕМСЯ СНОВА В ФУНКЦИЮ 
** ОБРАБОТКИ ПУНКТОВ ГЛАВНОГО МЕНЮ. 
** mainMenuState = 9 - НЕСУЩЕСТВУЮЩИЙ ПУНКТ МЕНЮ (ТО ЕСТЬ МЫ ВЫШЛИ
**  В САМЫЙ ВЕРХНИЙ УРОВЕНЬ)
*/
void sceneSelector() {
  if (digitalRead(ENCODER_SW) == LOW) {
    mainMenuState = 9;
    delay(debounce);
  }
}

// ФУНКЦИЯ ВЫБОРА ЭФФЕКТА ЭНКОДЕРОМ, АНАЛОГИЧНА ПРЕДЫДУЩЕЙ
void effectSelector() {
  if (digitalRead(ENCODER_SW) == LOW) {
    mainMenuState = 9;
    delay(debounce);
  };
}

// ФУНКЦИЯ ВЫБОРА СКОРОСТИ ЭНКОДЕРОМ, АНАЛОГИЧНА ПРЕДЫДУЩЕЙ
void speedSelector() {
  if (digitalRead(ENCODER_SW) == LOW) {
    mainMenuState = 9;
    delay(debounce);
  }
}

// ФУНКЦИЯ ВЫБОРА ЯРКОСТИ ЭНКОДЕРОМ, АНАЛОГИЧНА ПРЕДЫДУЩЕЙ
void powerSelector() {
  if (digitalRead(ENCODER_SW) == LOW) {
    mainMenuState = 9;
    mainMenuPosition = 3;
    delay(debounce);
    mainMenuState = 9;
    mainMenuPosition = 3;
  }
}

// ФУНКЦИЯ ВКЛЮЧЕНИЯ-ОТКЛЮЧЕНИЯ СМАРТ-ЛЕНТ КНОПКОЙ ЭНКОДЕРА С УДЕРЖАНИЕМ
void smartSubmenu() {
  if (digitalRead(ENCODER_SW) == LOW) smartState = !smartState;
  if (smartState == 0) lcd1.printAt(14, 0, "\xDB");
  if (smartState == 1) lcd1.printAt(14, 0, "\xFF");
  mainMenuState = 9;
  delay(debounce);
}

// ФУНКЦИЯ ВКЛЮЧЕНИЯ-ОТКЛЮЧЕНИЯ УСТРОЙСТВ DMX КНОПКОЙ ЭНКОДЕРА С УДЕРЖАНИЕМ
void dmxSubmenu() {
  if (digitalRead(ENCODER_SW) == LOW) dmxState = !dmxState;
  if (dmxState == 0) lcd1.printAt(14, 1, "\xDB");
  if (dmxState == 1) lcd1.printAt(14, 1, "\xFF");
  mainMenuState = 9;
  delay(debounce);
}


// ФУНКЦИЯ ВКЛЮЧЕНИЯ-ОТКЛЮЧЕНИЯ ЛАЗЕРОВ КНОПКОЙ ЭНКОДЕРА С УДЕРЖАНИЕМ
void laserSubmenu() {
  if (digitalRead(ENCODER_SW) == LOW) laserState = !laserState;
  if (laserState == 0) lcd1.printAt(14, 2, "\xDB");
  if (laserState == 1) lcd1.printAt(14, 2, "\xFF");
  mainMenuState = 9;
  delay(debounce);
}

// ПРОВАЛИВАЕМСЯ В МЕНЮ НАСТРОЕК (ПОКА НЕ ДОПИСАНО)
void setupSubmenu() {
  if (digitalRead(ENCODER_SW) == LOW) {
    mainMenuState = 9;
    delay(debounce);
  }
}

 

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

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

опишите mainMenuPosition как int - и проверьте еще раз

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

АЁ... голова садовая! Я пробовал, но не ту переменную менял, поэтому и результата не было. Работает! Огромное спасибо!

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

М-да, всё-таки косяк был не в этом... Я, баран, протупил, и допустил логическую ошибку. Сейчас, когда constrain стоИт ниже счётчика, всё работает как с int, так и с byte! То есть изначально у меня 8-я и 9-я строки были поменяны местами, поэтому ограничение происходило ДО переполнения на 1, вот и провозился 2 дня впустую, пока не нашёл...

void effectSelector() {
  lcd1.printAt(8, 0, " ");
  lcd1.printAt(8, 2, " ");
  lcd1.printAt(8, 3, " ");
  lcd1.printAt(8, mainMenuState, "\x7E");
  signed char eff = encoder.getPosition();
  if (eff != 0) {
    effectNumber = effectNumber + eff;
    effectNumber = constrain(effectNumber, minEffectNumber, maxEffectNumber);
    lcd1.printAt(9, 1, effectNumber);
    if (effectNumber < 10) {
      lcd1.printAt(10, mainMenuState, "  ");
    }
    encoder.reset();
  }
  if (digitalRead(ENCODER_SW) == LOW) {
    delay(debounce);
    lcd1.printAt(8, mainMenuState, " ");
    mainMenuState = 9;
  }
}

 

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Ну так сильно голову то пеплом посыпать не нужно. Нужно просто понять, что как правило в 99.99% в ошибках виноват автор кода. Так что изначально нужно искать ошибку у себя. 

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

brokly пишет:
в 99.99% в ошибках виноват автор кода

Это я знаю и понимаю, просто неправильно подошёл к вопросу. Нужно было поэтапно решать каждый функционал, а я набросился одновременно на всё. В результате получил 4к кода, в котором потом сам же и застрял. Ещё раз всем спасибо.

vvadim
Offline
Зарегистрирован: 23.05.2012

да и научиться работать с кнопками тоже не помешает.

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

vvadim пишет:

да и научиться работать с кнопками тоже не помешает.

А при чём тут кнопки? У меня энкодер с раздолбанной кнопкой, и новый сейчас нет возможности купить (все рынки и единственный радиомагазин в городе закрыты). Программный дебонс срабатывает через раз, вот и юзаю через делей, пока новый не куплю. И я уже об этом писал выше.

Или я что-то не понял? Да и вообще, вопрос был по конкретной функции, которую я по своей невнимательности прописал раньше приращений энкодера.