これまでに SPI通信について勉強しながら、Arduino をコントローラとして、ペリフェラルにシフトレジスタ 74HC595、74HC597 を使ってデータの送受信を試してみました。
今回は、Arduino に置き換えられるようなコントローラを、ロジックIC でつくってみようと思います。ただ、いろいろな機能を持たせようとすると訳わかんなくなりそうですから、コントローラからデータを送信することだけを考えることにしました。
タイミングダイヤグラム
まず、SPI コントローラの動作タイミングチャートをつくりましょう。
なお、カウンタ回路は 74HC161、シフトレジスタは 74HC597 を使用することにし、その仕様にあわせて各信号を考えていきます。

チップセレクト信号 CS は外部からの非同期入力信号で、LOW になるとデータ伝送処理を開始するものとします。
CS の立ち下がりエッジを検出し、クロック CLK に同期したトリガパルス TR を生成します。
TR が LOW の間に、反転したクロック CLK によりカウンタの初期値ロード信号 LD を HIGH にし、カウント動作を開始します。カウントは CLK で行ないます。
カウンタの初期値は 0x7 とし、0x8~0xF まで 8 カウントさせます。
カウンタが 0xF になるとキャリーオーバー CO が出力されるので、これにより LD をリセット、カウント動作を停止します。
LD が HIGH の間だけゲートを開いて CLK を通過させることで、8個のパルストレインが出力できます。これがシリアルクロック SCK となります。
外部から入力される 8ビットパラレルデータ D[0..7] を、シフトレジスタでシリアルデータに変換します。シフトレジスタには、トリガパルス TR をパラレルロード PL として入力します。次に、カウンタのロード信号 LD をストレージクロック STCP として入力します。
PL が LOW の間に STCP が入力されると、入力データはストレージレジスタに格納されると同時に、シフトレジスタへも送られます。そのため、入力されたデータはすぐにシリアルデータとして出力されます。
シリアルクロックを反転させた SCK をシフトクロック SHCP としてデータをシフトさせ、MSB から順にシリアル出力 COPI へ出力します。
ペリフェラル側では、SCK の立ち上がりエッジがデータの中央に位置するので、データを正確に読み取ることができます。
なお、ペリフェラルからデータを受けとる CIPO はありません。クロック周波数は 8MHz 固定、SPIモードは 0 固定とします。
SPIコントローラ回路
各部の回路図とブロックダイヤグラムです。
ブロックダイヤグラム

コントローラの部分が、今回試作する SPI コントローラです。コントローラは、CS 信号と 8ビットパラレルデータを受けとり、ペリフェラルへチップセレクト CS、シリアルクロック SCK、シリアルデータ COPI を出力します。
コントローラをテストするための回路として、パラレルデータ発生回路、CS 信号発生回路、ペリフェラル回路もつくります。
初期化回路
まずは毎度々々の初期化回路です。
R1、C1 によるタイマ回路で、電源オン時 100ms の LOW 信号を出力した後、HIGHになります。これにより、フリップフロップとカウンタを初期化します。
R2 は保護抵抗です。
クロック発振回路

16MHz の水晶発振子による発振回路で、2分周して 8MHz のクロックパルス CLK を出力します。反転したクロックパルス CLK も出力しています。
発振回路には、アンバッファタイプのインバータ 74HCU04 を使用します。
分周回路は、Dフリップフロップによる 2分周回路です。JKフリップフロップ CD4027 はクロック周波数が最大 3.5MHz なので使えません。Dフリップフロップ 74HC74 は 31MHz です。
エッジ検出回路

外部からの CS 信号の立ち下がりを検出し、クロックに同期した負のパルス TR を出力します。
CS が LOW になると、U5A の反転出力が HIGH になります。次のクロックまで U5B の出力は HIGH のままですから、TR は 1クロック周期の間だけ LOW になります。
CS 信号発生回路

CS 信号を発生させるテスト用の回路です。
押しボタンスイッチを押すと CS が LOW になります。離すと HIGH になります。
R6、C4 とシュミットトリガインバータ 74HC14 で構成されるタイマ回路は、押しボタンスイッチのチャタリング防止回路です。
R5 はプルアップ抵抗で、スイッチに 5mA 流します。R7 は保護抵抗です。
シリアルクロック発生回路

