Renesas Synergy™

FAQ 1007017 : ソフトウェアでの半二重3線式シリアル・スレーブ機能の実現について2(78K0S/Kx1+)

ソフトウェアでの3線式シリアルのスレーブ機能の検討」の結果から
スレーブとして送受信/受信動作を行う
CSはエッジ検出割り込みを使用(データ転送まで約12μS必要)
CSに対して、BUSY信号をサポート
通信タイプ1(SCK立ち下がりで送信、立ち上がりで受信)
転送速度は 160kbps程度(83kbps以上)までに対応
の条件での3線式シリアルのスレーブ機能をソフトウェアによるポート制御で実現してみます。通信はループで処理することで、プログラムのサイズを小さくします。

Ⅰ 仕様検討
1. 接続

ここでは、上記の端子構成例に示すように2本のハンドシェイク信号を含めて5本のポートを使用するものとします。
ソフトウェアでの半二重3線式シリアル・スレーブ機能の実現について1とはSI信号とSCK信号に使用する端子が入れ替わっています。)
2. 動作概要
SPIのマスタからのCS信号の立ち下がりをエッジ検出割り込みにより検出し、ベクタ割り込み処理により通信を起動します。送信データの有無は、事前にフラグに設定しておくものとします。SCK信号の立ち下がりでデータをSO端子から送信しますが、フラグの状態で送信データがない場合にはダミー・データ(0FFH)をSO端子から送信します。引き続いて、SCK信号の立ち上がりでSI端子の状態を読み込みます。8ビットのデータを読み込むとその結果をバッファの中に保存します。その後、残り送信データがなくなれば、フラグをクリアして以降は受信で動作(ダミー・データを送信)することになります。CS信号を確認して、もしハイ・レベルであれば処理を終了します。ロウ・レベルが継続しているときには、処理を継続します。
なお、データはMSBファーストで転送します。また、バッファは16バイトの容量を確保しておき、先頭から使用するだけとし、リング・バッファ構造にはしません。

3. 全体の処理
ここで考えている通信処理全体の概略状態遷移図を以下に示します。


初期状態(待機状態)では割り込みは禁止で、送信データの設定が可能です。その状態でEI命令により割り込みを許可すると通信が可能(状態遷移の通信可能状態)になります。その状態で、CS信号が立ち下がると、ベクタ割り込みを受け付けて、スレーブ通信状態となります。
スレーブ通信状態になると、通信に使用するBUSY信号やSO信号を出力に設定します。必要なポインタの設定を行ってからBUSY信号を立ち下げて、SCK信号の立ち下がりを待ちます。SCK信号が立ち下がると、SO信号に出力すると同時にBUSY信号を立ち上げます。このとき、送信するデータがないときにはダミー・データ(0FFH)を出力するので、SO端子はハイ・レベルとなります。その後、SCK信号の立ち上がりに同期してSI信号を読み込みます。8ビット分の受信が完了すると、データをバッファに保存して、送信データの残り数を確認し、BUSY信号を立ち下げて次の通信準備ができたことをマスタに通知して、次のデータ通信か通信完了(CS信号の立ち上がり)を待ちます。
このあとは3つの条件に分かれます。

① 次の送信データが残っている状態で、CS信号が引き続きロウ・レベルであれば、次データ送信のためのBUSY信号を立ち下げて次の通信準備ができたことをマスタに通知して、SCK信号の立ち下がり待ちとなります。

② 送信データがなくなったが、CS信号がロウ・レベルを継続している場合には、BUSY信号を立ち下げて、次の通信準備ができたことをマスタに通知して、SCK信号の立ち下がり待ちとなります。以降はダミー・データを送信して受信処理だけを行ないます。

③ CS信号がハイ・レベルになった場合には、BUSY信号をHi-Zにしてから割り込み処理を抜けて、通信可能状態になります。


Ⅱ 制御プログラム検討
1. 通信の起動(CS信号の立ち下がり検出)
通信を開始するタイミングはCS信号の立下りをエッジ検出したベクタ割り込みで検出するものとします。このとき、送信データがあるかどうかのフラグでデータを送信するかダミー・データを送信するか判断します。データ送信の場合には、バッファからデータを読み出して送信の準備を行います。CS信号の立下りから実際にデータを送受信(特に送信)できるまでには80クロック+割り込み応答時間(最大19クロック)で8MHz動作とすると12μsが必要になります。ただし、CS信号の立下りから6クロック+割り込み応答時間(最大19クロック)でBUSY信号が出力されますし、それ以前のBUSY信号はHi-Z(=ハイレベル)となっているので、BUSY信号を利用することで、この部分の応答速度をカバーすることができます。


この起動部分のプログラム例を以下に示します。BUSY(P42の出力ラッチ)には初期状態で1がセットされているので、1行目でPM4を設定した段階 (①)でハイ・レベルが出力されます。通信可能(下記の例では送信可能)になると(②)、BUSYを立ち下げて、マスタに対して通信準備ができたことを示 しています。なお、実際に送信するためのSCK信号確認までは、さらに13クロックかかります。最初のビットの処理を2ビット目以降と分けると、この時間を5クロックまで短縮することはできますが、マスタでのBUSY立ち下がり確認からSCK出力までの時間を考えるとあまり意味はないと考えられます。


