CallBack Function с параметром

gonzales
Offline
Зарегистрирован: 13.07.2015

Доброго времени суток. Задачка следующая.

Есть библиотека (могу привести полный код). Кусок меня интересующий ниже.

Суть такова, что создается объект типа AlarmClass, с параметрами: время и функция обратного вызова, но проблема в том, что она (эта функция) идет без параметров, а мне нужно передать в нее параметр.

Сейчас вызов идет вот так

Alarm.timerOnce(10, OnceOnly); 

void OnceOnly() {
  Serial.println("Test");
}

А хочется вот так

Alarm.timerOnce(10, OnceOnly(123)); 

void OnceOnly(data) {
  Serial.println(data);
}

Я полез в библиотеку и поменял typedef void (*OnTick_t)(byte); // alarm callback function typedef

Но получаю ошибку "invalid use of void expression"

Подскажите, как правильно оформить передачу параметра. 

Код библы

typedef void (*OnTick_t)();  // alarm callback function typedef
//typedef void (*OnTick_t)(byte);  // alarm callback function typedef

// class defining an alarm instance, only used by dtAlarmsClass
class AlarmClass
{
public:
  AlarmClass();
  OnTick_t onTickHandler;
  void updateNextTrigger();
  time_t value;
  time_t nextTrigger;
  AlarmMode_t Mode;
};

// class containing the collection of alarms
class TimeAlarmsClass
{
private:
  AlarmClass Alarm[dtNBR_ALARMS];
  void serviceAlarms();
  uint8_t isServicing;
  uint8_t servicedAlarmId; // the alarm currently being serviced
  AlarmID_t create(time_t value, OnTick_t onTickHandler, uint8_t isOneShot, dtAlarmPeriod_t alarmType);

public:
  TimeAlarmsClass();
  // functions to create alarms and timers

  // trigger once at the given time in the future
  AlarmID_t triggerOnce(time_t value, OnTick_t onTickHandler) {
    if (value <= 0) return dtINVALID_ALARM_ID;
    return create(value, onTickHandler, true, dtExplicitAlarm);
  }

  // trigger once at given time of day
  AlarmID_t alarmOnce(time_t value, OnTick_t onTickHandler) {
    if (value <= 0 || value > SECS_PER_DAY) return dtINVALID_ALARM_ID;
    return create(value, onTickHandler, true, dtDailyAlarm);
  }
  AlarmID_t alarmOnce(const int H, const int M, const int S, OnTick_t onTickHandler) {
    return alarmOnce(AlarmHMS(H,M,S), onTickHandler);
  }

 

 

gonzales
Offline
Зарегистрирован: 13.07.2015

Перечитывал, я там ошибся. Хочется вот так

Alarm.timerOnce(10, OnceOnly(123)); 

void OnceOnly(byte data) {
  Serial.println(data);
}
DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Смотри, какое дело: то, что ты делаешь (Alarm.timerOnce(10, OnceOnly(123));) - это ВЫЗОВ функции OnceOnly с параметром 123.

Если тебе надо, чтобы вызывающая сторона умела передавать какой-либо дополнительный параметр, то:


typedef void (*PHandler)(byte param);

class SomeClass
{
 private:
	byte param;
	PHandler handler;
	
