Описание прерывания внутри класса

На этот раз делаю класс для контроллов… и в них используется энкодер, нуждающийся в прерываниях. Вот так работает:

int16_t encoderValue;
class cControlls {

  public:

    void define () {
      pinMode(ENCODER_B, INPUT);
      pinMode(ENCODER_A, INPUT);
      attachInterrupt(digitalPinToInterrupt(ENCODER_A), encoderItterupt, CHANGE);
    }

    int16_t get_enc () {
      int16_t value = encoderValue;
      encoderValue = 0;
      return value;
    }

  private:

    static void encoderItterupt () {
      if (digitalRead(ENCODER_A)) encoderValue += digitalRead(ENCODER_B) ? -1 : 1;
    }

   static const uint8_t
      ENCODER_A = 7,
      ENCODER_B = 6;

};

мне уснуть мешает объявление encoderValue вне класса, можно как-то ее туда упихать? Допустимо одну на все экземпляры класса, если иное невозможно.

Не совсем понял кстати юмора: static const ENCODER_A, ENCODER_B мы значит видим в static void encoderItterupt, но encoderValue как только не объявлял, не крутил и не слюнявил - компилятор не принимает

можно, но с ограничениями

Ну как то же ты упихал пины ?
Что мешает точно так же сделать с этой переменной

функция, выполняемая при прерывании должна быть static void

если объявить внутри класса:

int16_t encoderValue = 0;
Compilation error: invalid use of member ‘cControlls::encoderValue’ in static member function
(ф-ия не может получить значение класса)

static int16_t encoderValue = 0;
Compilation error: ISO C++ forbids in-class initialization of non-const static member ‘cControlls::encoderValue’
(ладно, это понятно, что в классе инициализировать стартовое значение для static не надо)

static int16_t encoderValue;
Compilation error: exit status 1
(вообще непонятно)

Ну, какие проблемы? Объявили же Вы функцию со словом static, ну и переменную объявите, кто или что мешает? Пхайте её внутрь класса со словом static, делов-то. Всё скомпилируется и даже “заработает”.

Но есть нюанс. Вот Вы говорите, что

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

Работать-то оно работает, но только в том случае, если у Вас в программе всего один экземпляр класса cControlls.

Как только Вы заведёте второй (третий и т.д.), Вы обнаружите, что Ваша переменная encoderValue одна на всех! Каждый экземпляр гадит в неё что-то своё и надеется, что именно его данные там и останутся, а оно “хрен там”. Т.е. по логике это должна быть переменная экземпляра (своя на каждый экземпляр), а Вы сделали её глобальной и единственной на все экземпляры!

Так, вот, если Вы обзовёте её static и запихаете внутрь класса – ничего не изменится. Она по-прежнему будет “одна на всех”.

==================================

Крик души: как же Вы все задолбали!

Ребята, я уже устал умолять вопрошающих: выкладывайте ПОЛНЫЙ код, который можно просто запустить у себя!

Вот взял я Ваш код, вставил к себе и тут же плюха:

.../main.cpp:43: undefined reference to 'setup'
.../main.cpp:46: undefined reference to 'loop'

пришлось старому человеку лезть, что-то туда добавлять … Вам трудно? Что я Вам плохого сделал? Это же Ваша проблема, так нахера Вы создаёте дополнительные трудности тем, кто желает Вам помочь её решить? Нахера?

3 лайка

Кстати, ТС, пины ты тоже упихал туда через жопу.
Сделай конструктор класса и через них задавай, а не это вот всё.

public:
cControlls(uint8_t Enc_A, uint8_t Enc_B);

Обьясните, какие у вас могут быть “экземпляры”, если функция прерывания у вас одна на всех? Как вы собираетесь одной функцией отрабатывать прерывание на нескольких пинах?
Уберите функцию прерывания из класса, сделайте отдельную функцию на каждый экземпляр, а в класс передавайте только ссылку на функцию

1 лайк

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

ну какой класс баттон если у тебя одна кнопка??
может я и не прав…

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

