みなさんこんにちわ、

SAIです。

 

前回、関数ポインタ呼び出しを学習しました。

 

ただ、前回の使い方だと、

周期呼び出しを関数ポインタにする程度。

 

この程度、別に関数羅列でも実現できますし、

あまり有益ではないですよね。

 

 

実は、関数ポインタが本当に活躍するのは、

非同期コールバックとか、状態遷移処理だと考えています。

 

 

今日は非同期コールバックを勉強しましょう。

 

 

非同期コールバックって何でしょうか?

 

その前に、同期処理 と 非同期処理 を理解しましょう。

 

 

同期処理とは、その名の通り 関数内で同期で処理を完結させるものです。

 

通常の関数呼び出しだと考えてください。

 

 

例えば、

0.5秒ごとにLEDを点滅する処理を作る場合どうしますか?

 

皆さん使ったことがありますよ。

  delay(500);

500ms待ちですね。

 

絵にすると、こんな感じです。

 

 

ただ、この処理には問題があります。

 

0.5秒間、他の処理ができません。

 

あせる現実世界でいえば、

 カップヌードルにお湯を入れたとしましょう。

 お湯を入れてから、3分経過するまでずっと時計を見続ける必要があります。

 

合格マイコンで例えば、

 4桁7セグLEDを使った場合、

 この時間待ち中も表示を続けなければなりません。

 

それではほかの処理ができません。困ります。

 

 

では、どうするか?

ほかの同期処理の方法として

メイン側の関数内で、0.5秒経過したかを毎回チェックする必要がありますよね。

 

もし、0.5秒待ちを同期処理すると

絵にするとこんな感じ。

 

 

毎周期、0.5秒たったかをチェックしなければならず、

処理がもったいないですよね。

 

あせる現実世界でいえば、

 カップヌードルにお湯を入れたとしましょう。

 お湯を入れてから、3分経過するまでちらちらと時計を見続ける必要があります。

 

 集中して計算なんかはできませんね。

 



では、
非同期処理はどのようなものでしょうか?


処理が終わったときに、処理が終わったことを通知をしてもらうものです。

 

 

 

 

あせる現実世界でいえば、

 カップヌードルにお湯を入れたとしましょう。

 お湯を入れた直後に、キッチンタイマーに3分をセットします。

 

 3分経過したら、タイマーアラームが鳴りますよね

 アラームが鳴るので、それまで計算などをしていても大丈夫ですね!

 

 

パソコン イヒ ラーメン         風鈴    時計 

 

 

 

ほかの非同期打とうとしては

タイマーとか、SD読み出しとか、モータをX回転回すとか、

処理が終わった際に、特定の関数を呼び出して、教えてあげるよ!

 

というものです。

 

 

そうすれば、その間別のことに集中できますね。

 

 

これが、関数ポインタだとどう役に立つのかというと・・・・

特にサードパーティソフトとしてソフト提供するようなときです。

 

 

非同期処理を複数実装する際に、どの呼び出し元からの応答か区別をつけるため

複数の関数に応答を作ってあげることになると思います。

 

自分が作るコードとか、同じ職場の人が作るコードなら、

Aの機能の応答は、FuncA_CallBack()、FuncB_CallBack()、で実装お願い!って言えますよね。

 

あせる現実世界でいえば、

 A君のカップラーメンを入れてから5分、

 B君のカップラーメンを入れてから3分、

 というように、開始タイミングも、終了までの時間も違うようなとき、

 1つのタイマーだと同時に処理できません。

 

 仮に一つのタイマーで処理をしたとすると、

 完了のアラームが、どっちの処理が完了したか見分けが尽きませんよね。

 複数のタイマーで、FuncA、FuncBのタイマを使用すると思います。

 

  A君 

パソコンイヒラーメン  時計               B君

                ベル時計  ラーメンウシシ パソコン

 

 

これがもし第三者に提供したり、提供してもらう場合どうでしょうか?

C言語の場合、同名の関数は一つしか作れません。

 

 

  A君 

パソコン イヒラーメン  時計          B君

               ラーメン!! 叫び パソコン

 

 

