2006/12/23 【ソフトウエア編TOPに戻る】
遅ればせながらですが、今回はSH2を使ったシリアル通信のプログラムをご紹介します。シリアル通信は109.パソコンとシリアル通信するなどの記事でH8による事例をご紹介していましたが、レジスタの使い方や基本的な部分などはほとんど一緒です。しかし、以前ご紹介した通信プログラムでは、受信時には内部処理が終る前に新しいデータは受信できませんでしたし、送信時にはひとつのデータ送信が終るまで次のデータの送信処理を開始することができませんでした。
そこで、今回ご紹介するプログラムではメモリーを使ったバッファを経由させることにします。受信時には受信バッファを、送信時には送信バッファを経由して外部との通信を行います。バッファを経由させる利点は、受信時には内部処理に掛かる時間に関係なく、送信元からのデータを連続受信して蓄えることが可能で、送信時には送信速度に束縛されることなく、内部の送信処理を先に終了させて他の処理に移ることができるという点です。外部との直接のデータ送受信は割り込み処理に任せることで、main関数内の本来の処理を、好きなペースで進めることができるというわけです。

216−1.バッファ付きシリアル通信プログラム概要
それでは、実際のプログラムをご紹介します。本プログラムはHEW4の開発環境下でSH2-7144F用に作成したもので、他の環境やターゲットに適用するには手を加えなければなりません。ユーザーが作成するmain関数で汎用的に利用できるよう、ヘッダファイル形式に取りまとめました。ヘッダファイル内は以下のプログラムから成ります。
| sio.h | ||
| └ | sci1_init(......); | sci1チャンネルの通信設定。 |
| sci1_putchar(...); | sci1チャンネル用の送信バッファにASCIIキャラクタデータを1文字分書き込む。 | |
| sci1_putstr(...); | sci1チャンネル用の送信バッファに\0(null)で終るASCII文字列を書き込む。 | |
| sci1_getchar( ); | sci1チャンネル用の受信バッファからACSIIキャラクタデータを1文字分読み込む。 | |
| int_sci1_rxi1( ); | sci1チャンネルの受信割り込みにより、受信データを受信バッファに書き込む。 | |
| int_sci1_txi1( ); | sci1チャンネルの送信割り込みにより、送信バッファからデータを送信する。 | |
| int_sci1_eri1( ); | sci1チャンネルの受信エラー処理。 | |
| int_sci1_tei1( ); | sci1チャンネルの送信エラー処理。 |
■受信動作の説明

外部機器からの受信データは、受信シフトレジスタRSRを介して一端受信データレジスタRDRに保持され、受信割り込みが許可になっていれば、受信割り込み関数int_sci1_rxi1();が実行されます。この割り込み処理で、受信バッファrx1buff[ ]に受信データを書き込んで行きます。このとき受信バッファのrx1.writeという構造体変数が示すアドレスに書き込みます。rx1.writeの値は、はじめは0から始まり、受信データが1文字書き込まれる度に+1されて行き、受信バッファの上限まで行ったら、再び0から上書きして行きます。事例のプログラムではバッファのサイズをBUFF_SIZE = 256としてありますので、256文字(256バイト)以上の受信データが読み出されずに蓄えられた場合、古い順にデータが捨てられていくことになります。
割り込み処理によって受信バッファにデータが書き込まれる度に、rx1.remainという構造体変数の値も+1して行きます。これは、受信バッファの中にいくつのデータが読み出されずに蓄えられているかの残量を示します。
受信バッファのデータは、sci1_getchar();関数を使って読み出します。読み出しはrx1.readという構造体変数が示すアドレスによって行います。rx1.readの値もはじめは0から始まり、1文字読み出すごと(sci1_getchar();が実行される度)に+1されて行き、受信バッファの上限まで行ったら、再び0から読み出します。同時にrx1.remainの値を−1して、データ残量の値をひとつ減らします。ここで一つ約束事があり、rx1.remainが0より大きな値でないと読出しができないようにしています。つまり、受信バッファからの読出しは、受信バッファへの書込みを追い越すことができないということです。
上記の仕組みにより、外部機器からの受信タイミングと内部処理での受信データ読み込みのタイミングは、受信バッファの容量以内であれば独立に動作させることが出来るようになります。
■送信動作の説明