 public:
	void timerOnce(PHandler h, byte p)
	{
		if(!h)
			return;
		
		handler = h;
		param = p;
		
		delay(10000);
		
		handler(param);
		
	}
};

void MyHandler(byte param)
{
	Serial.println(param);
}

SomeClass some;

void setup()
{
	some.timerOnce(MyHandler,123);
}

void loop()
{
	
}

delay там чисто для прикола, я просто показал принцип, как сделать.

gonzales
Offline
Зарегистрирован: 13.07.2015

Круто, спасибо!!!

Получается придется конкретно библу переколбасить, протащить параметр в конструктор... и как-то поменять вызов.

Буду пробывать

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Как вариант просто сделать добавку, что бы и так и так.

/**/
typedef void (*PHandlerA)(byte param);
typedef void (*PHandlerB)();
//---------------------------------
class SomeClass {
  private:
    byte param;
    PHandlerB handler;
  public:
    void timerOnce(PHandlerA h, byte p) {
      if (!h)   return;
      h(p);
      handler = (PHandlerB)h;
      param = p;
      delay(10000);

    }
    void timerOnce(PHandlerB h) {
      if (!h)   return;
      h();
      handler = h;
      delay(10000);
    }
};
//---------------------------------------
void MyHandlerA(byte param) {
  Serial.println(param);
}
void MyHandlerB() {
  Serial.println("none");
}
SomeClass someA;
SomeClass someB;
//---------------------------------------
void setup() {
  someA.timerOnce(MyHandlerA, 123);
  someB.timerOnce(MyHandlerB);
}

void loop() {
}
/*Скетч использует 1778 байт (5%) памяти устройства. Всего доступно 30720 байт.
  Глобальные переменные используют 196 байт (9%) динамической памяти, оставляя 1852 байт для локальных переменных. Максимум: 2048 байт.
*/

 

gonzales
Offline
Зарегистрирован: 13.07.2015

Спасибо большое за пример! Именно так и сделаю.

Только я не совсем понял, не надо разве в секцию private задать PHandlerA handler;

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

gonzales пишет:

Спасибо большое за пример! Именно так и сделаю.

Только я не совсем понял, не надо разве в секцию private задать PHandlerA handler;

Надо, квон просто забыл ;)

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

А зачем много тратить памяти. Я просто сделал приведение к беспараметному виду. Если надо запустить снова, то приведите к типу с параметром и запустите.

/**/
typedef void (*pDoByte)(byte param);
typedef void (*pDo)();
//---------------------------------
class SomeClass {
  private:
    byte param;
    pDo handler;
  public:
    void timerOnce(pDoByte h, byte p) {
      if (!h)   return;
      handler = (pDo)h; // < -запись с беспараметный тип
      param = p;
      delay(10000);
      pDoByte Do = (pDoByte)handler; //<- приводение в параметный вид
      Do(param);
    }
    void timerOnce(pDo h) {
      if (!h)   return;
      h();
      handler = h;
      delay(10000);
    }
};
//---------------------------------------
void MyHandlerA(byte param) {
  Serial.println(param);
}
void MyHandlerB() {
  Serial.println("none");
}
SomeClass someA;
SomeClass someB;
//---------------------------------------
void setup() {
  someA.timerOnce(MyHandlerA, 123);
  someB.timerOnce(MyHandlerB);
}

void loop() {
}
/*Скетч использует 1786 байт (5%) памяти устройства. Всего доступно 30720 байт.
  Глобальные переменные используют 196 байт (9%) динамической памяти, оставляя 1852 байт для локальных переменных. Максимум: 2048 байт.
*/

 

gonzales
Offline
Зарегистрирован: 13.07.2015

Чего-то у меня не сростается.

Завел новый тип с параметром OnTickByte_t

В описании класса AlarmClass ввел переменные OnTickByte_t onTickByteHandler; byte param;

В описании класс TimeAlarmsClass ввел новый конструктор AlarmID_t createbyte(time_t value, OnTickByte_t onTickByteHandler, uint8_t isOneShot, dtAlarmPeriod_t alarmType, byte param);

В конструкторе сохраняю значение Alarm[id].param = param;

И в процедуре void TimeAlarmsClass::serviceAlarms() ввел новый вызов.

        if (TickByteHandler != NULL) {
          (*TickByteHandler)(param);     // call the handler
        }
 