Спасибо, я и без проверок это знаю. Вообще экземпляр класса 1 на устройство, если будет 2 энкодера, то будет и два обработчика, и соотв. количество констант и переменных увеличится. Класс в данном случае нужен, чтобы функции устройства разделить логически, а не чтобы множить экземпляры. Ну и к методам обращаться через точку, лаконичнее что ли…

не скомпилируется, в # 4 писал об этом

int16_t encoderValue;
class cControlls {

  public:

    void define () {
      pinMode(ENCODER_B, INPUT);
      pinMode(ENCODER_A, INPUT);
      attachInterrupt(digitalPinToInterrupt(ENCODER_A), encoderItterupt, CHANGE);
    }

    int16_t get_enc () {
      int16_t value = encoderValue;
      encoderValue = 0;
      return value;
    }

  private:

    static void encoderItterupt () {
      if (digitalRead(ENCODER_A)) encoderValue += digitalRead(ENCODER_B) ? -1 : 1;
    }

   static const uint8_t
      ENCODER_A = 7,
      ENCODER_B = 6;

};

cControlls controlls;
void setup(){}
void loop(){}

Значит, неправильно “запихали”. Вы же в #4 кода не выложили, так что, хрен Вас знает, что у Вас там не компилируется. Если правильно запихать, то всё компилируется, куда ему деваться. Например, не надо инициализировать её там. Или это надо делать снаружи, или вообще не делать (воспользоваться тем, что все static переменные и так нулём инициализируются).

пробовал так

class cControlls {

  static int16_t encoderValue;

  public:

    void define () {
      pinMode(ENCODER_B, INPUT);
      pinMode(ENCODER_A, INPUT);
      attachInterrupt(digitalPinToInterrupt(ENCODER_A), encoderItterupt, CHANGE);
    }

    int16_t get_enc () {
      int16_t value = encoderValue;
      encoderValue = 0;
      return value;
    }

  private:

    static void encoderItterupt () {
      if (digitalRead(ENCODER_A)) encoderValue += digitalRead(ENCODER_B) ? -1 : 1;
    }

   static const uint8_t
      ENCODER_A = 7,
      ENCODER_B = 6;

};

cControlls controlls;
void setup(){}
void loop(){}

возвращает Compilation error: exit status 1 без доп. пояснений

Еще пробовал так (в секции public):

class cControlls {

  public:

    static int16_t encoderValue;

    void define () {
      pinMode(ENCODER_B, INPUT);
      pinMode(ENCODER_A, INPUT);
      attachInterrupt(digitalPinToInterrupt(ENCODER_A), encoderItterupt, CHANGE);
    }

    int16_t get_enc () {
      int16_t value = encoderValue;
      encoderValue = 0;
      return value;
    }

  private:

    static void encoderItterupt () {
      if (digitalRead(ENCODER_A)) encoderValue += digitalRead(ENCODER_B) ? -1 : 1;
    }

   static const uint8_t
      ENCODER_A = 7,
      ENCODER_B = 6;

};

cControlls controlls;
void setup(){}
void loop(){}

дословно не помню, но жаловался на использование класс мембера в статик функции, что в целом поддается логике

Если класс - синглтон, то работать будет. У мня работает

Спойлер
static TTimerList* TimerInstance;  // singleton


TTimerList::TTimerList(uint8_t ATimersCount) {

.
.
.
        if (TimerInstance != nullptr) delete(TimerInstance);

	TimerInstance = this;

	FHardTimer = timerBegin(1000000);
	timerAttachInterrupt(FHardTimer, &TTimerList::onTimer);
	timerAlarm(FHardTimer, 1000, true, 0);

}

void TTimerList::Tick() {
	for (uint8_t i = 0; i < FSize; ++i) {
		if ((FItems[i] == nullptr) || (!FItems[i]->Active)) continue;
		if ((--FItems[i]->WorkCounter) == 0) {
			FItems[i]->WorkCounter = FItems[i]->InitCounter;
			SendMessage(msg_TimerEnd, FItems[i]->Handle);
		}
	}
}
.
.
.
void ARDUINO_ISR_ATTR TTimerList::onTimer(void) {
	if (TimerInstance != nullptr) TimerInstance->Tick();
}