基本は 8ビットのパルストレイン発生回路です。動作の詳細については関連記事も参照ください。
前にこの回路を試作したときは JKフリップフロップを使っていましたが、今回はクロック周波数が高いので Dフリップフロップを使用しました。そのため、前段にもう一つ Dフリップフロップ U6B を置き、RSラッチとして使っています。
RSラッチはトリガパルス TR でセットされ、カウンタのキャリーオーバー CO でリセットされます。
形としては、リセットの CO は CLR に入れるのが良い感じですが、その場合は NOT で反転してやる必要があります。そのために、CO をクロック CK に入力してリセットするようにしています。
次段のフリップフロップ U6A は、RSラッチの出力を受けて反転したクロック CLK によりカウンタの初期値ロード信号 LD を出力します。CLK の立ち下がりで動作させることで、カウンタより先行させています。
カウンタは 4ビット同期カウンタ 74HC161 です。
初期値は 0x7 にセットされています。LD が HIGH になると動作を開始し、0x8~0xFまで 8カウントします。0xF でキャリーオーバー CO が出力されて RSラッチがリセット、LD が LOW になり、カウンタは停止します。
LD が HIGH の間、AND ゲート U4A が開きますので、CLK がシリアルクロック SCK として出力されます。そのパルストレインの数は、カウント数の 8個です。
パラレル・シリアル変換

PISO シフトレジスタ 74HC597 の動作については、関連記事も参照ください。
入力された 8ビットパラレルデータ D[0..7] は、ストレージクロック STCP によりストレージレジスタへ取り込まれます。
パラレルロード PL が LOW になると、ストレージレジスタのデータがシフトレジスタへ送られます。ただし、PL が LOW の間に STCP を立ち上げると、入力されたパラレルデータはすぐにシフトレジスタへ送られます。今回はこの動きを利用しています。
トリガパルス TR をパラレルロード PL として入力します。また、カウンタのロード信号 LD をSTCP として入力します。
PL が LOW になり、次に STCP が HIGH になるので、入力データはすぐにシフトレジスタへ取り込まれます。シリアルクロックを反転した SCK によりデータをシフトして、MSB から順に Q7 へ出力、これがシリアルデータ COPI となります。SCK でシフトすることにより、SCK の立ち上がり部分がデータの中央になるため、ペリフェラル側で正確にデータを読み込むことができます。
なお、シリアル入力 DS を 5V につないでいますので、待機中の COPI は HIGH になります。
パラレルデータ発生回路

テスト用の回路です。ディップスイッチにより 8ビットパラレルデータ D[0..7] を出力します。
プルダウン抵抗を 10KΩにしましたので、スイッチに 0.5mA流れます。このあたりはまぁ、気分です。
ペリフェラル回路

テスト用のペリフェラル側の回路です。SIPO シフトレジスタ 74HC595 を使用します。動作の詳細については、関連記事も参照ください。
コントローラから出力されたシリアルデータ COPI を受信し、シリアルクロック SCK に同期してデータをシフトしていきます。
8ビットのデータ受信後、CS が HIGH に戻ると、それをレジスタクロック RCLK として、シフトレジスタからストレージレジスタへデータが送られます。パラレル出力は 8ビットのパラレルデータとして LED に点灯表示されます。
ブレッドボード
試作したブレッドボードのようすです。
一番上は 5V電源回路です。9Vの ACアダプタの出力を、三端子レギュレータ 7805 で 5Vにしています。これはお好みで用意してください。
CS 信号発生回路にあるタクトスイッチが CS 信号を出力する押しボタンです。
クロック発振回路に 16MHz 水晶発振子が見えます。その上が 74HCU04、左が分周回路の 74HC74 です。
エッジ検出回路は 74HC74 と 74HC00、SCK 発生回路は 74HC74 と、変換基板に載っているのが 74HC161 です。
ディップスイッチはパラレルデータを出力。その右がシフトレジスタ 74HC597 のパラレル・シリアル変換回路です。
一番下のブレッドボードがペリフェラル回路で、コントローラと 3芯のジャンパでつながっています。
ディップスイッチが 10010011 で、LED 表示も 10010011 となっています。ディップスイッチを切り替えてタクトスイッチを押すと、設定値に応じた出力表示となることを確認しました。
後記
今回は、SPI の送信コントローラをつくってみました。
SPI で送信するしくみを理解するには良かったのですが、これどう使います?
どっちかというと、受信コントローラにして、ペリフェラルから送られてくるデータを受けとるほうが使いみちありそうな気がします。まぁその場合も、シフトレジスタが SIPO に換わるだけで基本的な考え方は同じでしょう。たぶん、ね。