__INTP1:
MOV     PM4,# 11011011b         ; 6:So(P45)とBUSY(P42)を出力に
PUSH    AX                      ; 4:使用レジスタをセーブ
PUSH    HL                      ; 4:
PUSH    DE                      ; 4:
PUSH    BC                      ; 4:
MOVW    AX,TxPointer            ; 6:送信ポインタ読み出し
MOVW    HL,AX                   ; 4:ポインタをセット
MOVW    AX,RxPointer            ; 6:受信ポインタ読み出し
MOVW    DE,AX                   ; 4:ポインタをセット
RTxByte:
MOV     A,#0FFH                 ; 6:ダミー・データの設定
BF      TxFlag,$RxData          ;10:フラグが0なら受信処理へ

RTxLoop:
MOV     A,[HL]                  ; 6:送信データの読み出し
Loop1:
BT      P4.3,$RTxExit           ;10:CSの立ち上がりで抜ける
RxData:
MOV     B,#8                    ; 6:ビット数をセット
CLR     P4.2 ; 6:BUSYを立ち下げる
;
;  通信準備完了
;


上記の青い部分は送信データの有無により送信データを選択している部分です。この部分で、データの間隔が2μs長くなってしまいます。この処理をなくすには、送信データ・バッファに最初からダミー・データをセットしておけばいいことになります。

2. 1キャラクタ(8ビット)の通信処理
通信可能状態で、CS信号の立ち下がりを検出したらスレーブ通信状態に移ります。通信処理がスタートすると、SCK信号の立ち下がりを検出して、データをSO端子に出力します。その際に同時にBUSY信号を立ち上げて、送信処理を開始したことを示します。この際の処理では実際にSO信号を出力する際のMOV命令でBUSYを立ち上げる処理も同時に行うことで、処理時間の増加をなくしています。その部分を含めた処理は以下のようになります。ここでは、できるだけSCK信号の立ち下がりからの遅延を小さくするため、送信データが0か1かの判定を前もって行っておきます。


CLR1    CY                      ; 2:次のビット用にクリア
Loop2:
ROLC    A,1                     ; 2:出力データをキャリーへ
BC      $Data1                  ; 6:データが1なら分岐
;
;  出力データが0の場合の処理
;
Data0:
BT      P4.4,$Data0 ;10:SCK立ち下がり待ち
MOV     P4,#00000100b           ; 6:送信&BUSYセット
MOV     SIO,A                   ; 4:データをセーブ
CLR1    CY                      ; 2:次のビット用にクリア
Loop3:
MOV     A,P4                    ; 4:ポートの読み出し
;               BF      A.4,$Loop3              ; 8: SCK立ち上がり待ち
AND     A,#0000001B             ; 4:SIデータ以外をマスク
OR      A,SIO                   ; 4:ビットを追加
DBNZ    B,$Loop2                ; 6:8ビット分繰り返し
BR      $RTxEnd
;
;  出力データが1の場合の処理
;
Data1:
BT      P4.4,$Data1             ;10:SCK立ち下がり待ち
MOV     P4,#00100100b           ; 6:送信&BUSYセット
MOV     SIO,A                   ; 4:データをセーブ
CLR1    CY                      ; 2:次のビット用にクリア
Loop4:
MOV     A,P4                    ; 4:ポートの読み出し
;               BF      A.4,$Loop4              ; 8: SCK立ち上がり待ち
AND     A,#0000001B             ; 4:SIデータ以外をマスク
OR      A,SIO                   ; 4:ビットを追加
DBNZ    B,$Loop2                ; 6:8ビット分繰り返し


データのビット送信が完了したら受信処理を行います。本来はSCK信号の立ち上がりを確認してSI信号を取り込みます(プログラムの緑色のコメント・アウト部)。ここでは、通信速度をできるだけ低下させないように、この確認を省略(コメント・アウト)しています。転送速度が遅い場合にはSCK信号の立ち上がりより前にSI信号を取り込む可能性があります。殆んどの場合に、SCK信号の立ち下がりで出力されることになるので、送信処理の期間にはデータは確定していると考えられるので、コメント・アウトしています。
ここでの送受信処理ではデータは共通の変数(変数SIO)で処理しています。送信のために送信データ左シフトすることで、ビット0を受信用に空けておき、SI信号(ビット0)をORすることで新たなビットを取り込んでいきます。
Bレジスタをループ・カウンタとして、Loop2までを8回ループすることで、1バイトのデータの送受信が完了します(Bレジスタを使用することで、2クロック分短くなります)。

実際の送信タイミング、受信タイミングは以下のようになります。このプログラムは1ビット分の送信と受信で48クロック必要です。つまり1ビットの時間は6μsとなります。SCK信号の立ち上がりをチェックしていないので、次ビットのBT命令の実行までSCK信号が連続していなければ(6μs以上の長さで83kbps以下の転送レートでなければ)、対応でき、最大で166kbpsの転送速度まで可能です。


3. キャラクタ完了時の処理
1キャラクタの送信、受信タイミングが完了したら、受信データを受信バッファに保存し、送信バッファの次の送信データがあるかを確認します。この部分のプログラム例を以下に示します。