B君、計測できません!

困りますね。


 

こんな時、A君のタイマー用のコールバック関数、B君のタイマー用のコールバック関数を

別に分けることができたらいいと思いませんか?

 

 

 

もし、タイマーの設定をする際に、A君とB君の完了時の音を別にすることができたらどうでしょうか?

 

  A君 

イヒラーメン      風鈴    時計        B君

                  ベル  ラーメンウシシ

 

A君は、3分を設定するときに風鈴の音   ⇒A_CallBack

B君は、5分を設定するときにベルの音       ⇒B_CallBack

 

と設定すれば、カップ麺の完成時間がわかりますね!


 

 

ソフトとしては、

タイマーの要求をする際に、コールバックしてもらう関数を、引数で渡してしまえば

IF整合なんて必要なくなりますね!

イメージはこのような感じです。

 

 

 

 

では、ソフトを作ってみましょう。

 

前回は関数を配列にしましたが、

今回は関数とタイマのなる時間を配列で覚えます。

 

なので、定義がちょっと長いですね。

 

/* 関数ポインタの型定義 */
typedef void (*pFunc)(void);

/* 関数ポインタと時間の配列の構造体定義 */
struct TFuncParam
{
  pFunc  iFunc;           // callback 関数 /
  long   iTimerFireTime;  // callbackの時間 /
};

/* 関数ポインタと時間の配列 */
TFuncParam FunctionList[] =
{
  {NULL ,0}       /* 機能1 */
  ,{NULL ,0}      /* 機能2 */
  ,{NULL ,0}      /* 機能3 */
  ,{NULL ,0}      /* 機能4 */
  ,{NULL ,0}      /* 機能5 */
};

 

あとは、タイマーを改造して、処理を実装してみてくださいね!

 

晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ

雑談

C++言語でクラスと継承を使えば、めっちゃ楽になります。

 

その説明用にC言語で実装しましたが、

C言語で書くと、かなり面倒くさいですね。

 

あと、お試しで作ったSAIのコードも汚いので、イメージしにくいかも。。。

 

閲覧数が多そうであれば、改めて関数を整理しようかしら。。

晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ

 

それでは、作成したコードです。

main.ino

------------------------------

/*
 * Author: SAI
 */

/* *** Include *** */
#include <TimerOne.h>
/* 注意
   TimerOne.h を使用するためには、
   スケッチ -> ライブラリのインクルード -> ライブラリの管理
   から、TimerOne をインストールしておく必要があります。
*/

// 7Seg LED 表示Function //
#include "DisplayFunction.h"


/* *** Define 定義 *** */
/* 1桁の表示時間 */
const long TIMER_DELAY = (5 * 1000L);
/* 1秒にかかるカウント数 */
const long TIMER_CNT_SECOUND = ((long)1000 * 1000L) /(TIMER_DELAY);

/* RoundUp時間 ms */
const long TIMER_ROUNDUP = 1000 * 1000L;

/* 砂時計用内部カウンター */
long gTimerCounter;
/* 7セグ更新用時間 */
long gDispTime;

/* 関数ポインタの型定義 */
typedef void (*pFunc)(void);

/* 関数ポインタと時間の配列の構造体定義 */
struct TFuncParam
{
  pFunc  iFunc;           // callback 関数 /
  long   iTimerFireTime;  // callbackの時間 /
};

/* 関数ポインタと時間の配列 */
TFuncParam FunctionList[] =
{
  {NULL ,0}       /* 機能1 */
  ,{NULL ,0}      /* 機能2 */
  ,{NULL ,0}      /* 機能3 */
  ,{NULL ,0}      /* 機能4 */
  ,{NULL ,0}      /* 機能5 */
};


long GetMsTime()
{
  return (gTimerCounter *1000 / (TIMER_CNT_SECOUND ) ) ;
}

/* 機能関数の登録関数(ms) */
void AddFunction(void *aFunc, long aDelayTime)
{
  int i=0;
  while(FunctionList[i].iFunc!= NULL )
  {
    i++;
  }
  FunctionList[i].iFunc=aFunc;
  FunctionList[i].iTimerFireTime = aDelayTime + GetMsTime();
}

