ようやく CPLD で作りたいものが決まった。なにかというと SPI to パラレル変換器。単純なものは シフトレジスタ + αで作れるわけだが、多少機能を付ける。
機能は、制御バイト + データ のプロトコル のサポート。
データを送る前に 制御バイトを送る。こうすることで SPI だけで制御できるようになり XMEGA だと DMA が使えるようになる。AVR の SPI でも パラレル出力 並の性能にできる。
制御バイトを使うやり方は、i2c液晶の設計で参考にした ST7032i のものをベースに拡張する。
制御バイトの定義:
bit7 CONT 0: CS=L の間は 次からデータのみ。(制御バイト固定)
bit6 RS RS ピンに出力 ( LCD では 1: DATA 0: CMD )
:
bit2 RD 1: READ 0: WRITE
補足:
RD のビットが離れているのは、RD を受け取った時点で処理するため。機能拡張する場合 bit5 - bit3 で定義して 状態を確定させておかないといけない。
また、制御バイトが来たとき 1 回 READ するだけなので、CONT=0 は READ には使えない。
さてこういうものを作りたいわけだが、まず前提として
- Cool Runner II XC2C32A/64A (VQ44) を使う。
- 1.8V のレギュレータが必要だが、32A は 125 円/(64A は 245 円)と安価。
- 記述言語は VHDL
ということにする。
まずは、外部仕様の定義:
SCK : in STD_LOGIC;
MOSI : in STD_LOGIC;
MISO : out STD_LOGIC;
CS : in STD_LOGIC;
RS : out STD_LOGIC;
WR : out STD_LOGIC;
RD : out STD_LOGIC;
DB : inout STD_LOGIC_VECTOR (7 downto 0) bus
8080 インターフェイスと SPI だから 基本的な説明はしない。ここでは制御の仕方のポイントだけ。
- DB は、tri-state で、CS=H のとき 無条件に Z にする。出力は、WRITE するときのみ。
- CS は L で active 。H にする毎に初期状態にする。
- READ の場合、前のバイトで RD を↓ にしておいて、読み込みを指示する。最後に読み込んで RD ↑ 。
- 使おうとしている LCD は、WR のパルス幅 50ns 以上。RD のパルス幅 150 ns 以上。という条件がある。SPI のクロックを 16MHz にした場合これをクリアするには、WR 1 クロック、RD 3 クロックのパルスにする必要がある。
次に内部状態の定義:
signal spireg : std_logic_vector(7 downto 0) := "00000000";
signal rdreg : std_logic_vector(7 downto 0) := "00000000";
signal spicnt : std_logic_vector(2 downto 0) := "000";
signal sWR : std_logic := '1'; -- WRITE 指示、負論理
signal sOE : std_logic := '1'; -- 出力 指示、負論理
signal sRD : std_logic := '1'; -- READ 指示、負論理
signal sRS : std_logic := '1'; -- RS のラッチ
signal sCB : std_logic := '1'; -- 制御バイトかどうか
signal sCONT : std_logic; -- CONT のラッチ
READ 用 レジスタを分けている。うまくすれば不要だと思えるが、とりあえず。spireg は、SPI からの 入力専用。spicnt は、何 bit 目かを数えるカウンタ。
スタティックな制御
WR <= sWR;
RD <= sRD;
RS <= sRS;
DB <= spireg when (sOE = '0') else "ZZZZZZZZ";
MISO <= rdreg(7);
基本的に内部状態がそのまま外に出ている。DB は tri-state なので、上記のように記述。
次は、同期動作。process (CS, SCK)
まず CS が H になったときの動作から
sWR <= '1';
sOE <= '1';
sRD <= '1';
sRS <= '1';
sCB <= '1';
spicnt <= "000";
spireg <= "00000000";
rdreg <= "00000000";
-- 初期状態にしているだけ。sOE が 1 になるので、DB も Z になる。
次に SCK が H になったとき(↑)
-- シフト動作
spireg(7 downto 1) <= spireg(6 downto 0);
spireg(0) <= MOSI;
-- RS の 受け取り (= 出力変更)
if ((spicnt = 1) and (sCB = '1')) then
sRS <= MOSI;
end if;
-- RD の制御
if ((spicnt = 5) and (sCB = '1')) then
sRD <= not MOSI;
end if;
if (spicnt = 0) then
sRD <= '1';
end if;
-- CB と CONT の制御
if (spicnt = 7) then
if (sCB = '1') then
sCB <= '0';
sCONT <= spireg(6);
else
sCB <= sCONT;
end if;
end if;
-- sOE の 制御
if ((spicnt = 7) and (sRD = '1') and (sCB = '0')) then
sOE <= '0';
else
sOE <= '1';
end if;
補足:
RD を変化させる前に 変化させておく必要があるので、データが来たらすぐに変化させる。
データの読み込みをしておいて、次の バイトで送り出したいので、データが来たらすぐに変化(↓)させる。↑ するタイミングは、次の バイトに入ったとき。
RD のパルス幅は 3 クロック。例えば ↓を 1 クロック遅らせるには、(spicnt = 6) のとき、 入力を spireg(0) にして処理する。
最後のデータが来たとき、次の制御を決める。sCB = 1 なら、次は sCB = 0 でデータ処理。sCB = 0 で データ処理が終わったなら、次は、sCONT で決める。sCONT = 0 なら次もデータ処理。
最後のデータが来たら、RD でも CB でもなければ、DBに出力するために、sOE = 0 にする。
最後、SCK が L になったとき(↓)
-- カウンタ ++
spicnt <= spicnt + 1;
-- rdreg の ロード/シフト動作
if (spicnt = 7) and (sCB = '0') and (sRD = '0') then
rdreg <= DB;
else
rdreg(7 downto 1) <= rdreg(6 downto 0);
rdreg(0) <= '0';
end if;
-- sWR の 制御
if ((spicnt = 6) and (sRD = '1') and (sCB = '0')) then
sWR <= '0';
else
sWR <= '1';
end if;
補足:
最後のデータの1つ前で、RD でも CB でもなければ、WR のパルスを出すために、sWR = 0 にする。
WR の↑で データが採取される。L にする 期間は 1 クロック。↑のあと 半クロックデータを保持する必要がある。(15ns 以上)
以上は、とりあえず書いてみただけのもの。合成できたものを ベースにしているが、バグっているかも。思った通りに動作するのかどうかの検証はこれから。
タイミングの検証
CS ~|________________________________________...._|~
_ _ _ _ _ _ _ _ _
SCK ___| |_| |_| |_| |_| |_| |_| |_| |_| |_
: :
MOSI |MSB| D6| D5| D4| D3| D2| D1| D0|
spireg S S S S S S S S S (S: SHIFT)
: : :
spicnt 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 0
rdreg S S S S S S S L (L: LOAD)
MISO MSB| D6| D5| D4| D3| D2| D1| D0| MSB
: : : :
(WRITE) : : : :
________________________________ _____
sOE : : |___|
_____________________________ _____
WR : : |___|
: :
DB[7..0] ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ| |ZZZZZ
: : spireg
(READ) : : :
________________________ _____
RD : |___________|
DB[7..0] ZZZZZZZZZZZZZZZZZZZZZZZ| READ DATA |ZZZZZ
:
_______ ________________________
RS _______X________________________
バグを直したらこのページも直しておく予定。
ソースコードは、spi2par.vhd だが、これも直したら ファイル名そのままで 更新する予定。
あと、ピンアサイン spi2par.ucf 。
ピン19 〜 38 の bank 2 メインで割り当ててみた。順番は LCD の ピン 1 からの順番。ただし、DB[765] は離れているので間に SPI の信号を割り込ませている。あと、CS は、GCR で SCK は GCK から選んでいる。
あと、変更した設定は、I/O Voltage Standard を LVCMOS33 に。(→ 参考)
結果:
************************* Mapped Resource Summary **************************
Macrocells Product Terms Function Block Registers Pins
Used/Tot Used/Tot Inps Used/Tot Used/Tot Used/Tot
26 /32 ( 81%) 38 /112 ( 34%) 37 /80 ( 46%) 25 /32 ( 78%) 15 /33 ( 45%)