;
;       バイト・データの送受信完了
;
RTXEND:
MOV     [DE],A                  ; 6:受信バッファに格納
INCW    DE                      ; 4:受信ポインタ更新
INC     RxCount                 ; 4:受信データ数更新
MOV     A,#0FFH                 ; 6:ダミー・データをセット
BF      TxFlag,$Loop1           ;10:送信データなしで分岐
INCW    HL                      ; 4:送信ポインタ更新
DBNZ    TxCount,$RTxLoop        ; 8:残り送信データ有れば分岐
CLR1    TxFlag                  ; 6:送信フラグをクリア
BR      Loop1                   ; 6:受信のみ(0FFH送信)


ここでは、以下に示すように処理が3つの場合に分けられます。

・まだ、送信データがある場合           →         次のデータ読み出して通信処理へ
・最後のデータ送信が完了した場合     →         送信フラグをクリアして受信処理へ
・受信のみを処理中である場合          →         ダミー・データをもって受信処理へ


この部分で処理時間が最もかかるのは最後のデータの送信が完了したときです。この場合には、上記の命令を全て実行することになり、上記だけで54クロックとなり、これに通信準備の22クロックを加算して76クロックでBUSY信号の立ち下げとなります。

BUSY信号が立ち下がるのは最後のビットの受信完了から76クロックとなり、9μsつまり1.5ビット分の間隔が空くことになります。これを1 ビット分の時間にするためには、ダミー・データを前もって送信バッファに準備しておき、送信フラグを使用しなくても済むようにします。

4. 転送完了時の処理(CS信号の立ち上がり)
上記のプログラムでは、次のキャラクタの転送開始処理でCS信号の確認を行い、CS信号が引き続きロウ・レベルであれば転送処理を継続します。
なお、このプログラム例では、CS信号は8ビット目の受信処理完了から35クロックでしかチェックしていません。そのため、マスタは最後のデータの転送が完了したらCS信号を切るようにしてください。どうしても、35クロックまでに不可能な場合には、転送を1ビット目と2ビット目以降で処理を変える必要があります。


;
;       CSの立ち上がりで、受信処理完了へ
;
RTXEXIT:
SET1    BUSYPort                ;BUSYを立ち上げ
RTXEXIT2:
MOV     PM4,#0FFH               ;ポートをHi-Zに
MOVW    AX,HL
MOVW    TXPOINTER,AX            ;送信ポインタを更新
MOVW    AX,DE
MOVW    RXSETPOINTER,AX         ;受信ポインタを更新
CLR1    PIF1
POP     BC
POP     DE                      ;レジスタの復帰
POP     HL
POP     AX
RETI



Ⅲ 周辺プログラム検討

ここでは、これまで説明してきた制御プログラムを使用するためのサポート・プログラムについて示します。ここで触れるプログラムは以下の通りです。なお、ここで触れるプログラムはサブルーチン化され、C記述のプログラムからも関数呼び出しできるようにしてあります。

①初期化処理
②受信バッファの初期化
③送信ステータス確認
④受信ステータス確認
⑤1バイトのデータを送信バッファに追加
⑥送信バッファ設定
⑦受信バッファから1バイトのデータ読み出し
⑧受信バッファ設定


1. 初期化処理
制御プログラムを使用するために、ポインタ類の初期化や受信データ・バッファのクリア及び割り込みの設定を行います。この処理はバッファを制御するためのポインタや変数を設定するため、割り込み禁止で処理させます。通常は起動後に1回使用します。


;***********************************************************************
;                                                                      *
;       送信/受信(バッファ)の初期化                                 *
;                                                                      *
;  送信/受信で使用するバッファを内部で定義したバッファに設定し、受信  *
;バッファはデータを0クリアしておきます。                              *
;  また、CS信号の検出で使用するINTP1に立ち下がりエッジ検出を有効*
;に設定し、割り込みマスクの解除までの処理を行います。                  *
;  入力データ   :なし                                                 *
;  戻り値       :なし                                                 *
;  使用レジスタ :なし                                                 *
;                                                                      *
;       extern void _init_buff(void);                                  *
;                                                                      *
;***********************************************************************
;
PUBLIC  __init_buff,INIT_BUFF
__init_buff:
INIT_BUFF:
PUSH    PSW                     ;IEフラグをセーブ
DI                              ;以下の処理は割り込み禁止で行う
PUSH    HL
PUSH    AX

MOVW    AX,#TxBuff
MOVW    TxPointer,AX            ;送信データ読み出しポインタ初期化
MOVW    SetPointer,AX           ;送信データ設定ポインタ初期化
MOV     TxCount,#0              ;データ数を初期化
CLR1    TxFlag                  ;送信フラグをクリア

MOVW    AX,#RxBuff              ;受信バッファの初期化初期化
MOVW    RxPointer,AX            ;受信データ読み出しポインタ初期化
MOVW    RxSetPointer,AX         ;受信データ格納用ポインタ初期化
MOV     RxCount,#16             ;データ数を初期化
MOVW    HL,AX
MOV     A,#0
Init_Loop:
MOV     [HL],A                  ;受信バッファをクリア
INCW    HL
DBNZ    RxCount,$Init_Loop
POP     AX
POP     HL
MOV     P4,#00100100b           ;SOとBUSY出力をハイにする
MOV     INTM1,#00000000b        ;INTP1を立ち下がりエッジ検出に
CLR1    PIF1
CLR1    PMK1
POP     PSW
RET