в .h файле функция отклика на прерывание описана как

static void ARDUINO_ISR_ATTR onTimer(void);

Так понимаю - это единственный вариант. Тогда лучше оставлю одну глобальную переменную вне объявления класса, некрасиво, но не смертельно… Спасибо!

P.s. что меня прям убило - константы класса в функции прерывания работают, а переменная, объявленная таким же образом - нет.

Вот прямо это, ничего не меняя, скопипастил к себе – всё отлично компилируется.

Скетч использует 444 байт (1%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 9 байт (0%) динамической памяти, оставляя 2039 байт для локальных переменных. Максимум: 2048 байт.

Что-то Вы не так делаете :frowning:

тут вы меня подловили, надо было действительно полностью код давать, с использованием. Компилятор материться при вызове define

class cControlls {

  static int16_t encoderValue;

  public:

    void define () {
      pinMode(ENCODER_B, INPUT);
      pinMode(ENCODER_A, INPUT);
      attachInterrupt(digitalPinToInterrupt(ENCODER_A), encoderItterupt, CHANGE);
    }

    int16_t get_enc () {
      int16_t value = encoderValue;
      encoderValue = 0;
      return value;
    }

  private:

    static void encoderItterupt () {
      if (digitalRead(ENCODER_A)) encoderValue += digitalRead(ENCODER_B) ? -1 : 1;
    }

   static const uint8_t
      ENCODER_A = 7,
      ENCODER_B = 6;

};

cControlls controlls;

void setup() {

  controlls.define();

}

void loop() {


}

Блин! Т.е. после моего вчерашнего “крика души”, Вы, по-прежнему, даёте мне неполный код, который сами не запускали и не проверяли? В итоге, я трачу своё время на проверку и эти траты оказываются “псу под хвост”? Вот, скажите, Вы здоровы? У Вас всё в порядке?

Простите, но у меня пропало желание пытаться Вам помогать. Дальше без меня. Успехов!

2 лайка
class cControlls {

  public:

    void define () {
      pinMode(ENCODER_B, INPUT);
      pinMode(ENCODER_A, INPUT);
      attachInterrupt(digitalPinToInterrupt(ENCODER_A), encoderItterupt, CHANGE);
    }

    int16_t get_enc () {
      int16_t value = encoderValue;
      encoderValue = 0;
      return value;
    }

  private:

    static void encoderItterupt () {
      if (digitalRead(ENCODER_A)) encoderValue += digitalRead(ENCODER_B) ? -1 : 1;
    }

   static const uint8_t
      ENCODER_A = 7,
      ENCODER_B = 6;

    static volatile int16_t encoderValue;

};
volatile int16_t cControlls::encoderValue;


cControlls controlls;



void setup () {

  controlls.define();

}

void loop () {

}

Вот так работает. Ключевая строка #30 volatile int16_t cControlls::encoderValue; - без этого объявления encoderItterupt () отказывается видеть encoderValue

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

Один из мотивов введения классов - уменьшение количества однотипного кода для множества родственных действий. Если этого нет, то смысла использовать классы не много. В программировании МК есть узкие места, где применения классов не применимо прямо. Стоит ли везде стараться использовать классы если прямой выгоды от этого не заметно?

1 лайк

Я понимаю ваши тезисы и полностью с ними согласен. Но использовать классы бывает очень удобно, чтобы разделить сущности логически, даже если в программе МК используется всего лишь 1 экземпляр класса. Как минимум, классы позволяют определить область видимости констант, переменных и методов, тем самым обезопасив от случайного переприсвоения значений или от их изменения/использования неправильным образом.

Ну, сударь, Вы уверены, что пушка – лучшее оружие для охоты на воробьёв?

С этим вполне справится пространство имён:

namespace Kaka {
// видимость всего, что записано здесь ограничено фигурными скобками
int mumu = 321;
}

// Снаружи обратиться можно только явно указав пространство имён
Serial.println(Kaka::mumu);
1 лайк