平成27年10月31日
汎用の高速シリアル通信機能(15) タイマプログラムの作成
C++でタイマ割り込みによるインターバルタイマを動作させます。
C言語の範疇であれば極めて基本的なプログラムですが、C++ではそう簡単にはいきませんでした。
主な問題はクラスと割り込み処理の組み合わせではクラスの持つ特殊な制限事項をクリアする必要があること、XC32でC++を使うための情報が不足していることでした。
最初にプログラムの全リストを示します。このプログラムはブログに掲載する用に極めて単純化してあります。このため実際の使用には向きませんが、その分プログラムが短く問題点に注力しやすくなっています。
// XC32 Ver.1.33
/* Config設定はオプティマイズ社製PIC32ボードに合わせてある。
* 発信器(16MHz)の仕様が異なる場合は変更が必要
* プログラムが正しく動作するには、PBCLK=80MHzに設定する。
*/
#pragma config FPLLMUL = MUL_20, FPLLIDIV = DIV_4, FPLLODIV = DIV_1, FWDTEN = OFF
#pragma config FPBDIV = DIV_1, POSCMOD = HS, FNOSC = PRIPLL, DEBUG = ON
#include <cstdint> //各種整数型
#include <cstdbool> //bool
#include <cstdlib> //NULL
extern "C" {
#include <sys/attribs.h>
#include "xc.h"
}
/* include関連での疑問
* intx_t型(xは8,16,32)にunsignedをつけるとコンパイルエラーになる。
* uintx_tならエラーにはならない。
* 例;
* unsigned int8_t dummy; //エラー
* uint8_t dummy; //OK
* 気持ちが悪いがとりあえずはuintx_tを使用する。
*/
#ifndef nullptr
#define nullptr 0
#endif //nukkptr
/*-------------------------------------------------------------------
* 引数などで使用する構造体
*/
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);
}
//-------------------------------------------------------------------
// PICの動作周波数と実行時間の関係を固定するために必要
// CPUの入力クロック = 実行命令数 となるように設定
/* pic32のデバイス仕様
*/
#define SYS_DEVCON_PIC32MX_MAX_PB_FREQ 80000000UL
#define ROM_0WAIT_FREQ 30000000UL
#define ROM_1WAIT_FREQ 60000000UL
void sysPerrformanceConfig( unsigned int sysclk )
{
//キャッシュを有効にする(キャッシュ機能の無いCPUでは削除する)
/* キャッシュの管理はCPUではなくCP0にある。CP0のレジスタ16セレクト0
* ConfigRegのbit0〜2にあるK0レジスタがそれ。
* 詳細はPIC32日本語マニュアルセクション2「M4Kコア搭載デバイス用CPU」
*/
register unsigned long tmp;
asm("mfc0 %0,$16,0" : "=r"(tmp));
tmp = (tmp & ~7) | 3; //2:キャッシュ無効, 3:有効
asm("mtc0 %0,$16,0" :: "r" (tmp));
//ROMへアクセスするときのウェイト数
int wait;
if(sysclk <= ROM_0WAIT_FREQ) wait = 0;
else if (sysclk <= ROM_1WAIT_FREQ) wait = 1;
else wait = 2;
CHECONbits.PFMWS = wait;
//RAMへアクセスするときのウェイト数
BMXCONbits.BMXWSDRM = 0;
}
/*-------------------------------------------------------------------
/* インライン関数定義
* 重要
* これらの関数はXC32 Cコンパイラに同等の関数が定義されているが、
* これらはC++言語では使用できない。現時点での使用制限らしい。
* (XC32 Vre1.33)
* (__builtin_disable_interrupts(), __builtin_enable_interrupts())
*/
inline void DI(void)
{
__asm__ volatile ("di");
}
inline void EI(void)
{
__asm__ volatile ("ei");
}
/*-------------------------------------------------------------------
* メイン関数
*/
int main(void)
{
//CPUが80MIPSで動作するようキャッシュを有効にし不要なwaitを取り去る。
sysPerrformanceConfig(SYS_DEVCON_PIC32MX_MAX_PB_FREQ);
//割り込み動作をマルチベクタモードに
INTCONbits.MVEC = 1; //割り込みマルチベクタ
CTimerInt sys_timer; //クラスをインスタンス(実体)化
const TMR16_CFG_t timer_cfg = {
0x8010, //タイマON, プリスケーラ1/8
10000, //PR 10,000(PBCLK80MHzで1ms)
{1, 1} //IP, IS
};
INT_CB_ARG_t timer_cb_arg = {
(void*)CTimerInt::TimerCB, //"CTimerInt::"の前置は必須
&sys_timer,
};
//動作確認のためのIOポート
LATD = TRISD = 0;
sys_timer.InitTimer(&timer_cfg, &timer_cb_arg);
EI();
for(;;){
Nop();
}
}
/*-------------------------------------------------------------------
* 割り込み処理関数
* 重要
* extern "C"は必須。これが無いとコンパイルでエラー
*/
extern "C" void __ISR(_TIMER_1_VECTOR, IPL1SOFT) TIMER1_ISR(void)
{
//グローバル変数から必要なデータを取り出す
INT_CB_ARG_t* arg = g_timer1_interrupt;
//CB関数の呼び出し
(*(void (*)(void*))arg->func)(arg->para);
}
まず動作環境ですが、従来と同じくオプティマイズ社製PIC32ボードを使用します。
プログラムの動作としては、1ms毎にDポートの出力を反転させるだけの非常に単純なものです。
時間を扱うので周辺バス動作周波数(PBCLK)設定が重要になります。本プログラムではPBCLKを80MHzで動作させています。
他のボードを使用する場合はConfigビットの設定をボードの仕様に合わせて変更してください。PBCLKを80MHzに設定するためにはCPUクロックも80MHzに設定する必要があります。
#pragma config FPLLMUL = MUL_20, FPLLIDIV = DIV_4, FPLLODIV = DIV_1, FWDTEN = OFF
#pragma config FPBDIV = DIV_1, POSCMOD = HS, FNOSC = PRIPLL, DEBUG = ON
次はグローバル変数です。この変数はメイン側から割り込み処理関数へ割り込み処理内で呼び出す関数を指示するためにあります。
クラスCTimerIntのインスタンス(実体)であるsys_timerはmain関数内のローカルなオブジェクトとして定義されているため、割り込み処理関数から直接参照することが出来ません。このため、このグローバル変数を介して間接的にsys_timerへアクセスします。
もちろん、sys_timer自体をグローバル領域で定義しても良いです。
次にクラスの説明になりますが、これは長くなるので日を改めます。