2. 受信バッファの初期化
このプログラムでは送信を行ったときにも同時に受信も行うため、半二重通信の場合には無意味なデータを受信バッファに取り込みます。このデータを1バイト 単位で読み出して確認するのは無駄です。そこで、明らかに送信時に受信したデータだけの場合に受信バッファをクリアする処理を準備しておきます。


;***********************************************************************
;                                                                      *
;       受信(バッファ)の初期化                                       *
;                                                                      *
;  受信で使用するバッファを内部で定義したバッファに設定し、データを    *
;0クリアしておきます。                                                *
;                                                                      *
;       extern void _init_rxbuff(void);                                *
;                                                                      *
;***********************************************************************
;
PUBLIC  __init_rxbuff,INIT_RXBUFF
__init_rxbuff:
INIT_RXBUFF:
PUSH    PSW                     ;IEフラグをセーブ
DI                              ;以下の処理は割り込み禁止で行う
PUSH    HL
PUSH    AX
MOVW    AX,#RxBuff              ;受信バッファの初期化
MOVW    RxPointer,AX            ;受信データ読み出しポインタ初期化
MOVW    RxSetPointer,AX         ;受信データ格納用ポインタ初期化
MOV     RxCount,#16             ;データ数を初期化
MOVW    HL,AX
MOV     A,#0
Init_Loop2:
MOV     [HL],A                  ;受信バッファをクリア
INCW    HL
DBNZ    RxCount,$Init_Loop2
POP     AX
POP     HL
POP     PSW
RET


3. 送信ステータス確認
送信データ・バッファの未送信のデータ数を戻します。結果はCレジスタで確認できますが、それ以外にCYフラグでバッファが空かどうかの確認もできます。通常は、送信データ・バッファに送信データをセットした後で、送信が完了したかを確認するために使用します。


;***********************************************************************
;                                                                      *
;       送信データ・バッファのステータス確認                           *
;  入力データ   :なし                                                 *
;  戻り値       :Cレジスタ(空ならCY=0)                         *
;  使用レジスタ :なし                                                 *
;  バッファの残りデータ数を戻します。                                  *
;       extern unsigned char _check_txbuff(void);                      *
;                                                                      *
;***********************************************************************
;
PUBLIC  __check_txbuff,CHECK_TXBUFF
__check_txbuff:
CHECK_TXBUFF:
PUSH    AX
MOV     A,TxCount               ;バッファ中のデータ数読み出し
MOV     C,A                     ;戻り値にセット
SUB     A,#1                    ;空ならCYをセット
NOT1    CY                      ;CYを反転
POP     AX
RET


4. 受信ステータス確認
受信データ・バッファの中にある受信データの数を戻します。受信したデータ数はCレジスタに格納されて戻ります。データがあれば、1バイト読み出し等でバッファから読み出してください。


;***********************************************************************
;                                                                      *
;       受信データ・バッファのステータス確認                           *
;  入力データ   :なし                                                 *
;  戻り値       :Cレジスタ、CYフラグ(バッファ空)                 *
;  使用レジスタ :なし                                                 *
;  バッファ中のデータ数を戻します。データが存在するときにデータを読み  *
;出してください。                                                      *
;       extern unsigned char _check_rxbuff(void);                      *
;                                                                      *
;***********************************************************************
;
PUBLIC  __check_rxbuff,CHECK_RXBUFF
__check_rxbuff:
CHECK_RXBUFF:
PUSH    AX
MOV     A,RxCount               ;バッファ中のデータ数読み出し
MOV     C,A                     ;戻り値にセット
SUB     A,#1                    ;データ数0ならCYをセット
POP     AX
RET


5. 1バイトのデータを送信バッファに追加
Xレジスタにセットされたデータを送信データ・バッファに追加します。なお、バッファの大きさは管理していないので、送信バッファのサイズ以上にデータを追加しないようにする必要があります。送信バッファ内のデータ数は送信ステータス確認で確認してください。


;***********************************************************************
;                                                                      *
;       送信データ1バイトをバッファに追加                             *
;  入力データ   :Xレジスタ                                           *
;  戻り値       :なし                                                 *
;  使用レジスタ :なし                                                 *
;  バッファのデータが全て送信されると、TxFlagが0になります。          *
;       extern void _add_data(unsigned char);                          *
;                                                                      *
;***********************************************************************
;
PUBLIC  __add_data,ADD_DATA
__add_data:
ADD_DATA:
PUSH    HL
PUSH    AX
MOVW    HL,AX                   ;データをセーブ
MOVW    AX,SetPointer           ;セット・ポインタ読み出し
XCHW    AX,HL
MOV     A,X
MOV     [HL],A                  ;データをバッファにセット
INCW    HL                      ;ポインタ更新
XCHW    AX,HL                   ;H:データ、AX:ポインタ
MOVW    SetPointer,AX           ;ポインタ保存
PUSH    PSW                     ;PSWのセーブ
DI                              ;割り込み禁止
INC     TxCount                 ;データ数を+1
SET1    TxFlag                  ;送信要求フラグをセット
MOV     P4,#00100100b           ;SOとBUSY出力をハイにする
POP     PSW
POP     AX
POP     HL
RET


