Arduino学习:Callback回调函数的理解

在学Arduino的过程中,回调函数(Callback)的概念让我迷糊了好一阵。在理解后把这个理解过程记录下来。

网上搜索了很久,很多文章没有看的太明白,后续问了专业编程的同事,并且找到了一个网上的描述的很清晰文章,感觉终于理解过来了。

网上文章出处:https://www.gammon.com.au/callbacks 以下是翻译+理解过程:

我们在写一些的Arduino程序时,经常会有需求:希望在某种条件发生的情况下,执行这个情况对应的操作。一般我们采用条件语句switch case 来完成就可以,比如:

int act = 2; // 2 is an example

switch (action)

  {

  case 0: doAction0 (); break;

  case 1: doAction1 (); break;

  case 2: doAction2 (); break;

  case 3: doAction3 (); break;

  case 4: doAction4 (); break;

  } // end of switch

这样写也没问题,但是比较冗长。我们也可以这样写:

action=3;

doAction(action);

这样执行我们需要把action作为参数传递到一个广义的doAction函数的内部,doAction需要定义类似的条件语句并描述好原来doAction1-4的所有算法。其实和Switch case 方法没有什么区别,只是换了一个地方。

函数指针的使用:

可以定义指向函数类型的指针型变量。首先用typedef先来声明你计划调用的函数的类型是很有用的,比如我们定义调用的函数是不带参数和不带返回值的类型的:

typedef void (*GeneralFunction) ();

接下来我们可以定义一些这类的函数如:

void doAction1 () {

Serial.println (“动作1”);

}

void doAction2 () {

Serial.println (“动作2”);

}

然后我们可以用定义好的指针型变量直接指向不同的函数:

GeneralFunction Foo;

Foo = doAction1;

Foo(); // 切记不要忘了括号!

这样Foo()执行的,其实就是doAction1()函数,我们可以更改变量Foo的值来改变指向的函数;

以上的函数类型是很特定的,那么怎么调用带有参数和返回值的函数呢?

如果要用指针型变量指向这类函数,需要这样去做typedef的定义:

typedef int (*GeneralArithmeticFunction)  (const int arg1, const int arg2);

这样定义了一个带两个整形参数并返回一个整形结果的函数指针。

然后我们定义几个这中类型的函数:

int Add (const int arg1, const int arg2) {

return arg1 + arg2;

} // 加法运算

int Subtract (const int arg1, const int arg2) {

return arg1 - arg2;

} // 减法运算

int Multiply (const int arg1, const int arg2) {

return arg1 * arg2;

} // 乘法运算

int Divide (const int arg1, const int arg2) {

return arg1 / arg2;

} // 除法运算

然后我们可以定义这类函数的指针变量通过赋值去指向不同运算:

GeneralArithmeticFunction fAdd = Add;

GeneralArithmeticFunction fSubtract = Subtract;

GeneralArithmeticFunction fDivide = Divide;

GeneralArithmeticFunction fMultiply = Multiply;

  //  use the function pointers

  Serial.println (fAdd (40, 2)); # 输出42

  Serial.println (fSubtract (40, 2)); # 输出38

  Serial.println (fDivide (40, 2)); # 输出20

  Serial.println (fMultiply (40, 2)); # 输出80

到这里,我们已经做到了灵活的通过指针型变量去指向不同的函数。它是回调函数的基础,但不是回调函数的完全体现。现在我们尝试一下:

通常我们的“主”代码将调用库(或其他)里的函数,向该函数传递指向“回调”函数的指针,然后该函数将在适当的时间调用。

typedef void (*GeneralMessageFunction) ();

void sayHello ()

    Serial.println ("Hello!"); 

} // end of sayHello

void sayGoodbye ()

    Serial.println ("Goodbye!"); 

} // end of sayGoodbye

void checkPin (const int pin, GeneralMessageFunction response); // prototype

void checkPin (const int pin, GeneralMessageFunction response) 

    if (digitalRead (pin) == LOW) { 

        response (); // call the callback function 

        delay (500); // debounce 

    } 

} // end of checkPin 

void setup ()

    Serial.begin (115200); 

    Serial.println (); 

    pinMode (8, INPUT_PULLUP); 

    pinMode (9, INPUT_PULLUP); 

} // end of setup

void loop () {

    checkPin (8, sayHello); 

    checkPin (9, sayGoodbye);

}  // end of loop

我们在checkPin函数的定义里增加了一个函数指针的参数,当pin的值符合拉低条件时,该指针便指向一个类型的函数。在实际应用checkPin函数时,为该指针赋一个值(实参),使其指向特定的函数并执行。也可以这么理解,checkPin是这样工作的:如果指定的pin值被拉低,那么checkPin函数就去调用另一个函数去执行。这个被调用的函数,它是checkPin函数中的一个实参。我们可以说,checkPin函数实现了Callback的功能。

