みなさんこんにちわ、
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;
}
}