内部処理で送信したいデータはsci1_putchar();関数を実行することにより送信バッファtx1buff[ ]に書込みます。この時、tx1.writeという構造体変数が示すアドレスに書き込みます。tx1.writeの値ははじめは0から始まり、送信データが1文字書き込まれる度に+1されて行き、送信バッファの上限まで行ったら、再び0から上書きして行きます。事例のプログラムでは受信バッファと同じくバッファサイズをBUFFSIZE = 256としてありますので、256文字(256バイト)以上の送信データが読み出されずに蓄えられようとした場合にはバッファオーバーフローということでエラーとし、sci1_putchar();の戻り値を−1として内部処理に対してエラーを教えます。
sci1_putchar();によって送信バッファにデータが書き込まれる度に、tx1.remainという構造体変数の値も+1して行きます。これは、送信バッファの中にいくつのデータが読み出されずに蓄えられているかの残量を示します。
sci1_putchar();によって送信バッファにデータを書き込んだタイミングで送信割り込みを許可すると、送信割り込みint_sci1_tx1();が実行されます。送信割り込み処理からのデータ読出しはrx1.readという構造体変数が示すアドレスによって行います。tx1.readの値もはじめは0から始まり、1文字読み出すごとに+1されていき、送信バッファの上限まで行ったら、再び0から読み出します。同時にtx1.remainの値を−1して、データ残量の値をひとつ減らします。送信割り込み処理内では、送信バッファから読み出したデータを送信データレジスタTDRに書込み、送信データレジスタエンプティフラグTDREをクリアすると、TDRのデータが送信シフトレジスタTSRに転送されて、シリアルポートから外部機器へのデータ送信が開始されます。そして、割り込み処理内で現在のデータ送信が終了するまでにTDRに対して次のデータをセットしておくことによってデータの連続送信を行います。tx1.remainが0、つまりデータ残量がなくなった時点で送信割り込みに禁止を掛けて、連続送信動作を終了します。
上記の仕組みにより、内部処理でのデータ送信のタイミングと外部機器への実際のデータ送信処理の速度は、送信バッファの容量以内であれば独立に動作させることが出来るようになります。
216−2.バッファ付きシリアル通信ヘッダファイル
では、バッファ付きシリアル通信の中心となるソースが記述されているヘッダファイルの内容をご紹介します。中に含まれる関数は前項でご紹介したとおりです。 ここをクリックするとダウンロードすることができます。7144fsio.h
■7144fsio.h
| /********************************************************** 7144fsio.h 2006.12.5 RIKIYA SH2_7144F バッファ付きSIO1 送受信動作 /*********************************************************/ #define BUFF_SIZE 256 void sci1_init(unsigned short,unsigned char,unsigned char,unsigned char); char sci1_putchar(char); void sci1_putstr(char *); char sci1_getchar(void); typedef struct { // バッファデータ型構造体宣言 unsigned short remain; // バッファ中の残データ数 unsigned short read; // 書込み位置 unsigned short write; // 読出し位置 char *buff; // 読書きデータ } BUFF; static char rx1buff[BUFF_SIZE]; // 受信データバッファ static char tx1buff[BUFF_SIZE]; // 送信データバッファ static BUFF rx1; // BUFF型受信データ構造体 static BUFF tx1; // BUFF型送信データ構造体 // SCI1割り込み関数プロトタイプ宣言 /////////////////////// void int_sci1_eri1(void); #pragma interrupt INT_SCI1_ERI1 void INT_SCI1_ERI1(void){ int_sci1_eri1();} void int_sci1_rxi1(void); #pragma interrupt INT_SCI1_RXI1 void INT_SCI1_RXI1(void){ int_sci1_rxi1();} void int_sci1_txi1(void); #pragma interrupt INT_SCI1_TXI1 void INT_SCI1_TXI1(void){ int_sci1_txi1();} void int_sci1_tei1(void); #pragma interrupt INT_SCI_TEI1 void INT_SCI1_TEI1(void){ int_sci1_tei1();} /* SCI1初期化 ********************************************/ #define PCLOCK 24000000 void sci1_init(unsigned short bps,unsigned char bit,unsigned char parity,unsigned char stopbit){ // bps 4800 / 9600 / 14400 / 19200 / 28800 / 31250 / 38400 // bit 8 / 7 // parity 0:無し / 1:偶数パリティ / 2:奇数パリティ // stopbit 1 / 2 // RX FIFO 初期化 rx1.remain = 0; rx1.read = 0; rx1.write = 0; rx1.buff = rx1buff; // TX FIFO 初期化 tx1.remain = 0; tx1.read = 0; tx1.write = 0; tx1.buff = tx1buff; MST.CR1.BIT._SCI1 = 0; // SCI1 スタンバイ解除 INTC.IPRF.WORD |= 0x000A; // SCI1 割込みLEVEL = 10 SCI1.SCR.BYTE = 0x00; // rx,tx割り込み送受信禁止 // 通信フォーマット設定 // SMR設定 SCI1.SMR.BYTE = 0; // SMR通信設定白紙 if(bit == 7){ SCI1.SMR.BIT.CHR = 1; // データ長7bit設定 } if(parity == 1){ SCI1.SMR.BIT._PE = 1; // パリティ付加 SCI1.SMR.BIT.OE = 0; // 偶数パリティ付加 } else if(parity == 2){ SCI1.SMR.BIT._PE = 1; // パリティ付加 SCI1.SMR.BIT.OE = 1; // 奇数パリティ付加 } if(stopbit == 2){ SCI1.SMR.BIT.STOP = 1; // ストップビット 2bit } // BRR設定 SCI1.BRR = (PCLOCK/(32*bps))-1; // BRR設定計算 // PFC設定 PFC.PACRL2.BIT.PA3MD=1; // 入力端子PA3 → RXD1 有効 PFC.PACRL2.BIT.PA4MD=1; // 出力端子PA4 → TXD1 有効 // SSR設定 SCI1.SSR.BYTE &= 0x87; // RDRF / ORER / FER / PER クリア // SCR設定 SCI1.SCR.BIT.TIE = 0; // 送信割り込み禁止 SCI1.SCR.BIT.RIE = 1; // 受信割り込み許可 SCI1.SCR.BIT.TE = 1; // 送信許可 SCI1.SCR.BIT.RE = 1; // 受信許可 } /* 一文字送信バッファへ格納 ******************************/ char sci1_putchar(char data){ char txdata; txdata = -1; // エラー時の戻り値 if(data > 0){ SCI1.SCR.BIT.TIE = 0; // 送信割り込み禁止 if(tx1.remain < BUFF_SIZE){ // データ量がバッファより少なければ tx1.buff[tx1.write] = data; // データをバッファに書き込み tx1.remain ++; // データ残数を1増 tx1.write ++; // 書込み位置を1増 if(BUFF_SIZE <= tx1.write){ // 書込み位置とバッファサイズの比較 tx1.write = 0; // 大きければ書込み位置を0に。 } txdata = data; // 戻り値用の代入処理 } SCI1.SCR.BIT.TIE = 1; // 送信割り込み許可 } return txdata; // txdataを返す } /* 文字列送信バッファへ格納 *****************************/ void sci1_putstr(char *str){ while(*str != '\0'){ // Nullの検出で終了 if(*str == '\n'){ // 改行処理 sci1_putchar(0x0a); // LFラインフィード sci1_putchar(0x0d); // CRキャリッジリターン } else{ sci1_putchar(*str); // 一文字送信処理 } str++; // ポインタの1増 } return; } /* 送信割り込み *****************************************/ void int_sci1_txi1(void){ char data; if(tx1.remain > 0){ // データ残量が0より大きければ data = tx1.buff[tx1.read]; // バッファからデータを読出し tx1.remain --; // データ残数を1減 tx1.read ++; // 読出し位置を1増 if(tx1.read >= BUFF_SIZE){ // 読出し位置とバッファサイズの比較 tx1.read = 0; // 大きければ読出し位置を0に。 } while(SCI1.SSR.BIT.TDRE == 0){} // 送信レジスタが空になるまで待つ。 SCI1.TDR = data; // 読み出したデータを送信レジスタへ。 SCI1.SSR.BIT.TDRE = 0; // 送信レジスタエンプティフラグのクリア } else { SCI1.SCR.BIT.TIE = 0; // データ残がなければ送信割り込み禁止 } } /* 送信終了割り込み *************************************/ void int_sci1_tei1(void){ SCI1.SCR.BIT.TEIE = 0; // 送信終了割り込み禁止 } /* 一文字受信バッファから読出し**************************/ char sci1_getchar(void){ char data; SCI1.SCR.BIT.RIE = 0; // 受信割り込み禁止 if(rx1.remain == 0){ // バッファデータが無ければ data = -1; // -1を戻り値とする。 } else { // バッファデータがあれば data = rx1.buff[rx1.read]; // データのバッファ読み出し rx1.remain --; // データ残数を1減 rx1.read ++; // 読出し位置を1増 if(BUFF_SIZE <= rx1.read){ // 読出し位置とバッファサイズの比較 rx1.read = 0; // 大きければ読出し位置を0に。 } } SCI1.SCR.BIT.RIE = 1; // 受信割り込み許可 return data; // 戻り値 data } /* 受信割り込み ****************************************/ void int_sci1_rxi1(void){ char data; data = SCI1.RDR; // 受信データ取り込み SCI1.SSR.BIT.RDRF = 0; // 受信レジスタフルフラグクリア if(rx1.remain < BUFF_SIZE){ // データ量がバッファより少なければ rx1.buff[rx1.write] = data; // 受信データをバッファに書込み rx1.remain ++; // データ残数を1増 rx1.write ++; // 書込み位置を1増 if(rx1.write >= BUFF_SIZE){ // 書込み位置とバッファサイズの比較 rx1.write = 0; // 大きければ書込み位置を0に。 } } } /* 受信エラー割り込み **********************************/ void int_sci1_eri1(void){ SCI1.SSR.BYTE &= 0x87; // RDRF / ORER / FER / PER クリア } |
上記のプログラムの細かな説明は省略させて頂きます。すみません。 基本的には前項216−1でご説明したバッファリング動作を記述しているもので、コメント部分を参照してみて下さい。
ただし、1点だけ。
// SCI1割り込み関数プロトタイプ宣言 ///////////////////////
で示している割り込み関数のプロトタイプ宣言は、もともとintprg.cの中に記述しているものですが、今回はこの7144fsio.hに含めてしまいました。なので、次に示すようにのintprg.cの中を修正する必要があります。
■216−2.割り込み処理プログラム宣言部の手直し。
前項で割り込み関数のプロトタイプ宣言を7144fsio.h内部で行うため、HEW4の環境下でもともとそれらが記述されているintprg.cを以下のように手直しします。
■intprg.c の手直し
// 128 SCI0 ERI0 void INT_SCI0_ERI0(void){/* sleep(); */} // 129 SCI0 RXI0 void INT_SCI0_RXI0(void){/* sleep(); */} // 130 SCI0 TXI0 void INT_SCI0_TXI0(void){/* sleep(); */} // 131 SCI0 TEI0 void INT_SCI0_TEI0(void){/* sleep(); */} // 132 SCI1 ERI1 //void INT_SCI1_ERI1(void){/* sleep(); */} // 133 SCI1 RXI1 //void INT_SCI1_RXI1(void){/* sleep(); */} // 134 SCI1 TXI1 //void INT_SCI1_TXI1(void){/* sleep(); */} // 135 SCI1 TEI1 //void INT_SCI1_TEI1(void){/* sleep(); */} // 136 A/D ADI0 void INT_ADI0(void){/* sleep(); */} // 137 A/D ADI1 void INT_ADI1(void){/* sleep(); */} // 138 Reserved |
intprg.cのソース273行目あたりから、// 132 SCI1 ERI1 で始まるSCI1チャンネルの割り込みに関する一連の関数が並びますが、それぞれの行の先頭に // を追記してコメント化します。これにより、7144fsio.h中で宣言している割り込み関数とのダブりを解消します。
// 132 SCI1 ERI1
//void INT_SCI1_ERI1(void){/* sleep(); */}
// 133 SCI1 RXI1
//void INT_SCI1_RXI1(void){/* sleep(); */}
// 134 SCI1 TXI1
//void INT_SCI1_TXI1(void){/* sleep(); */}
// 135 SCI1 TEI1
//void INT_SCI1_TEI1(void){/* sleep(); */}
■216−3.リセット処理部の手直し
プログラム場で割り込み処理を行う場合には必ず必要な設定です。たまにSH2とHEW4で遊ぶとすっかり忘れてしまって「動かないー!」とさんざん悩みます。
■resetprg.c の手直し
| /***********************************************************************/ /* */ /* FILE :resetprg.c */ /* DATE :Mon, May 15, 2006 */ /* DESCRIPTION :Reset Program */ /* CPU TYPE :SH7144F */ /* */ /* This file is generated by Renesas Project Generator (Ver.4.0). */ /* */ /***********************************************************************/ #include <machine.h> #include <_h_c_lib.h> //#include <stddef.h> // Remove the comment when you use errno //#include <stdlib.h> // Remove the comment when you use rand() #include "typedefine.h" #include "stacksct.h" //#define SR_Init 0x000000F0 #define SR_Init 0x00000000 #define INT_OFFSET 0x10 【以下略】 |
resetprg.cのソース冒頭の部分に上記のような記述があり、初期状態として #define SR_Init 0x000000F0の記述があります。これはCPUの基本動作を決定するSR(ステータスレジスタ)の初期値を設定しているものですが、これを上記の青色の記述のように修正して、#define SR_Init 0x00000000としてしまいます。 F を 0 に書き換えることによって割り込みマスクレベルを0に設定し、1以上の割り込み優先レベルに設定されている割り込みであれば実行を許可するようにしています。
■216−4.main関数での利用例
では最後に、7144fsio.hを利用した実際のシリアル通信プログラムの事例をご紹介します。事例といっても最も簡単な処理として、SCI1ポートで繋いだパソコンでハイパーターミナルを立ち上げ、sci1_init( );関数で設定した通信条件で接続し、ハイパーターミナル上で打ち込んだ半角英数文字をエコーバックするというだけのものです。
| /********************************************************** sio.c 2006.12.5 RIKIYA SH2_7144F バッファ付き送受信動作 /*********************************************************/ #include "iodefine.h" #include "7144fsio.h" /* メイン関数 ********************************************/ main(void) { char data; int i; sci1_init(38400,8,0,1); // SCI1初期化 38.4kbps,data8bit,parity0,stop1bit while(1){ data = sci1_getchar(); // 受信バッファからのデータ読み取り sci1_putchar(data); // 送信バッファへのデータ書込み } } |
#include "7144fsio.h" のインクルードだけは忘れないで下さい。その後はsci1チャンネルの送受信バッファの存在や、送受信割り込み処理などについては意識することなく、シリアル送受信処理のプログラムを記述することができます。
今回はバッファ経由でのシリアル通信処理についてご紹介しました。動作の検証としては、受信処理においては内部処理に大きなwaitによる処理遅延を持たせ、ハイパーターミナルからの勝手な送信を滞りなく受信して、遅れて実施される内部処理でもデータが間違いなく受信できていることを確認し、送信処理においては割り込み送信処理にwaitによる処理遅延を持たせ、内部処理で高速に送信処理を済ませたデータが、送れて実施される実際の送信処理でも間違いなく外部のパソコンで受け取れることを確認しました。実はまだ十分な検証を実施しているとは言いがたいところもありますので、もしご利用される場合には十分ご注意下さい。
以前よりご紹介していたその場のタイミングでの送受信処理よりも、データの取りこぼしが少なく信頼性が高く、処理の効率的にも高いものになるのではないかと考えています。