Arduino中的中断功能,就是典型的回调:

attachInterrupt (0,blink, CHANGE);

attachInterrupt函数定义如果中断引脚的值发生变化的情况下,就去调用blink函数。

接下来我们看回调函数相对复杂的应用,比如用在排序功能中。

const int COUNT = 10;

int someNumbers [COUNT] = { 7342, 54, 21, 42, 18, -5, 30, 998, 999, 3  };

// callback function for doing comparisons

template<typename T> int myCompareFunction (const void * arg1, const void * arg2) {

    T * a = (T *) arg1;  // cast to pointers to T

    T * b = (T *) arg2;

    if (*a < *b) { return -1;} // a less than b?

    if (*a > *b) {return 1; }// a greater than b?

    return 0; // must be equal

  }  // end of myCompareFunction

void setup () {

    // sort using custom compare function

    qsort (someNumbers, COUNT, sizeof (int), myCompareFunction<int>);

    for (int i = 0; i < COUNT; i++) {

        Serial.println (someNumbers [i]); }

}  // end of setup

void loop () { }

qsort的compar参数是一个回调函数(关于qsort的函数介绍请网上搜索),它被设定为myCompareFunction<数据类型>,通过template <typename T>的模板定义,可以用函数去定义数据类型,不需要固定死。于是我们就可以在用qsort时没有数据类型的限制,例如替换成:

qsort (someNumbers, COUNT, sizeof (float), myCompareFunction<float>);

用它可以直接实现浮点数的排序而不需要更改回调函数的内容。

关于函数指针,我们还可以采用数组的形式去批量指向:

void doAction0 () { Serial.println (0); }

void doAction1 () { Serial.println (1); } 

void doAction2 () { Serial.println (2); }

void doAction3 () { Serial.println (3); }

void doAction4 () { Serial.println (4); }

typedef void (*GeneralFunction) ();// array of function pointersGeneralFunction doActionsArray [ ] = {

doAction0,

doAction1,

doAction2,

doAction3,

doAction4,

};

void setup ()  {  

    Serial.begin (115200);  

    Serial.println ();  

    int action = 3;  // 3 is an example 

    doActionsArray [action] (); //依旧不要忘记它作为一个函数指针,调取时需要代括号。

}  // end of setup

void loop () { }

如果你的函数指针数组里有很多函数,而不想占用有限的RAM资源,可以将指针数组存储到程序存储空间中。使用PROGMEM和对应的操作。

void doAction0 () { Serial.println (0); }

void doAction1 () { Serial.println (1); } 

void doAction2 () { Serial.println (2); }

typedef void (*GeneralFunction) ();// array of function pointers

const GeneralFunction doActionsArray [] PROGMEM =

    doAction0, 

    doAction1, 

    doAction2, };

    void setup () { 

   Serial.begin (115200); 

   Serial.println (); 

   int action = 2; // 2 is an example

    // get function address from program memory, call the function

    ((GeneralFunction) pgm_read_word (&doActionsArray [action])) ();

}  // end of setup

void loop () { }

最近的版本的Arduino IDE 和C++11语法上支持Lambda(未命名的)函数,不需要定义很多函数的名字,所以上述也可以写得更简化些:

void (*doActionsArray []) () = {

    [] { Serial.println (a); } ,

    [] { Serial.println (b); } ,

    [] { Serial.println (c); } ,

    [] { Serial.println (d); } ,

    [] { Serial.println (e); } ,

};

void setup ()  {

    int action = 2;  // 举例

    doActionsArray [action] ();

 }  // end of setup

void loop () { }

以上是阅读网上的文章进行的梳理。希望有助于理解。

实际应用中,可以拿Arduino中MQTT的示例来理解回调函数:

声明一个用来被回调的函数mqtt_callback:

void mqtt_callback(char *topic, byte *payload, unsigned int length) {

    Serial.print("Message arrived [");

    Serial.print(topic);

    Serial.print("] ");

    payload[length] = '\0';

    Serial.println((char *)payload);

}

在setup中调用这个函数,在收到订阅的消息时调用它:

mqttClient.setCallback(mqtt_callback);

在loop中让其能够不停的轮询:

mqttClient.loop();

以及在蓝牙例程中的接收信息用的回调:

//ble callback receive the data

class MyCallbacks: public BLECharacteristicCallbacks {

     void onWrite(BLECharacteristic *pCharacteristic) {

         std::string rxValue = pCharacteristic->getValue();

         int dataLen = rxValue.length();

         if ( dataLen > 0) {

            //Serial.print("Received Value: ");

            for (int i = 0; i < dataLen; i++){

            Serial.print(rxValue[i]);

            }

        }

    }

}

关于回调函数的理解如上,希望有所帮助。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268