Класс для чтения резисторной клавиатуры

Ну, поехали.

Номера строк, я буду везде приводить по последнему выложенному мною скетчу, а то я его меняю, номера плывут и чёрт их там разберёт. Поэтому, по последнему, выложенному в этом сообщении. Его-то я вижу у себя в IDE.

Изначальный скетч: (версия 672 / 46)
#define REMOTE_PIN    A2
#define BUTTONS_PIN   A3
#define MOTOR1_PIN    2//Вверх
#define MOTOR2_PIN    1//Вниз

#define MOTOR1_ON digitalWrite(MOTOR1_PIN, HIGH)
#define MOTOR1_OFF digitalWrite(MOTOR1_PIN, LOW)
#define MOTOR2_ON digitalWrite(MOTOR2_PIN, HIGH)
#define MOTOR2_OFF digitalWrite(MOTOR2_PIN, LOW)

#define MAX_TIME_UP 2000
#define MAX_TIME_DOWN 2000

bool motor_flag;
uint32_t motor_tmr;
uint32_t motor_off;

void setup() {
  pinMode(MOTOR1_PIN, OUTPUT);
  pinMode(MOTOR2_PIN, OUTPUT);
  MOTOR1_OFF;
  MOTOR2_OFF;
}

class Resistive_keyboard {
  public:
    Resistive_keyboard(uint16_t min, uint16_t max, uint8_t count);
    bool get();
  private:
    uint32_t _tmr;
    uint8_t _counts;
    uint8_t _count;
    bool _status;
    uint16_t _min;
    uint16_t _max;
};

Resistive_keyboard::Resistive_keyboard(uint16_t min, uint16_t max, uint8_t count) {
  _min = min;
  _max = max;
  _count = count;
}

bool Resistive_keyboard::get() {
  uint16_t _sensor = analogRead(BUTTONS_PIN);
  _status = false;
  if (_sensor > _min && _sensor < _max) {//93
    if (millis() - _tmr >= 50) {
      _tmr = millis();
      _counts++;
      if (_counts >= _count) {
        _counts = 0;
        _status = true;
      }
    }
  } else {
    _counts = 0;
  }
  return _status;
}

Resistive_keyboard Up(80, 100, 10);     //500мс (10*50)
Resistive_keyboard Down(270, 300, 10);  //500мс (10*50)
Resistive_keyboard Stop(350, 380, 5);   //250мс (5*50)

void loop() {
  if (Up.get()) {
    MOTOR1_ON;
    MOTOR2_OFF;
    motor_flag = true;
    motor_tmr = millis();
    motor_off = MAX_TIME_UP;
  }
  if (Down.get()) {
    MOTOR1_OFF;
    MOTOR2_ON;
    motor_flag = true;
    motor_tmr = millis();
    motor_off = MAX_TIME_DOWN;
  }
  if (motor_flag && millis() - motor_tmr >= motor_off || Stop.get()) {
    motor_flag = false;
    MOTOR1_OFF;
    MOTOR2_OFF;
  }
}