6. 送信バッファ設定
複数バイトのデータを送信する場合に1バイトのデータ送信を 必要な回数使用しても実現できますが、それよりもバッファを切り替えたほうが便利です。ここでは、AXレジスタに送信したいデータが格納されたバッファの アドレス、スタックにデータ数を設定してサブルーチン・コールします。このようなパラメータの渡し方はCC78K0Sの引数の渡し方とあわせるためのもの です。アセンブラで使用するだけの場合には、データ数もレジスタで渡すようにした方が処理は簡単になります。送信が完了したかは、送信ステータス確認で確認することができます。


;***********************************************************************
;                                                                      *
;       送信データNバイトをバッファにセット                           *
;  入力データ   :AXレジスタ(バッファの先頭アドレス):第一引数     *
;               :スタック(データ数)                  :第二引数     *
;  戻り値       :なし                                                 *
;  使用レジスタ :なし                                                 *
;  バッファのデータが全て送信されると、TxFlagが0になります。          *
;                                                                      *
;       extern void _set_data(unsigned int,unsigned char);             *
;                                                                      *
;***********************************************************************
;
PUBLIC  __set_data,SET_DATA
__set_data:
SET_DATA:
PUSH    PSW                     ;PSWをセーブ
DI                              ;割り込み禁止
PUSH    HL                      ;使用レジスタをセーブ
PUSH    AX
MOVW    TxPointer,AX            ;送信ポインタを変更
MOVW    SetPointer,AX           ;送信セット・ポインタを変更
MOVW    AX,SP                   ;スタック・ポインタ読み出し
MOVW    HL,AX
MOV     A,[HL+7]                ;データ数読み出し
;
; ここでのスタックの内容は以下の通り
;       HL(SP)  ---> X
;       HL+1    ---> A
;       HL+2    ---> L
;       HL+3    ---> H
;       HL+4    ---> PSW
;       HL+5    ---> 戻り番地(下位)
;       HL+6    ---> 戻り番地(上位)
;       HL+7    ---> データ数
;       HL+8    ---> ダミー・データ
;
MOV     TxCount,A               ;データ数を設定
CLR1    TxFlag                  ;フラグをクリアしておく
AND     A,A                     ;データ数チェック
BZ      $Set_Data_End           ;データ数0ならポインタセットのみ
SET1    TxFlag                  ;送信要求フラグをセット
POP     HL
PUSH    HL
ADD     A,L
MOV     X,A
MOV     A,H
ADDC    A,#0
MOVW    SetPointer,AX           ;送信セット・ポインタを変更
Set_Data_End:
MOV     P4,#00100100b           ;SOとBUSY出力をハイにする
POP     AX                      ;レジスタの復帰
POP     HL
POP     PSW
RET


7. 受信バッファから1バイトのデータ読み出し
受信データ・バッファからもっとも古いデータを読み出して、Cレジスタに格納して戻ります。もし、受信データ・バッファにデータがない場合にはCレジスタには0が格納され、CYフラグがセットされます。読み出してデータがなくなったら、ポインタを初期値に戻します。


;***********************************************************************
;                                                                      *
;       受信データ1バイトをバッファから読み出す                       *
;  入力データ   :なし                                                 *
;  戻り値       :Cレジスタ(データなければCY=1)                     *
;  使用レジスタ :なし                                                 *
;  バッファから最も古いデータを読み出し、残りデータ数を-1します。    *
;       extern unsigned char _get_data(void);                          *
;                                                                      *
;***********************************************************************
;
PUBLIC  __get_data,GET_DATA
__get_data:
GET_DATA:
PUSH    AX
MOV     A,RxCount               ;データ数を読み出し
CMP     A,#1                    ;データ数チェック
MOV     C,A                     ;ダミーの戻り値をセット
BC      $GetEnd                 ;データなければ戻る
PUSH    HL
MOVW    AX,RxPointer            ;リード・ポインタ読み出し
MOVW    HL,AX                   ;HLレジスタにセット
INCW    AX                      ;ポインタ更新
MOVW    RxPointer,AX            ;ポインタ保存
MOV     A,[HL]                  ;データ読み出し
MOV     C,A                     ;戻り値をセット
POP     HL
DEC     RxCount                 ;データ数を-1
BNZ     $GetEnd                 ;残りデータ有りなら分岐
PUSH    PSW
DI                              ;以下の処理は割り込み禁止
MOVW    AX,#RxBuff              ;受信バッファの初期化
MOVW    RxPointer,AX            ;データ読み出しポインタ初期化
MOVW    RxSetPointer,AX         ;受信データ格納用ポインタ初期化
POP     PSW
GetEnd:
POP     AX
RET


8. 受信バッファ設定
受信バッファをパラメータとして渡されたアドレスに変更します。


