平成27年11月 1日
汎用の高速シリアル通信機能(16) タイマプログラムの作成(2)
グローバルでないクラスのクラス関数を割り込み処理関数の内から呼び出すためには、クラスやクラス関数へのポインタを使用します。ポインタを経由して使用する場合、結論だけを先に書くと次のようになります。
- 割り込み処理から呼び出すクラス関数にはstatic宣言が必要。
- クラス関数にstatic指定を行うと、クラス内の他のクラス関数やクラス変数には直接アクセスできない。
- 上記の制限を無くすためには、static宣言をした静的クラス関数にクラスのインスタンスポインタ(thisポインタ)を渡す必要がある
- 静的クラス関数内から他のクラス関数やクラス変数にアクセスするためにはthisポインタによる修飾が必要
規則さえ分かれば良い人は以下を読み飛ばしてください。C++言語仕様に関わる内容なので、他のHPも参考にしてください。
ここでは概略のみの説明になります。
1.割り込み処理から呼び出すクラス関数にはstatic宣言が必要。
クラス内にあるクラス関数もそのアドレスを取ることは可能です。しかし、そのアドレスを(void*)型の変数に代入することはできません。コンパイラがエラーを返します。
void型および(void*)型は全ての型を代入できる筈ですが、これはC言語の範疇での話でC++言語では成り立たないようです。
これはクラス関数がクラスインスタンスを示すthisポインタと関数ポインタの二つセットで機能するため、この関数アドレス部分だけを取り出すのは意味がないためのようです(個々のクラス毎にクラス関数ポインタ型の変数を定義すれば代入できます)。
例外はstatic宣言を付加した静的クラス関数で、こちらは通常のC言語と同じく関数ポインタを(void*)型に代入できます。
つまり静的クラス関数は他のクラス関数とは異なる管理をされています。
今回は割り込み処理関数にデータを渡すグローバル変数として構造体を宣言します。構造体には関数ポインタとして(void*)型を用意してあるので静的クラス関数が必要です。
2.クラス関数にstatic指定を行うと、クラス内の他のクラス関数やクラス変数には直接アクセスできない。
3.上記の制限を無くすためには、static宣言をした静的クラス関数にクラスのインスタンスポインタ(thisポインタ)を渡す必要がある
静的クラス関数の制限として他のクラス関数やクラス変数へアクセスできないという制約があります。これを回避するためにクラスインスタンスへのポインタを静的クラス関数に渡して、このポインタを介してアクセスします。これはルールであると認識すべきでしょう。
グローバル変数として渡される構造体INT_CB_ARG_tには関数ポインタのほかにインスタンスポインタを渡すための変数*paraを用意してあります。
4.静的クラス関数内から他のクラス関数やクラス変数にアクセスするためにはthisポインタによる修飾が必要
これもルールとして受け入れるべきでしょう。静的クラス関数内から他のクラス関数やクラス変数へアクセスするときには常にインスタンスポインタの前置が必要です。
これらを確認するために、先のソースプログラム該当部分を再度示します。
/*-------------------------------------------------------------------
* 引数などで使用する構造体
*/
typedef union {
struct {
volatile uint8_t IS:2;
volatile uint8_t IP:3;
};
volatile uint8_t byte;
} INT_PRI_t;
typedef struct {
uint32_t CON;
uint16_t PR;
INT_PRI_t pri;
} TMR16_CFG_t;
typedef struct {
void *func; //コールバック関数ポインタ(static指定必須)
void *para; //クラスのポインタ
} INT_CB_ARG_t;
/*-------------------------------------------------------------------
* グローバル変数
* 割り込み処理とメイン側で情報をやり取りするためには、少なくとも
* 一つのグローバル変数が必要(特定の名前空間内でもいい)
*/
INT_CB_ARG_t* g_timer1_interrupt;
/*-------------------------------------------------------------------
* クラスの定義
* タイマ1を使って周期タイマを作る
* なお、本プログラムでは複雑にならないよう可能な限り単純化してある
*/
class CTimerInt {
public:
uint32_t m_count; //カウント値(= ms)
static void TimerCB(void *ptr); //ISR関数から呼び出す関数(要static)
bool InitTimer(const TMR16_CFG_t* cfg, INT_CB_ARG_t* arg);
uint8_t GetTimerIF(void);
void SetTimerIF(uint8_t val);
void SetTimerIE(uint8_t val);
void SetTimerIP(uint8_t IP, uint8_t IS);
};
/* 注意:
* class定義ではstatic宣言が必要だが、ここでは必要ない
*/
void CTimerInt::TimerCB(void *ptr)
{
CTimerInt* thisptr = (CTimerInt*)ptr;
/* 本来はクラス変数やクラス関数を呼び出せない静的(static)クラス関数
* だがthisポインタを入手できるなら、それを行うことが出来る。
*/
if(thisptr->GetTimerIF()){ //'thisptr->'は省略不可
thisptr->SetTimerIF(0);
thisptr->m_count++;
LATD ^= 0xffff; //タイマの周期確認用IO
}
}
bool CTimerInt::InitTimer(const TMR16_CFG_t*const cfg, INT_CB_ARG_t* arg)
{
if(cfg == nullptr)
return false;
g_timer1_interrupt = arg; //割り込み処理で使用するデータを記録
T1CON = 0;
PR1 = cfg->PR;
SetTimerIP(cfg->pri.IP, cfg->pri.IS);
TMR1 = 0;
T1CON = cfg->CON;
SetTimerIE(1);
return true;
}
uint8_t CTimerInt::GetTimerIF(void)
{
return IFS0bits.T1IF;
}
void CTimerInt::SetTimerIF(uint8_t val)
{
IFS0bits.T1IF = val;
}
void CTimerInt::SetTimerIP(uint8_t IP, uint8_t IS)
{
IPC1bits.T1IP = IP;
IPC1bits.T1IS = IS;
}
void CTimerInt::SetTimerIE(uint8_t val)
{
IEC0bits.T1IE = (val ? 1 : 0);
}
最後にXC32特有の問題を書いていきますが、日を改めます。