Но вызов не происходит, как будто у меня TickByteHandler пустой(((
Посмотрите пожалуйста, где я накосячил?

 

typedef void (*OnTick_t)();  // alarm callback function typedef
typedef void (*OnTickByte_t)(byte param);

class AlarmClass
{
public:
  AlarmClass();
  OnTick_t onTickHandler;
  OnTickByte_t onTickByteHandler; 
  void updateNextTrigger();
  time_t value;
  time_t nextTrigger;
  AlarmMode_t Mode;
  byte param;
};

class TimeAlarmsClass
{
private:
  AlarmClass Alarm[dtNBR_ALARMS];
  void serviceAlarms();
  uint8_t isServicing;
  uint8_t servicedAlarmId; // the alarm currently being serviced
  AlarmID_t create(time_t value, OnTick_t onTickHandler, uint8_t isOneShot, dtAlarmPeriod_t alarmType);
  AlarmID_t createbyte(time_t value, OnTickByte_t onTickByteHandler, uint8_t isOneShot, dtAlarmPeriod_t alarmType, byte param);

public:
  TimeAlarmsClass();
  
AlarmID_t timerOnce(time_t value, OnTick_t onTickHandler) {
    if (value <= 0) return dtINVALID_ALARM_ID;
    return create(value, onTickHandler, true, dtTimer);
  }
  
  AlarmID_t timerOnce(time_t value, OnTickByte_t onTickByteHandler, byte param) {
    if (value <= 0) return dtINVALID_ALARM_ID;
    return createbyte(value, onTickByteHandler, true, dtTimer, param);
  }  
};


AlarmID_t TimeAlarmsClass::create(time_t value, OnTick_t onTickHandler, uint8_t isOneShot, dtAlarmPeriod_t alarmType)
{
  if ( ! ( (dtIsAlarm(alarmType) && now() < SECS_PER_YEAR) || (dtUseAbsoluteValue(alarmType) && (value == 0)) ) ) {
    // only create alarm ids if the time is at least Jan 1 1971
    for (uint8_t id = 0; id < dtNBR_ALARMS; id++) {
      if (Alarm[id].Mode.alarmType == dtNotAllocated) {
        // here if there is an Alarm id that is not allocated
        Alarm[id].onTickHandler = onTickHandler;
        Alarm[id].Mode.isOneShot = isOneShot;
        Alarm[id].Mode.alarmType = alarmType;
        Alarm[id].value = value;
        enable(id);
        return id;  // alarm created ok
      }
    }
  }
  return dtINVALID_ALARM_ID; // no IDs available or time is invalid
}

AlarmID_t TimeAlarmsClass::createbyte(time_t value, OnTickByte_t onTickByteHandler, uint8_t isOneShot, dtAlarmPeriod_t alarmType, byte param)
{
  if ( ! ( (dtIsAlarm(alarmType) && now() < SECS_PER_YEAR) || (dtUseAbsoluteValue(alarmType) && (value == 0)) ) ) {
    // only create alarm ids if the time is at least Jan 1 1971
    for (uint8_t id = 0; id < dtNBR_ALARMS; id++) {
      if (Alarm[id].Mode.alarmType == dtNotAllocated) {
        // here if there is an Alarm id that is not allocated
        Alarm[id].onTickByteHandler = onTickByteHandler;
		Alarm[id].param = param;
        Alarm[id].Mode.isOneShot = isOneShot;
        Alarm[id].Mode.alarmType = alarmType;
        Alarm[id].value = value;
        enable(id);
        return id;  // alarm created ok
      }
    }
  }
  return dtINVALID_ALARM_ID; // no IDs available or time is invalid
}

void TimeAlarmsClass::serviceAlarms()
{
  if (!isServicing) {
    isServicing = true;
    for (servicedAlarmId = 0; servicedAlarmId < dtNBR_ALARMS; servicedAlarmId++) {
      if (Alarm[servicedAlarmId].Mode.isEnabled && (now() >= Alarm[servicedAlarmId].nextTrigger)) {
        OnTick_t TickHandler = Alarm[servicedAlarmId].onTickHandler;
		OnTickByte_t TickByteHandler = Alarm[servicedAlarmId].onTickByteHandler;
		byte param = Alarm[servicedAlarmId].param;
        if (Alarm[servicedAlarmId].Mode.isOneShot) {
          free(servicedAlarmId);  // free the ID if mode is OnShot
        } else {
          Alarm[servicedAlarmId].updateNextTrigger();
        }
        if (TickHandler != NULL) {
          (*TickHandler)();     // call the handler
        }
        if (TickByteHandler != NULL) {
          (*TickByteHandler)(param);     // call the handler
        }			
      }
    }
    isServicing = false;
  }
}

 

gonzales
Offline
Зарегистрирован: 13.07.2015

Все нашел!!!!

У меня не стартовал таймер, там тоже проверка хэндла была. Добавил свой и все заработало.

void TimeAlarmsClass::enable(AlarmID_t ID)
{
  if (isAllocated(ID)) {
    if (( !(dtUseAbsoluteValue(Alarm[ID].Mode.alarmType) && (Alarm[ID].value == 0)) ) && ((Alarm[ID].onTickHandler != NULL) || (Alarm[ID].onTickByteHandler != NULL)) ) {
      // only enable if value is non zero and a tick handler has been set
      // (is not NULL, value is non zero ONLY for dtTimer & dtExplicitAlarm
      // (the rest can have 0 to account for midnight))
      Alarm[ID].Mode.isEnabled = true;
      Alarm[ID].updateNextTrigger(); // trigger is updated whenever  this is called, even if already enabled
    } else {
      Alarm[ID].Mode.isEnabled = false;
    }
  }
}

Огромное всем спасибо!!!!

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Когда у тебя переменная - указатель на функцию, разыменовывать через звездочку ее не надо, gcc и так пропускает, а читабельность получше маленько. Хотя, чоэтоя, где С, а где читабельность...

Вот так тоже прокатит

098         if ( !TickByteHandler ) TickByteHandler(param);   

 

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

DetSimen пишет:

Когда у тебя переменная - указатель на функцию, разыменовывать через звездочку ее не надо, gcc и так пропускает, а читабельность получше маленько. Хотя, чоэтоя, где С, а где читабельность...

Вот так тоже прокатит

098         if ( !TickByteHandler ) TickByteHandler(param);   

 

Деда, убери !, а то у тоби NULL разыменуется :)

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Да. ! лишний. Ну это я не иначе, с пахмелью. Простите.

DIYMan, спасибо, что разглядел. 

STALKER1204
Offline
Зарегистрирован: 28.09.2015

Добрый день. Ситуация у меня похожая. Arduino due, 15 кнопок. По нажатию на каждую из них выполняется однотипный код. В развернутом виде все работает, но переписывание одной и той же функции 15 раз... Прочитал ветку. Решил сделать "оптимизацию". Но при компилировании получаю ошибку в строках 31 и 40: invalid use of void expression. Что я не так делаю? И вообще возможно ли это в прерываниях?

#include <Arduino.h>

const int pins[10] = {30, 31, 32, 33, 34, 35, 36, 37, 38, 39};
volatile boolean isPressed[10] = {0,0,0,0,0,0,0,0,0,0};

typedef void (*PHandler)(byte param);

class FunClass
{
private:
  byte param;
  PHandler handler;

public:
  void intFunction(PHandler h, byte p)
  {
    if (!h)
      return;
    handler = h;
    param = p;
    handler(param);
  }
};

FunClass funClass;

void interruptFunction(byte id)
{
  detachInterrupt(pins[id]); 
  isPressed[id] = digitalRead(pins[id]);
  attachInterrupt(pins[id], funClass.intFunction(interruptFunction, id), CHANGE);

}

void setup()
{
  for (int i = 0; i < 10; i++)
  {
    pinMode(pins[i], INPUT_PULLUP);
    attachInterrupt(pins[i], funClass.intFunction(interruptFunction, i), CHANGE);
  }
}

void loop()
{
  //  
}

 

Logik
Offline
Зарегистрирован: 05.08.2014

атачить в прерывания тока статическую ф-ию и без параметров, делайте обертку. А воще подход какойто мутный. Нафига там класс? И так ли нужны прерывания на кнопки при пустом лупе.

STALKER1204
Offline
Зарегистрирован: 28.09.2015


Logik пишет:

атачить в прерывания тока статическую ф-ию и без параметров, делайте обертку. А воще подход какойто мутный. Нафига там класс? И так ли нужны прерывания на кнопки при пустом лупе.

Луп там не пустой. Я этот пример написал отдельно и облегченно. В кратце - станок, 4 шаговых движка, 5 пневмоцилиндров порядка 12 датчиков и тому подобное. от delay и прочих тяжелых вещей я отказался. зачем например в лупе перечитывать состояние всей периферии, если есть возможность точечно получить инфу о событии. плюс с защитой от дребезга. Благо Due это позволяет. Как я и писал, код рабочий, но очень длинный. вот и пытаюсь в виду (стандартаная фраза на этом форуме) не совсем сильных познаниях в с++, научиться и сделать некоторую оптимизацию кода. Статическая функция и без параметров в обертке это примерно как? Что то типа глобальной переменной, обработка которой уже внутри стандартной Callback функции? Типа вот так:

#include <Arduino.h>

const int pins[10] = {30, 31, 32, 33, 34, 35, 36, 37, 38, 39};
volatile boolean isPressed[10] = {0,0,0,0,0,0,0,0,0,0};
volatile byte id;


void interruptFunction()
{
  detachInterrupt(pins[id]); 
  isPressed[id] = digitalRead(pins[id]);
  attachInterrupt(pins[id], interruptFunction, CHANGE);

}

void setup()
{
  for (int i = 0; i < 10; i++)
  {
    pinMode(pins[i], INPUT_PULLUP);
    id = i;
    attachInterrupt(pins[i], interruptFunction, CHANGE);
  }
}

void loop()
{
  //  
}

 

Logik
Offline
Зарегистрирован: 05.08.2014

похоже. Но работать не будет, при вызове прерывания id откуда возмется?

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

  for (int i = 0; i < 10; i++)
  {
   isPressed[i]= digitalRead(pins[i]);
  }

 избавит от гемора и сильно сократит код.

STALKER1204
Offline
Зарегистрирован: 28.09.2015

Logik пишет:

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

  for (int i = 0; i < 10; i++)
  {
   isPressed[i]= digitalRead(pins[i]);
  }

 избавит от гемора и сильно сократит код.

Это я пробовал, но тогда получаю неравномерность работы ШД. (использую AccelStepper) и если в датчиках ПЦ - два на цилиндр нет дребезга, то на кнопках дребезг есть, несмотря на шунтирование кнопок кондерами

id - ну да...

 

 

Logik
Offline
Зарегистрирован: 05.08.2014

при атаче - понятно, а при вызове прерывания? 

Время отработки приведеного кода порядка десятков мксек, он  быстрый, он не может повлиять на ШД. Если надо быстрей - digitalRead меняем на прямую работу с регистром, int i на byte i, разворачиваем цикл, можна до нескольких мксек дооптимизироапть.

STALKER1204
Offline
Зарегистрирован: 28.09.2015

4 ШД, требуемая частота импульсов для каждого разная один от 2400/сек до 4800/сек период соответственно  от 415 мкСек до 208 мкСек. ну и так далее. Приведенный код да, время не большое. А дребезг?

Logik
Offline
Зарегистрирован: 05.08.2014

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

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

STALKER1204 пишет:

4 ШД, требуемая частота импульсов для каждого разная один от 2400/сек до 4800/сек период соответственно  от 415 мкСек до 208 мкСек. ну и так далее. Приведенный код да, время не большое. А дребезг?

Дребезг для быстрореагирующих систем надо глушить аппаратно. Программно глушится дребезг для медленых систем. Если дребезг на прерывание. То или блокировать повторные прерывания отключением прерываний на время дребега или глушить всю систему чем-то вроде delay. Что для быстрых систем критично. Так что глушите аппаратно дребег.

Logik
Offline
Зарегистрирован: 05.08.2014

Ага. Станцию подавления дребезга монтировать, если ума нет код писать.