;***********************************************************************
;                                                                      *
;       受信バッファの設定                                             *
;  入力データ   :AXレジスタ(受信用バッファの先頭アドレス)         *
;  戻り値       :なし                                                 *
;  受信データを格納するバッファを指定します。また、受信データ数を0に  *
;設定します。受信完了は受信データ・バッファのステータス確認で行います。*
;       extern void _set_rxbuff(unsigned int);                         *
;                                                                      *
;***********************************************************************
;
PUBLIC  __set_rxbuff,SET_RXBUFF
__set_rxbuff:
SET_RXBUFF:
PUSH    PSW
DI
MOVW    RxPointer,AX                    ;リード・ポインタをセット
MOVW    RxSetPointer,AX                 ;セット・ポインタをセット
MOV     RxCount,#0                      ;データ数をクリア
POP     PSW
RET


9. データ領域の定義
内部で使用している変数やポインタ及びバッファは以下のように定義してあります。これらを直接操作することはありませんが、作業用でこれらの領域を確保しています。そのため、メモリの使用量には注意が必要です。


;
;       データ・ポインタ領域
;
DSEG    saddrp
TxPointer:
DS      2       ;送信用データ読み出しポインタ
SetPointer:
DS      2       ;送信データ設定用ポインタ
RxPointer:
DS      2       ;受信データ読み出しポインタ
RxSetPointer:
DS      2       ;受信データ格納用ポインタ

DSEG    saddr
;
;       データ・カウンタ領域
;
TxCount:
DS      1       ;送信データ数カウンタ
RxCount:
DS      1       ;受信データ数カウンタ
BitCount:
DS      1       ;ビット数カウント用

;
;       データ領域
;
TxBuff:
DS      16      ;送信データ・バッファ領域
RxBuff:
DS      16      ;受信データ・バッファ領域
SIO:
DS      1       ;受信作業用

;
;       フラグ領域
;
BSEG
TxFlag          DBIT            ;送信要求フラグ



Ⅳ プログラムの使用方法
で説明した周辺プログラムを介して通信を行います。この際にサブルーチン群はPUBLIC宣言されているので、以下のようにEXTRN宣言することで、リンクします。


EXTRN           INIT_BUFF                       ;バッファ初期化
EXTRN           INIT_RXBUFF                     ;受信バッファ初期化
EXTRN           CHECK_TXBUFF                    ;送信バッファ確認
EXTRN           CHECK_RXBUFF                    ;受信バッファ確認
EXTRN           ADD_DATA                        ;送信データをバッファに
EXTRN           SET_DATA                        ;送信バッファを設定
EXTRN           GET_DATA                        ;受信データ読み出し
EXTRN           SET_RXBUFF                      ;受信バッファ設定


1. 1バイト送信の使用例
(1)プログラム例

ここでは、1バイトのデータを送信する例を示します。最初にサブルーチンINIT_BUFFを用いて、バッファ領域の初期化を行います。その後に、サブルーチンADD_DATAを用いてデータ(55H)をバッファにセットします。これで送信準備完了したので、割り込みを許可してINTP0割り込み待ちにします。送信完了はサブルーチンCHECK_TXBUFFを用いて確認します。送信と並行して受信しているので、そのデータを読み出しておきます。


;
;       送信例1
;
; 標準の16バイトのバッファを使用して1バイト単位で送信
;
CALL            !INIT_BUFF              ;バッファ初期化
MOV             X,#055H
CALL            !ADD_DATA               ;送信データをセット
EI
TXLOOP1:
CALL            !CHECK_TXBUFF           ;送信バッファを確認
BC              $TXLOOP1                ;バッファが空になるまで待つ
;
; 同時に受信もしているので、受信データを読み出す。
;
CALL            !GET_DATA               ;受信データ読み出し


(2)SM+での1バイト送信時のタイミング検証
実際にプログラムを動作させて、タイミングの確認を行ってみます。外部からの信号をシミュレートするために「信号データエディタ」を利用して、CS信号などの入力信号のタイミングを定義します。また、時間の単位としては、μ秒を選択します。リセット解除後からのデータを準備するのは大変なので、上記プログラムのEI命令にブレーク・ポイントを設定して、プログラムの実行をここで止めた状態で信号データエディタからの入力を開始するようにします。このときのデータの頭の部分を以下に示します。CS信号を入力してから(下記の図の)15μ秒後にSCK信号を立ち下げて通信を開始し、以降は3μ秒毎にSCK信号を8回反転させます()。これは周期が6μ秒、つまり、170kbps弱に該当します。その後にCS信号を立ち上げています()。

このときの信号波形を以下に示します。ここで、CS信号(一番上)が立ち下がると、SO信号(下から2つ目)はHi-Z状態(赤い表示)からハイ・レベル出力に推移しますが、BUSY信号(一番下)は抵抗でプルアップされているので、最初からハイ・レベルになっています。BUSY信号が立ち下がってから、SCK信号が立ち下がります()。それを確認して、BUSY信号が立ち上がると同時に送信データ55HのMSBが0であることから、SO信号が立ち下がります()。この波形で一番タイミング的に苦しいところが1ビット目になります()。この部分の遅れが17クロックで、約2μ秒となります。8ビットのデータの送信が完了するとCS信号が立ち上がり、それを受けて、SO信号がHi-Z状態になります()。