В строке №81, формально написано всё правильно, но я бы поставил скобки, чтобы не думалось, да и компилятор без скобок с предупреждениями лезет:

  if ((motor_flag && millis() - motor_tmr >= motor_off) || Stop.get()) {

В строке №33. Свойство _status абсолютно лишнее в классе. Оно используется только в методе get() и его использование начинается с присваивания. Так зачем тратить память на постоянное хранение старого значения? Строку №33 удаляем, а строку №46 заменяем на:

bool _status = false;

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

Едем дальше. В конструкторе класса у нас не делается ничего, кроме присваивания свойствам значений параметров конструктора. Кроме того, все экземпляры класса создаются с константными параметрами. Это сочетание просто требует исполнения конструктора на этапе компиляции! Зачем его исполнять при исполнении программы? Пусть компилятор проинициализирует переменные и всего делов. Заменяем конструктор на «constexpr конструктор» и выигрываем сразу 54 байта программной памяти!

Теперь наш скетч занимает 616 байтов программы и 43 байта переменных и выглядит вот так:

Версия 616/43
#define REMOTE_PIN    A2
#define BUTTONS_PIN   A3
#define MOTOR1_PIN    2//Вверх
#define MOTOR2_PIN    1//Вниз

#define MOTOR1_ON digitalWrite(MOTOR1_PIN, HIGH)
#define MOTOR1_OFF digitalWrite(MOTOR1_PIN, LOW)
#define MOTOR2_ON digitalWrite(MOTOR2_PIN, HIGH)
#define MOTOR2_OFF digitalWrite(MOTOR2_PIN, LOW)

#define MAX_TIME_UP 2000
#define MAX_TIME_DOWN 2000

bool motor_flag;
uint32_t motor_tmr;
uint32_t motor_off;

void setup() {
  pinMode(MOTOR1_PIN, OUTPUT);
  pinMode(MOTOR2_PIN, OUTPUT);
  MOTOR1_OFF;
  MOTOR2_OFF;
}

class Resistive_keyboard {
  public:
    constexpr Resistive_keyboard(const uint16_t min, const uint16_t max, const uint8_t count) 
    		: _tmr(0), _counts(0), _count(count), _min(min), _max(max) {}
    bool get();
  private:
    uint32_t _tmr;
    uint8_t _counts;
    uint8_t _count;
    uint16_t _min;
    uint16_t _max;
};

bool Resistive_keyboard::get() {
  uint16_t _sensor = analogRead(BUTTONS_PIN);
  bool _status = false;
  if (_sensor > _min && _sensor < _max) {//93
    if (millis() - _tmr >= 50) {
      _tmr = millis();
      _counts++;
      if (_counts >= _count) {
        _counts = 0;
        _status = true;
      }
    }
  } else {
    _counts = 0;
  }
  return _status;
}

Resistive_keyboard Up(80, 100, 10);     //500мс (10*50)
Resistive_keyboard Down(270, 300, 10);  //500мс (10*50)
Resistive_keyboard Stop(350, 380, 5);   //250мс (5*50)

void loop() {
  if (Up.get()) {
    MOTOR1_ON;
    MOTOR2_OFF;
    motor_flag = true;
    motor_tmr = millis();
    motor_off = MAX_TIME_UP;
  }
  if (Down.get()) {
    MOTOR1_OFF;
    MOTOR2_ON;
    motor_flag = true;
    motor_tmr = millis();
    motor_off = MAX_TIME_DOWN;
  }
  if ((motor_flag && millis() - motor_tmr >= motor_off) || Stop.get()) {
    motor_flag = false;
    MOTOR1_OFF;
    MOTOR2_OFF;
  }
}

Теперь давайте исправим небольшую ошибочку в методе get() и, заодно, причешем его в более божеский вид.

Ошибка состоит в том, что самый первый интервал в 50мс обычно не отрабатывает. Если данную кнопку давно (более 50 мс) никто не нажимал, а тут нажали, то условие в строке №42 выполнится сразу же, т.к. свойство _tmr содержит очень «древнее» значение. Если мы хотим, чтобы этот интервал честно отрабатывал, как все остальные, нам надо присваивать _tmr текущее значение millis() всякий раз, когда мы определили, что кнопка не нажата (в районе строки №51). Тогда, когда кнопку всё-таки нажмут, в _tmr у нас будет значение от последней проверки, а учитывая, что это проверки идут в loop постоянно – оно будет достаточно свежим. Итак, вставляем после строки №51 строку:

_tmr = millis();

Теперь первый интервал отрабатывает нормально, как все остальные.

Далее, заметим, что в этой маленькой функции, в которой нет никаких «долгоиграющих» операций, мы зачем-то трижды вызываем millis(). Зачем? Скорее всего, в течение этой функции она не изменится, а если и успеет измениться на 1, нам это абсолютно неважно! Давайте вызывать millis один раз в начале, а потом использовать готовое значение.

Ну, и, наконец, мы отсчитываем интервалы в 50 мс. Зачем нам значение времени аж uint32_t? При каких условиях нам такое огромное поле потребуется? Ни при каких! Нам здесь достаточно uint8_t за глаза! И для значения millis(), и для свойства _tmr.

Мелочь на закуску – переменная _sensor, получив значение, больше не изменяется. Делаем её константой.

Пора обновить текст. Кстати, заметьте, что наши последние манипуляции сократили память программ ещё на 28 байтов (стало 588), а память данных – на восемь байтов (стало 35). Скетч получился вот таким.

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

Версия 588/35 (584/35)
#define REMOTE_PIN    A2
#define BUTTONS_PIN   A3
#define MOTOR1_PIN    2//Вверх
#define MOTOR2_PIN    1//Вниз

#define MOTOR1_ON digitalWrite(MOTOR1_PIN, HIGH)
#define MOTOR1_OFF digitalWrite(MOTOR1_PIN, LOW)
#define MOTOR2_ON digitalWrite(MOTOR2_PIN, HIGH)
#define MOTOR2_OFF digitalWrite(MOTOR2_PIN, LOW)

#define MAX_TIME_UP 2000
#define MAX_TIME_DOWN 2000

bool motor_flag;
uint32_t motor_tmr;
uint32_t motor_off;

void setup() {
  pinMode(MOTOR1_PIN, OUTPUT);
  pinMode(MOTOR2_PIN, OUTPUT);
  MOTOR1_OFF;
  MOTOR2_OFF;
}

class Resistive_keyboard {
  public:
    constexpr Resistive_keyboard(const uint16_t min, const uint16_t max, const uint8_t count) 
    		: _tmr(0), _counts(0), _count(count), _min(min), _max(max) {}
    bool get();
  private:
    uint8_t _tmr;
    uint8_t _counts;
    uint8_t _count;
    uint16_t _min;
    uint16_t _max;
};

bool Resistive_keyboard::get() {
  const uint16_t _sensor = analogRead(BUTTONS_PIN);
  const uint8_t nowMillis = millis();
  bool _status = false;
  if (_sensor > _min && _sensor < _max) {//93
    if (static_cast<uint8_t>(nowMillis - _tmr) >= 50) {
      _tmr = nowMillis;
      if (++_counts >= _count) {
        _counts = 0;
        _status = true;
      }
    }
  } else {
    _counts = 0;
    _tmr = nowMillis;
  }
  return _status;
}

Resistive_keyboard Up(80, 100, 10);     //500мс (10*50)
Resistive_keyboard Down(270, 300, 10);  //500мс (10*50)
Resistive_keyboard Stop(350, 380, 5);   //250мс (5*50)

void loop() {
  if (Up.get()) {
    MOTOR1_ON;
    MOTOR2_OFF;
    motor_flag = true;
    motor_tmr = millis();
    motor_off = MAX_TIME_UP;
  }
  if (Down.get()) {
    MOTOR1_OFF;
    MOTOR2_ON;
    motor_flag = true;
    motor_tmr = millis();
    motor_off = MAX_TIME_DOWN;
  }
  if ((motor_flag && millis() - motor_tmr >= motor_off) || Stop.get()) {
    motor_flag = false;
    MOTOR1_OFF;
    MOTOR2_OFF;
  }
}

Обратите внимание на строки №№11 и 12. Если числа там никогда не будут превышать 65535, то из тех, же соображений, что были описаны выше, можно смело менять тип переменных motor_tmr и motor_off на uint16_t, что даст Вам ещё 4 байта экономии памяти данных.

Эти переменные (как и motor_flag, кстати) используются только в функции loop! Тогда, что они делают в глобальном контексте? Нечего им там делать, объявляем их static и переносим в loop.

Вы не поверите, но эти манипуляции дали нам ещё 50 (!!!) байтов памяти программы и четыре байта памяти данных!

Скетч теперь такой:

Версия 538/31 (518/31)
#define REMOTE_PIN    A2
#define BUTTONS_PIN   A3
#define MOTOR1_PIN    2//Вверх
#define MOTOR2_PIN    1//Вниз

#define MOTOR1_ON digitalWrite(MOTOR1_PIN, HIGH)
#define MOTOR1_OFF digitalWrite(MOTOR1_PIN, LOW)
#define MOTOR2_ON digitalWrite(MOTOR2_PIN, HIGH)
#define MOTOR2_OFF digitalWrite(MOTOR2_PIN, LOW)

#define MAX_TIME_UP 2000
#define MAX_TIME_DOWN 2000

void setup() {
  pinMode(MOTOR1_PIN, OUTPUT);
  pinMode(MOTOR2_PIN, OUTPUT);
  MOTOR1_OFF;
  MOTOR2_OFF;
}

class Resistive_keyboard {
  public:
    constexpr Resistive_keyboard(const uint16_t min, const uint16_t max, const uint8_t count) 
    		: _tmr(0), _counts(0), _count(count), _min(min), _max(max) {}
    bool get();
  private:
    uint8_t _tmr;
    uint8_t _counts;
    uint8_t _count;
    uint16_t _min;
    uint16_t _max;
};

bool Resistive_keyboard::get() {
	const uint16_t _sensor = analogRead(BUTTONS_PIN);
	const uint8_t nowMillis = millis();
	bool _status = false;
	if (_sensor > _min && _sensor < _max) {//93
		if (static_cast<uint8_t>(nowMillis - _tmr) >= 50) {
			_tmr = nowMillis;
			if (++_counts >= _count) {
				_counts = 0;
				_status = true;
			}
		}
	} else {
		_counts = 0;
		_tmr = nowMillis;
	}
	return _status;
}

Resistive_keyboard Up(80, 100, 10);     //500мс (10*50)
Resistive_keyboard Down(270, 300, 10);  //500мс (10*50)
Resistive_keyboard Stop(350, 380, 5);   //250мс (5*50)

void loop() {
	static bool motor_flag;
	static uint16_t motor_tmr;
	static uint16_t motor_off;

  if (Up.get()) {
    MOTOR1_ON;
    MOTOR2_OFF;
    motor_flag = true;
    motor_tmr = millis();
    motor_off = MAX_TIME_UP;
  }
  if (Down.get()) {
    MOTOR1_OFF;
    MOTOR2_ON;
    motor_flag = true;
    motor_tmr = millis();
    motor_off = MAX_TIME_DOWN;
  }
  if ((motor_flag && static_cast<uint16_t>(millis() - motor_tmr) >= motor_off) || Stop.get()) {
    motor_flag = false;
    MOTOR1_OFF;
    MOTOR2_OFF;
  }
}

Теперь, давайте посмотрим на класс. Вы предлагаете хранить _min и _max как двухбайтовые целые и это правильно. Но, давайте подумаем:

Мысля №1. Разница между _max и _min по смыслу задачи не очень большая и уж в байт точно влезет! Значит, можно хранить только _min (uint16_t) и разницу между ними (_diff - uint8_t). Экономим три байтика + ещё один от выравнивания, итого – четыре. Вот скетч:

Версия 538/27 (518/27)
#define REMOTE_PIN    A2
#define BUTTONS_PIN   A3
#define MOTOR1_PIN    2//Вверх
#define MOTOR2_PIN    1//Вниз

#define MOTOR1_ON digitalWrite(MOTOR1_PIN, HIGH)
#define MOTOR1_OFF digitalWrite(MOTOR1_PIN, LOW)
#define MOTOR2_ON digitalWrite(MOTOR2_PIN, HIGH)
#define MOTOR2_OFF digitalWrite(MOTOR2_PIN, LOW)

#define MAX_TIME_UP 2000
#define MAX_TIME_DOWN 2000

void setup() {
  pinMode(MOTOR1_PIN, OUTPUT);
  pinMode(MOTOR2_PIN, OUTPUT);
  MOTOR1_OFF;
  MOTOR2_OFF;
}

class Resistive_keyboard {
  public:
    constexpr Resistive_keyboard(const uint16_t min, const uint16_t max, const uint8_t count) 
    		: _min(min), _diff(max - min), _tmr(0), _counts(0), _count(count) {}
    bool get();
  private:
    const uint16_t _min;
    const uint8_t _diff;
    uint8_t _tmr;
    uint8_t _counts;
    uint8_t _count;
};

bool Resistive_keyboard::get() {
	const uint16_t _sensor = analogRead(BUTTONS_PIN);
	const uint8_t nowMillis = millis();
	bool _status = false;
	if (_sensor > _min && _sensor < (_min + _diff)) {//93
		if (static_cast<uint8_t>(nowMillis - _tmr) >= 50) {
			_tmr = nowMillis;
			if (++_counts >= _count) {
				_counts = 0;
				_status = true;
			}
		}
	} else {
		_counts = 0;
		_tmr = nowMillis;
	}
	return _status;
}

Resistive_keyboard Up(80, 100, 10);     //500мс (10*50)
Resistive_keyboard Down(270, 300, 10);  //500мс (10*50)
Resistive_keyboard Stop(350, 380, 5);   //250мс (5*50)

void loop() {
	static uint16_t motor_tmr;
	static uint16_t motor_off;
	static bool motor_flag;

  if (Up.get()) {
    MOTOR1_ON;
    MOTOR2_OFF;
    motor_flag = true;
    motor_tmr = millis();
    motor_off = MAX_TIME_UP;
  }
  if (Down.get()) {
    MOTOR1_OFF;
    MOTOR2_ON;
    motor_flag = true;
    motor_tmr = millis();
    motor_off = MAX_TIME_DOWN;
  }
  if ((motor_flag && static_cast<uint16_t>(millis() - motor_tmr) >= motor_off) || Stop.get()) {
    motor_flag = false;
    MOTOR1_OFF;
    MOTOR2_OFF;
  }
}

Мысля №2. А давайте пойдём дальше! Нас ведь интересует не конкретное значение, а попадает ли оно в интервал между _min и _max, так ведь? Причём этот интервал, заведомо больше четырёх (точность АЦП этого требует). Но тогда, мы ведь можем хранить не _min и _max, а их же делёнными на 4 (тогда они обе в байт поместятся!), а результат analogRead сразу же делить на 4! В итоге, получим то же самое, но несколько байтиков «захомячим» (вообще-то три байтика, но там один опять вылезет на выравнивание). Смотрите, как это выглядит:

Версия 538/25 (518/25)
#define REMOTE_PIN    A2
#define BUTTONS_PIN   A3
#define MOTOR1_PIN    2//Вверх
#define MOTOR2_PIN    1//Вниз

#define MOTOR1_ON digitalWrite(MOTOR1_PIN, HIGH)
#define MOTOR1_OFF digitalWrite(MOTOR1_PIN, LOW)
#define MOTOR2_ON digitalWrite(MOTOR2_PIN, HIGH)
#define MOTOR2_OFF digitalWrite(MOTOR2_PIN, LOW)

#define MAX_TIME_UP 2000
#define MAX_TIME_DOWN 2000

void setup() {
  pinMode(MOTOR1_PIN, OUTPUT);
  pinMode(MOTOR2_PIN, OUTPUT);
  MOTOR1_OFF;
  MOTOR2_OFF;
}

class Resistive_keyboard {
  public:
    constexpr Resistive_keyboard(const uint16_t min, const uint16_t max, const uint8_t count) 
    		: _min(min / 4), _max(max / 4), _tmr(0), _counts(0), _count(count) {}
    bool get();
  private:
    const uint8_t _min;
    const uint8_t _max;
    uint8_t _tmr;
    uint8_t _counts;
    uint8_t _count;
};

bool Resistive_keyboard::get() {
	const uint16_t _sensor = analogRead(BUTTONS_PIN) / 4;
	const uint8_t nowMillis = millis();
	bool _status = false;
	if (_sensor > _min && _sensor < _max) {//93
		if (static_cast<uint8_t>(nowMillis - _tmr) >= 50) {
			_tmr = nowMillis;
			if (++_counts >= _count) {
				_counts = 0;
				_status = true;
			}
		}
	} else {
		_counts = 0;
		_tmr = nowMillis;
	}
	return _status;
}

Resistive_keyboard Up(80, 100, 10);     //500мс (10*50)
Resistive_keyboard Down(270, 300, 10);  //500мс (10*50)
Resistive_keyboard Stop(350, 380, 5);   //250мс (5*50)

void loop() {
	static uint16_t motor_tmr;
	static uint16_t motor_off;
	static bool motor_flag;

  if (Up.get()) {
    MOTOR1_ON;
    MOTOR2_OFF;
    motor_flag = true;
    motor_tmr = millis();
    motor_off = MAX_TIME_UP;
  }
  if (Down.get()) {
    MOTOR1_OFF;
    MOTOR2_ON;
    motor_flag = true;
    motor_tmr = millis();
    motor_off = MAX_TIME_DOWN;
  }
  if ((motor_flag && static_cast<uint16_t>(millis() - motor_tmr) >= motor_off) || Stop.get()) {
    motor_flag = false;
    MOTOR1_OFF;
    MOTOR2_OFF;
  }
}

Ну, вот, такими нехитрыми шагами, мы уменьшили расход памяти с 672/46, до 538/25. Свободной памяти данных у Вас стало не 18 байтов, а 39, т.е. вдвое больше.

Теперь позвольте мне пройтись просто по коду (номера строк по последнему скетчу) и поворчать по-стариковски, уже без правок.

Строки №№ 6-9 – просто аплодирую! Все бы так делали. Чисто технически я бы советовал делать это inline функциями, а не макросами, но сама идея дать операциям осмысленные названия – нашим новичкам это надо на лбу в граните отливать!

Строки №№53-55. Эти переменные используются только в функции loop. Я бы их там и объявлял со словом static. Зачем им светиться на весь код? Не стоит расширять область видимости без крайней нужды. В смысле не стоит привыкать к этому.

Дополнение (вчера думал об этом, но забыл написать). Строки №№31 и 31 по последнему коду. Через месяц Вы ни в жисть не вспомните, что там для чего. Ну, обзовите их типа counter и countLimit - сразу понятно.

Это же касается MOTOR(1/2). Моторы же у Вас “вверх” и “вниз”. Ну, и называйте так, зачем 1 и 2? Чтобы потом думать что и где?

Ещё дополнение: раз уж мы делим на 4 с отбрасыванием остатка, наверное, надёжнее будет в строке №38 для нижней границы заменить “больше” на “больше или равно” (_sensor >= _min). Думаю, так лучше будет.

Ну, вот, как-то так. Простите за много букв, я уже говорил, что мой талант один у родителей, и сестёр у него нет :frowning:

P.S. Вроде, в протеусе всё работает

4 лайка