平成27年 4月26日
PIC32の周辺機能(USART)を活用する(その3) 初期化関数と割り込み処理
(H27/05/31追記)
この内容はPIC325xx/6xx/7xxファミリーでは動作しますが、それ以外の例えばPIC321xx/2xxファミリーでは動作しません。
詳細は
こちら以降を参照してください。この文書は資料の連続性を維持するために、残しておきます。
(追記ここまで)
USARTの切り替え準備が出来たので、次は初期化関数の仕様を決めていきます。
過去の経験からシリアル通信の細部仕様(送信ビット数・パリティ有無・ストップビット数など)を個別に指定するのは避けるようになりました。
どの道、通信仕様を変更する必要があるなら他の設定も変更になることが多いので、結局はデータシートを見る羽目になります。
このため最近はMODEレジスタやSTAレジスタへの設定値を直接指定する方法を取っています。
この方法でも比較的使用頻度の高い設定値をコメントに書いておけば、それほど不自由することはありません。
以上から初期化関数に渡すパラメータ変数の指定は下記のようになります。
また、デバッグなどで面倒が無いようにデフォルトの指定値を準備しておきます。
typedef struct {
uint32_t PBCLK;
uint32_t MODE;
uint32_t STA;
uint16_t BRT;
uint8_t IP:3;
uint8_t IS:2;
uint8_t CH; //使用するSCIのCH番号
} SCI_CFG__t;
#define SCI_CFG_t const SCI_CFG__t
/* SCI1_init()関数の引数としてDefaultを用意
* ・SCI1を使用する
* ・CPUは80Mhz動作
* ・TX,RXピンのみ使用(RTS, UTS未使用)
* ・TX,RXともに割り込み有効
* ・TXはFIFOエンプティ(最終文字送信中)で割り込み発生
* ・RXはFIFOに受信データありで割り込み発生
* ・ボーレートは19200bps(BRGH=0)
*/
extern SCI_CFG_t gDefaultSCI_cfg;
#define DEFAULT_SCI_CFG_PBCLK 80000000UL
#define DEFAULT_SCI_CFG_MODE 0x8000UL
#define DEFAULT_SCI_CFG_STA 0x9400UL
#define DEFAULT_SCI_CFG_BRT 19200
#define DEFAULT_SCI_CFG_IP 1
#define DEFAULT_SCI_CFG_IS 1
#define DEFAULT_SCI_CFG_CH 1
SCI_CFG_t gDefaultSCI_cfg = {
DEFAULT_SCI_CFG_PBCLK,
DEFAULT_SCI_CFG_MODE,
DEFAULT_SCI_CFG_STA,
DEFAULT_SCI_CFG_BRT,
DEFAULT_SCI_CFG_IP,
DEFAULT_SCI_CFG_IS,
DEFAULT_SCI_CFG_CH
};
SCI_REGDEF_t *SCI_init(SCI_CFG_t *cfg)
{
uint8_t ch = cfg->CH;
if((ch<1) || (6<ch) || (cfg == NULL)){
return NULL;
}
SCI_REGDEF_t *p = &SCIREG_def[ch-1];
*(uint_t*)p->MODEbits = 0;
*(uint_t*)p->STAbits = 0;
*(uint_t*)(p->STAbits) = cfg->STA;
//BRG設定
uint_t div = (cfg->MODE & 0x08) ? 4 : 16;
*p->BRG = cfg->PBCLK/(div*cfg->BRT) -1;
SCI_setIP(ch, cfg->IP, cfg->IS, "7");
//念のため割り込みフラグをクリアしておく
setTXIF(ch, 0, "1"); setRXIF(ch, 0, "2"); setEIF(ch, 0, "3");
setRXIE(ch, 1, "4"); setEIE(ch, 1, "5");
setTXIE(ch, 0, "6");
*(uint_t*)p->MODEbits = cfg->MODE;
*(uint_t*)(p->STAbits) = cfg->STA; //(H27/04/27訂正)
return p;
}
(H27/04/27追記)
当初のプログラムでも動作していたのですが、何かタイミングの問題があるようです。実験用の簡単なプログラムに使用すると期待通りには動いてくれません。
なぜかSTAレジスタへの書き込みが出来ていないようです。実験的に調べたところ、STAレジスタとMODEレジスタの設定には順番があるようです。
変更後の順番では今のところ問題は見つかっていません。
(追記ここまで)
(H27/05/ 4追記)
どうも何か問題がありそうなので、初期化関数を見直します。デバイスの隠し機能にアクセスする結果になっていたようで、データシート上では未実装のはずであるSTAレジスタのbit14が'1'になる現象が見つかりました。Cコンパイラのレジスタ定義部ではMD(モード?)という名でビットが配置されていることから、初期化の順番が良くないのは間違いないと思われます。
SCI_REGDEF_t *SCI_init(SCI_CFG_t *cfg)
{
uint8_t ch = cfg->CH;
if((ch<1) || (6<ch) || (cfg == NULL)){
SE_saveS("1");
return NULL;
}
SCI_REGDEF_t *p = &SCIREG_def[ch-1];
*(uint_t*)p->MODEbits = 0;
*(uint_t*)p->STAbits = 0;
//念のため割り込みフラグをクリアしておく
//setTXIF(ch, 0, "1"); //不要
setRXIF(ch, 0, "2"); setEIF(ch, 0, "3");
setRXIE(ch, 0, "4"); setEIE(ch, 0, "5");
setTXIE(ch, 0, "6");
//MODEとSTAの設定順序を逆にするとSTAへの書き込みが上手くいかない時がある
*(uint_t*)p->MODEbits = cfg->MODE;
*(uint_t*)(p->STAbits) = cfg->STA;
//BRG設定
uint_t div = (cfg->MODE & 0x08) ? 4 : 16;
*p->BRG = cfg->PBCLK/(div*cfg->BRT) -1;
SCI_setIP(ch, cfg->IP, cfg->IS, "7");
setRXIE(ch, 1, "8"); setEIE(ch, 1, "9");
return p;
}
(追記ここまで)
初期化関数では特に変わった点は無いのですが、送信処理のフラグ操作が少しだけ変則的かもしれません。
送信割り込みフラグTXIFは送信すべきデータが無いときには'1'の状態が正常です。とはいえ、これで割り込みがおきては困るので、割り込み許可フラグTXIEを'0'にすることで割り込みを禁止しています。送信バッファにデータを書き込んだときには
TXIF = 0; TXIE = 1;
の記述が必要になります。なお、これはPIC32USARTの仕様からきています。TXIFは送信完了条件がクリアされている(つまり送信バッファか送信シフトレジスタにデータが残っている)状態でしか'0'に出来ません。その後、送信完了状態になったあとは'1'になり、この状態で'0'を書き込んでもクリアできません。
次は割り込み処理関数です。割り込み処理関数では、幾つかの決め事を行います。
まず、全ての割り込み処理に関して必ずグローバル変数を定義します。これはメイン関数から割り込み処理に必要な情報を渡すためと、割り込み処理で管理するべき情報を保持します。例では割り込み処理から呼び出すCB(コールバック)関数と実行にかかった処理時間の最大値を定義しています。
メイン関数からこのグローバル変数にアクセスできるように各割り込み関数に対応する変数名は規則化されています。
USARTに関しては、ligSCI<CH番号>_paraが変数名です。メイン関数からは少なくともこの変数に割り込みCB関数を登録する必要があります。
もし、各CHごとに保管すべき内容が異なるときには、変数の型を新しく登録して使用します。
この場合でも変数名の命名規則とCB関数へのポインタの配置は変えない方が良い。
setInterruptIPL()関数は
以前説明したように、割り込み処理関数の__ISR()部に記述する割り込み優先度をIPL7SOFTに固定することでライブラリ化を行います。この設定では、割り込みの優先度管理に不都合を生じますので、該当する割り込み優先度(IPS設定値)を割り込みベクター番号をタグに読み出して、再設定を行います。
注意:
この割り込み処理では割り込みフラグをクリアできません。先に割り込み要因を排除する必要があります(PIC32の仕様)。このため必ずCB関数を登録して、CB関数内で割り込み要因の排除とフラグのクリアを行います。
typedef void(*CBfunc)(void);
/* 主として割り込み処理関数の情報を保持
*/
typedef struct {
CBfunc func; //ISRから呼び出すCB関数
TMR16_t maxTmr; //処理時間の最大値
} INT_CB_PARA_t;
INT_CB_PARA_t ligSCI1_para;
/* 割り込み処理ルーチン
* 送信終了・受信データあり・受信エラーで呼び出される。
*/
void __ISR(_UART_1_VECTOR, IPL7SOFT) SCI1_ISR(void)
{
setInterruptIPL(_UART_1_VECTOR);
TMR16_t s, e, diff;
TMR16_getTimer(&s); //開始タイマ値取得
if(ligSCI1_para.func != NULL)
(*ligSCI1_para.func)();
TMR16_getTimer(&e); //終了時タイマ値取得
TMR16_calcDiff(&s, &e, &diff); //差分を計算で求める
if(TMR16_cmp((TMR16_t*)&ligSCI1_para.maxTmr, &diff) < 0)
ligSCI1_para.maxTmr = diff;
}
時間計測は、最も単純には16ビットタイマをフリーランさせ、計測開始時と終了時のタイマ値によって経過時間を計算します。例えばタイマのクロック源となるPBクロックをCPUと同じ80MHz、プリスケーラを1/256に設定すると、タイマ値は(256/80us=)3.2us単位の分解能になります。
16ビットタイマで約200msまで計測可能なタイマができます。