このときのメモリの状態を以下に示します。

ここで、FE80,1番地がTxPointerで、FE82,3番地がSetPointerの送信データ・ポインタです。その後に受信データ・ポインタ(RxPointerRxSetPointer)が配置されています。FE8B ~ FE9A番地が送信データ・バッファ(TxBuff)で、FE9B ~ FEAA番地が受信データ・バッファ(RxBuff)になります。送信データ・バッファには送信したデータ55が残っています。受信データ・バッファには送信時に受信した無効データFFが格納され、受信データ数(RxCount)が1になっています。
この無効データを読み出すことで受信データ・バッファをクリア(ここでは単にデータを読み出すためにサブルーチンGET_DATAをコール)します。この結果のメモリの状態は以下のようになります。受信データ・ポインタ(RxPointerRxSetPointer)が初期値に戻され、受信データ数(RxCount)が1になります。受信データ・バッファはリング・バッファ構造にしていないので、データがなくなったらポインタを初期化することでバッファ・オーバ・フローが発生する可能性を少なくしています。

2. 複数バイト送信の使用例
(1)プログラム例

この場合の例は、以下に示すROM(CSEG)領域にDB疑似命令で定義したデータを送信するものです。このデータが格納された領域そのものを、送信バッファとして設定することで、このプログラム例では、余分なデータのコピー作業を省略しています。


CSEG
TXDATA:
DB              0AAH,0CCH,33H,00,0FFH,01,02,03


実際のプログラムは送信したいデータ数(この例では8バイト)をXレジスタに設定してPUSH命令でスタックにセットします。その後に、送信したいデータの先頭アドレス(この例ではTXDATA)をAXレジスタにセットして、サブルーチンSET_DATAを呼びます。それで、送信に必要な設定が完了するので、EI命令により割り込みを許可し、INTP0割り込み待ちします。送信データの設定が完了したら、サブルーチンCHECK_TXBUFFを用いて送信完了を待ちます。


;
;       送信例2
;
;  データ・テーブルTXDATAから8バイトを送信
;
DI
MOV             X,#8                    ;送信データ数は8キャラクタ
PUSH            AX                      ;スタックにセット
MOVW            AX,#TXDATA              ;バッファの先頭アドレス設定
CALL            !SET_DATA
POP             AX                      ;スタックを元に戻す
EI
TXLOOP2:
CALL            !CHECK_TXBUFF           ;送信バッファを確認
BC              $TXLOOP2                ;バッファが空になるまで待つ

 

注意
サブルーチンSET_DATAは排他制御のために割り込みの禁止を制御しているので、このようにメイン処理での制御は必要ないのですが、ここでは分かりやすくするためにメイン処理でも割り込みの制御を行っています。
全体の動作としては、上位側で割り込み制御した方が、EIDIだけで済むので処理は単純になります。


(2)SM+での複数(8)バイト送信時のタイミング検証
複数バイト送信を確認するためのタイミング・データは以下のようになります。3μs毎にSCK信号をロウ/ハイと切り替え、これを8回繰り返すことで、1バイト(1キャラクタ)の転送タイミング(SCK信号)を生成しています。1キャラクタの最後のSCK信号と次のキャラクタのSCK信号の間に12μsの間隔を設定して、8キャラクタ分の信号を生成します。

このときの信号波形は以下のようになります。指定されたデータが順にSO信号として出力されているのが分かります。また、BUSY信号も1キャラクタ毎にハイ/ロウが変化しているのがわかります。

3. 1バイト受信の使用例
このプログラム例は、受信ステータスを確認して受信データが存在したならば、バッファからそれを読み出すプログラムです。受信データが03(ETXのコー ド)になるまで受信を継続するものです。なお、この受信処理はかなりの処理時間が必要になるため、1キャラクタのデータをバッファから読み出している間に 複数のデータが割り込み処理により受信バッファに格納されてしまいます。最初に、送信例2で送信時に受信してしまった無効データを受信データ・バッファか らクリアしています。これまでに受信されたデータは全て消されてしまいますので、これからデータが送られてくる場合にだけ受信データ・バッファのクリア処 理を行ってください。

(1)プログラム例


;
;       受信例1
;
;  標準のバッファに受信する。受信したデータがETX(03)なら
;終了する。
;
CALL            !INIT_RXBUFF            ;受信バッファをクリア
EI
RXLOOP1:
CALL            !CHECK_RXBUFF           ;受信ステータス確認
BC              $RXLOOP1                ;受信バッファ空ならループ
CALL            !GET_DATA               ;受信データ読み出し
MOV             A,#03                   ;ETX
CMP             A,C                     ;受信データを比較
BNZ             $RXLOOP1                ;ETXを受信するまで継続
DI


(2)SM+での受信時のタイミング検証
送信例2を実行した段階で、8バイトの送信時に同時に無効データを受信しています。この受信例が動作する段階でのメモリの状態を以下に示します。

このままでは、無効データの処理も行う必要があるので、最初に受信データ・バッファをクリアして、以下のような状態にしておきます。

その後、以下のようなパターンで信号を入力して、55H、AAH、CCH、33H、03Hのデータを入力します。