/* 機能関数の登録関数(ms) */
void TimerRoundup()
{
  gTimerCounter = 0;
  int i=0;
  while(FunctionList[i].iFunc!= NULL )
  {
    FunctionList[i].iTimerFireTime -= TIMER_ROUNDUP;
    i++;
  }
}


/* 機能関数の削除 */
void RemoveFunction(void *aFunc)
{
  int i=0;
  while(FunctionList[i].iFunc!= aFunc )
  {
    i++;
  }

  // sort //
  while(FunctionList[i].iFunc!= NULL )
  {
    FunctionList[i].iFunc       =FunctionList[i+1].iFunc;
    FunctionList[i].iTimerFireTime =FunctionList[i+1].iTimerFireTime;
    
    i++;
  }
}




/* initial setting 初期設定 */
 void setup() {
  initialize();
  /* タイマー用のカウンターを初期化 */
  // Timer setting  タイマー設定
  Timer1.initialize(TIMER_DELAY); //マイクロ秒単位で設定 
  Timer1.attachInterrupt(timerFire); // コールバック関数設定 
  
  gTimerCounter = 0;
  gDispTime = 0;

  // Lチカ用 //
  pinMode(A0, OUTPUT);

  AddFunction(&DisplayDraw,1);
  AddFunction(&DisplayLedChika,500);  

}



/* Mail loop 通常処理(繰り返し) */
void loop() {

  delay(1000);

}



/*
 * タイマー割り込み処理
 *  */
void timerFire() {
  // 時間計測 //
  gTimerCounter++;
  /* 1ms単位の時間に変換 */
  long lTime = GetMsTime();
  if(lTime >= TIMER_ROUNDUP)
  {
    TimerRoundup();
  }

  if(gDispTime != lTime)
  {
    gDispTime = lTime;
    /* 桁上がりした時だけ、1msの処理をする */
    delay(1);
  }

  /* 関数呼び出し */
  int i=0;
  while(FunctionList[i].iFunc!= NULL )
  {
    /* 発動時間を超えていたら、コールバック実施 */
    if(FunctionList[i].iTimerFireTime < lTime)
    {
     (void)(FunctionList[i].iFunc)();
      RemoveFunction(*FunctionList[i].iFunc);
    }
    i++;
  }

}


  
/* LED点灯状態 */
static bool gIsLedOn;

void DisplayLedChika()
{
  /* 次の非同期呼び出しを500ms後に設定 */
  AddFunction(&DisplayLedChika,500);  


  if(gIsLedOn)
  {
    /* LED点灯中なら消灯 */
    analogWrite(A0, 0);
    gIsLedOn = false;
  }
  else
  {
    /* LED点灯中なら点灯 */
    analogWrite(A0, 255);
    gIsLedOn = true;
  }

}

------------------------------

DisplayFunctionは、↓この関数だけ変更しました。

 

void DisplayDraw()
{
  /* 次の非同期呼び出しを5ms後に設定 */
  AddFunction(&DisplayDraw,2);  

  
  /* 桁切り分け */
  int DispKeta = gTimerCounter  % 4 ;
  /* 100ms単位の時間に変換 */
  int lTime = gTimerCounter / (TIMER_CNT_SECOUND /10 ) ;

  switch(DispKeta)
  {
    case 0:
     /* 1桁を表示 */
      DigitDisp(lTime);
      digitalWrite(DotSeg, LED_OFF);  
      Keta1();
      break;
    case 1:
     /* 2桁を表示 */
      digitalWrite(DotSeg, LED_ON);  
      DigitDisp(lTime/10);
      Keta2();
      break;
    case 2:
     /* 3桁を表示 */
      digitalWrite(DotSeg, LED_OFF);  
      DigitDisp(lTime/100);
      Keta3();
      break;
    case 3:
    default:
     /* 4桁を表示 */
      digitalWrite(DotSeg, LED_OFF);  
      DigitDisp(lTime/1000);
      Keta4();
      break;
  }
}