YS電子工作ラボ

関数ポインタ メモ


 関数ポインタを使用すれば、事前にどの関数を指すかを設定することにより同じよびだしで異なる関数を呼び出せることからHarmonyのライブラリの中で多用されています。 以下に関数ポインタ使用の例を紹介します。



<外観>
  
PIC32MZ評価ボード(→購入方法)を使った実験品の外観です。 ボードには本テーマと関係ない部品が多々実装されています。


<回路図>( → PDFファイル










<プロジェクトファイル>
(→プロジェクトファイル(Harmony Ver.2.04版 ) ダウンロード



★本プロジェクト作成に際し、追加した
部分は青色になっています。

app.h
system_config.h
myLibrary_Func.h
app.c
myLibrary_Func.c





<動作結果> (→ 動画 :1080pのHD動画を見ることができます。)

 汎用モジュール評価ボード上のSW1をクリックすると 表示モードが Mode1 → Mode2 → …… Mode6 → Mode7 → Mode1 → Mode2……と変化します。 
       
  Mode 1: パラメータのない関数ポインタの宣言と関数ポインタの直接引き当て
  1.
void型でパラメータのない関数へのポインタ Func1を宣言する。
void (*Func1)();

 以下は、intのaをdoubleでキャストした時の書式であるが、 この書式流で(*Func1)()を眺めるとポインタFunc1でvoidをキャストしているとも見ることができる。
int a;
(double)(a);
  2. シリアル型キャラクタ液晶に関数名LCD1の 1行目に"Hellow World !!" 2行目に"How are you?"を
表示する関数を定義。
void LCD1()
{
 lcd_cmd(0x80); //1目の先頭へ
 sprintf(Buf,"Hellow World !! ");//
 lcd_str(Buf); //液晶表示
 lcd_cmd(0xC0); //2行目の先頭へ
 sprintf(Buf,"How are you ? "); //
 lcd_str(Buf); //液晶表示
}
  3. 関数へのポインタFunc1を 関数へのポインタLCD1に置き換え、 シリアル型キャラクタ液晶に表示を実行する。
I2C型キャラクタ液晶に、モード番号と モードに象徴的なコマンドなどを表示する
Func1 = LCD1;
Func0()
; 

lcd_ACM1602_cmd_i2c(0x80); //1行目の先頭へ
lcd_ACM1602_str_i2c("1.           ");
lcd_ACM1602_cmd_i2c(0xC0); //2行目の先頭へ
lcd_ACM1602_str_i2c(" void(*Func1)();  ");;
    <動作結果>


  Mode 2: プリプロセッサ(#define)による 引き当ての関数ポインタ変更      
  1. myLibrary_Func.cでシリアル型キャラクタ液晶への表示をLCD2()で定義
void LCD2()
{
 lcd_cmd(0x80); //1目の先頭へ
 sprintf(Buf,"I am fine    ");
 lcd_str(Buf);//液晶表示
 lcd_cmd(0xC0); //2行目の先頭へ
 sprintf(Buf,"Thank you   ");
 lcd_str(Buf);//液晶表示
}
  2. system_config.hで関数ポインタ変更
#define Func20 LCD2
  3.  app.cで関数を実行
Func20();
    <動作結果>


  Mode 3: パラメータのある関数ポインタの宣言と関数ポインタの直接引き当て
  1. void型でパラメータのある関数へのポインタ Func3を宣言する
void (*Func3)(int);
  2.  シリアル型キャラクタ液晶に関数名LCD3の 1行目に演算式、 2行目に入力パラメータと演算結果を
表示する関数を定義する。
void LCD3(int x)
{
 int y;

 y = x * x;
 lcd_cmd(0x80); //1目の先頭へ
 sprintf(Buf,"y=x**2 ");//
 lcd_str(Buf); //液晶表示
 lcd_cmd(0xC0); //2行目の先頭へ
 sprintf(Buf,"x=%d y=%d ", x, y); //
 lcd_str(Buf); // 開始メッセージ1行目表示
}
  3. 関数へのポインタFunc3を 関数へのポインタLCD3に置き換え、 シリアル型キャラクタ液晶に表示を実行する。
I2C型キャラクタ液晶に、モード番号と モードに象徴的なコマンドなどを表示する
Func3 = LCD3; //引数は不要
(*Func3)(2);
//Func3(2); //★実行結果は (*Func3)(2);と同じ

lcd_ACM1602_cmd_i2c(0x80); //1行目の先頭へ
lcd_ACM1602_str_i2c("3. (*Func3)(2); ");
lcd_ACM1602_cmd_i2c(0xC0); //2行目の先頭へ
lcd_ACM1602_str_i2c(" ");;
    <動作結果>



  Mode 4: 戻り値があり、複数パラメータのある関数ポインタの宣言と関数ポインタの直接引き当て
  1. int型でパラメータのある関数へのポインタ Func4を宣言する
int (*Func4)(int,int); //戻り値:整数、 引数:2個
  2.  シリアル型キャラクタ液晶に関数名LCD4の 1行目に演算式、 2行目に入力パラメータと演算結果を
表示する関数を定義する。
int LCD4(int x, int y)
{
 int Ret;

 Ret = x + y;
 return Ret;
}
  3. 関数へのポインタFunc4を 関数へのポインタLCD4に置き換え、 シリアル型キャラクタ液晶に表示を実行する。
I2C型キャラクタ液晶に、モード番号と モードに象徴的なコマンドなどを表示する
int a = 2;
int b = 3;
int c;

Func4 = LCD4;
c = (*Func4)(a,b);

lcd_cmd(0x80); //1目の先頭へ
sprintf(Buf,"c = a + b ");//
lcd_str(Buf); //液晶表示
lcd_cmd(0xC0); //2行目の先頭へ
sprintf(Buf,"a=%d b=%d c=%d ", a, b, c); //
lcd_str(Buf); // 開始メッセージ1行目表示

lcd_ACM1602_cmd_i2c(0x80); //1行目の先頭へ
lcd_ACM1602_str_i2c("4. ");
lcd_ACM1602_cmd_i2c(0xC0); //2行目の先頭へ
lcd_ACM1602_str_i2c("c=(*Func4)(a,b); ");
    <動作結果>



  Mode5: typedef
  1. typedefで 型Func5を app.hに定義する。 型定義なのでapp.cには定義できない。
typedef void (*Func5)(int);
  2.  シリアル型キャラクタ液晶に関数名LCD5の 1行目に演算式、 2行目に入力パラメータと演算結果を
表示する関数を定義する。
void LCD5(int x)
{
 int y;

 y = x * x * x;

 lcd_cmd(0x80); //1目の先頭へ
 sprintf(Buf,"y = x **3 ");//
 lcd_str(Buf); //液晶表示
 lcd_cmd(0xC0); //2行目の先頭へ
 sprintf(Buf,"x=%d y=%d ", x, y); //
 lcd_str(Buf); // 開始メッセージ1行目表示
}
  3. Func5のインスタンスfunc5を生成し、LCD5のアドレスで初期化する。 func5(4);でパラメータを引き当てシリアル型キャラクタ液晶への表示を実行する。
I2C型キャラクタ液晶に、モード番号と モードに象徴的なコマンドなどを表示する
Func5 func5 = &LCD5; //インスタンスを生成、初期化

func5(4); //実行

lcd_ACM1602_cmd_i2c(0x80); //1行目の先頭へ
lcd_ACM1602_str_i2c("5.typedef ");
lcd_ACM1602_cmd_i2c(0xC0); //2行目の先頭へ
lcd_ACM1602_str_i2c("void(*Func5)(int); ");
    <動作結果>




  Mode6: 関数の宣言と同時に定義
  1. シリアル型キャラクタ液晶に関数名LCD6の 1行目に演算式、 2行目に入力パラメータと演算結果を
表示する関数を定義する。
void LCD6(int x)
{
 int y;

 y = x * x * x;

 lcd_cmd(0x80); //1目の先頭へ
 sprintf(Buf,"y = x **3 ");//
 lcd_str(Buf); //液晶表示
 lcd_cmd(0xC0); //2行目の先頭へ
 sprintf(Buf,"x=%d y=%d ", x, y); //
 lcd_str(Buf); // 開始メッセージ1行目表示
}
  2. 関数func6を宣言と同時にアドレスを引き当て定義する。 その後(*func6)(5); で実行する。
void (*func6)(int) = &LCD6;  //初期化

(*func6)(5);  //実行

lcd_ACM1602_cmd_i2c(0x80); //1行目の先頭へ
lcd_ACM1602_str_i2c("6.void(*ptr6)(in");
lcd_ACM1602_cmd_i2c(0xC0); //2行目の先頭へ
lcd_ACM1602_str_i2c("t) = &LCD6; ");
  3. <動作結果>




  Mode7: 関数ポインタを関数ポインタでキャストした値をハンドルとして使用
 関数ポインタを関数ポインタでキャストした値をハンドルとして使用このハンドル値を整数でキャストした値の調査
本例では、関数名、型名は実際にHarmony内で使用されている名称を使用
  1.  typedefで 型DRV_BM64_BUFFER_EVENT_HANDLERをapp.hの中で定義

//以下、実際はライブラリの "C:\microchip\harmony\v2_04\framework\driver\bluetooth\bm64\drv_bm64.h"の中で定義
typedef void (*DRV_BM64_BUFFER_EVENT_HANDLER) (int x);

//以下、実際は"C:\microchip\harmony\v2_04\apps\bluetooth\audio\BM64_a2dp_hfp\firmware\src\audio.h" の中で定義
typedef struct
{
//....
DRV_BT_BUFFER_EVENT_HANDLER bufferHandler;
//....
} AUDIO_BT;

typedef struct
{
//....
AUDIO_BT bt;
//....
} AUDIO_DATA

  2. app.cの中で実際の実行関数を定義
void AUDIO_BT_RxBufferEventHandler(int x, int y)
{
}
  3. system_config.hで関数ポインタを置換
#define DRV_BT_BUFFER_EVENT_HANDLER DRV_BM64_BUFFER_EVENT_HANDLER
  4. 実際に実行する関数のポインタを置換した関数のポインタでキャストして取得値をハンドル値として利用
Valueは 関数ポインタAUDIO_BT_RxBufferEventHandlerを 関数ポインタDRV_BT_BUFFER_EVENT_HANDLERでキャストしたあとintでキャストした値である。 実行した結果、この値は 関数ポインタAUDIO_BT_RxBufferEventHandlerのアドレスと同じである。(シリアル型キャラクタ液晶に表示されている ValueとAdres参照)
int Value,Adres;

Value =(int) ((DRV_BT_BUFFER_EVENT_HANDLER) AUDIO_BT_RxBufferEventHandler); //キャストすればコンパイル可

//以下、実際のコード//コンパイル可
audioData.bt.bufferHandler = //ハンドル
(DRV_BT_BUFFER_EVENT_HANDLER) AUDIO_BT_RxBufferEventHandler;

// Value =(DRV_BT_BUFFER_EVENT_HANDLER) AUDIO_BT_RxBufferEventHandler; //warnig発生 //左辺、右辺の型不一致

lcd_cmd(0x80); //1目の先頭へ
sprintf(Buf,"Value=%X ", Value);//整数化した値を16進数表示
lcd_str(Buf); //液晶表示
lcd_cmd(0xC0);
sprintf(Buf,"Adres=%p ", AUDIO_BT_RxBufferEventHandler); //関数のアドレスを表示
lcd_str(Buf);

lcd_ACM1602_cmd_i2c(0x80); //1行目の先頭へ
lcd_ACM1602_str_i2c("7.Cast by Functi");
lcd_ACM1602_cmd_i2c(0xC0); //2行目の先頭へ
lcd_ACM1602_str_i2c("on Pointer ");
    <動作結果>