このときの信号波形は以下のようになります。


また、メモリは以下のようになり、5バイトのデータが受信されているのが分かります。

このように、受信処理そのものは割り込みで処理され、受信したデータはバッファに格納されていくので、受信バッファが溢れない限りは、受信処理を意識する必要はありません。

4. ベクタ・テーブル等の設定
このプログラムを使用するために、以下のようにベクタ・テーブルやスタック領域を確保しておきます。


RSTVECT CSEG    AT 0
DW              START
ORG     0AH
DW              __INTP1
STKADR  DSEG    IHRAM
DS              32                      ; スタック領域(32バイト)
STACK:



Ⅴ C言語プログラムでの使用方法
ここでは、C言語のプログラムからで説明した周辺プログラムを介して通信を行います。CC78K0Sでは最初にpragma指令により以下のように宣言を行います。


#pragma SFR
#pragma EI
#pragma DI
#pragma NOP


次にサブルーチン群(関数)はPUBLIC宣言されているので、以下のようにプロトタイプ宣言にextern宣言することで、リンクします。


/*--------------------------------------------------------------*/
/*                                                              */
/*                                                              */
/*      使用する関数の定義                                      */
/*                                                              */
/*                                                              */
/*--------------------------------------------------------------*/

/*      バッファ初期化処理関数          */

extern  void    _init_buff(void);

/*      受信バッファ初期化処理関数      */

extern void _init_rxbuff(void);

/*      送信ステータス確認関数          */

extern  unsigned char   _check_txbuff(void);

/*      受信ステータス確認関数          */

extern  unsigned char   _check_rxbuff(void);

/*      1バイト送信関数                 */

extern  void    _add_data(unsigned char data);

/*      ブロック送信関数                */

extern void _set_data(unsigned char *data,unsigned char size);

/*      1バイト受信関数                 */

extern unsigned char    _get_data(void);

/*      受信バッファ設定関数            */

extern void _set_rxbuff(unsigned char *data);


1. 割り込みベクタの設定
CC78K0Sの場合には、スタックはスタートアップ・ルーチンが自動的に設定するので、アセンブラ記述の割り込みを使用するには、ベクタを設定するだけです。ここでは、簡単のために、以下のようなアセンブラ記述のプログラムをリンクしておきます。


;
;       割り込みベクター・テーブル定義
;
EXTRN           __INTP1                         ;割り込み処理部

RSTVECT CSEG    AT 0AH
DW              __INTP1

END


2. 1バイト送信の使用例
使用するにはまず、_init_buff()関数を呼び出して、バッファの初期化や割り込み関係の設定を行います。


main(void)
{
unsigned char   RxData;

_init_buff(); /* バッファの初期化             */

RxData = 0;             /* 受信データ初期化             */

/****************************************************************

1バイトのデータ送信

****************************************************************/

_add_data(0x55); /* 1バイトのデータ(55H)送信     */
EI(); /* 割り込み許可                 */
/*
送信完了確認
*/

while(_check_txbuff() != 0){
NOP();
}
DI();



初期化が完了したら、送信データを引数として、_add_data()関数を呼び出します。これで、送信バッファにデータがセットされるので、後は割り込みを許可して転送可能にします。転送が完了したかは_check_txbuff()関数の戻り値(=残り送信データ数)が0になるまでwhile文で待ちます。while文を抜けたら、送信は完了です。送信が完了した段階で、同時に実行した受信処理により、無効データが受信されているので、必要に応じて受信データの空読み処理や受信バッファの初期化処理などを行う必要があります。

3. 複数バイト送信の使用例
送信データを設定する関数が異なるだけで、全体的な処理の流れは1バイトの送信と同じです。使用する関数は_set_data()になります。この関数では、第1引数で送信データの格納された配列のアドレス、第2引数でデータ数を示します。


/****************************************************************

ブロック(8バイト)データ送信

****************************************************************/

_set_data(TxData1,8); /* 送信データのセット           */
EI();

/*
送信完了確認
*/

while(_check_txbuff() != 0){
NOP();
}

DI();


このプログラムでは、以下のように定義した定数の配列TxData1の8バイトのデータを送信します。


const   unsigned char   TxData1[8] =
{0xAA,0xCC,0x33,0x00,0xFF,01,02,03};


ここでも、送信と同時に受信処理を行うので、無効データが8バイト分受信されます。無効データを空読みしてしまうか、受信データ・バッファをクリアする必要があります。

4. 1バイト受信の使用例
受信処理で、データの受信そのものは割り込みで処理されるので、前もって受信バッファに格納されています。そのため、プログラムとしては受信バッファの制御を行うだけになります。具体的な処理は、_check_rxbuff()関数を用いて受信バッファのデータ数をチェックし、データがあればそのデータを_get_data()関数で読み出します。


/****************************************************************

データ受信

****************************************************************/

EI();

while(RxData != 03){
while(_check_rxbuff() == 0){
NOP();
}
RxData = _get_data();
}


このプログラム例では、受信データが03(ETX)になるまで、while文で待つような処理にしてあります。

 

適用製品

78K0S/Kx1+
他にご質問がございましたら、リクエストを送信してください