先日からピコピコとPicoBlazeを触っていたわけですが、某所で
日本開閉器工業社製の
有機ELディスプレイ搭載スイッチIS-C15ANP4をPicoBlazeから制御したいと依頼を受けました。
メーカのウェブからダウンロードできる資料には、初期化時に使用するパラメータが列挙されています。このスイッチのディスプレイ・コントローラには、SOLOMON SYSTECH社のSSD1331が搭載されているようです。初期化コードをスイッチに送ってやるだけでディスプレイ・コントローラの初期化は完了です。後は、データを送ればそのまま絵として表示されます。
さて、今回のPicoBlazeのデザインは、先のLEDをピコピコさせたものをそのまま流用しました。
という事はBit Bangで制御するのね、ヒドイ!
ということで、トップのデザインです。
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity main is
Port (
oled_ss : out std_logic;
oled_res : out std_logic;
oled_dc : out std_logic;
oled_sck : out std_logic;
oled_sdi : out std_logic;
clk : in std_logic;
sw1 : in std_logic);
end main;
architecture RTL of main is
component kcpsm6
generic( hwbuild : std_logic_vector(7 downto 0) := X"00";
interrupt_vector : std_logic_vector(11 downto 0) := X"3FF";
scratch_pad_memory_size : integer := 64);
port ( address : out std_logic_vector(11 downto 0);
instruction : in std_logic_vector(17 downto 0);
bram_enable : out std_logic;
in_port : in std_logic_vector(7 downto 0);
out_port : out std_logic_vector(7 downto 0);
port_id : out std_logic_vector(7 downto 0);
write_strobe : out std_logic;
k_write_strobe : out std_logic;
read_strobe : out std_logic;
interrupt : in std_logic;
interrupt_ack : out std_logic;
sleep : in std_logic;
reset : in std_logic;
clk : in std_logic);
end component;
component c15anp4
generic( C_FAMILY : string := "S6";
C_RAM_SIZE_KWORDS : integer := 1;
C_JTAG_LOADER_ENABLE : integer := 0);
Port ( address : in std_logic_vector(11 downto 0);
instruction : out std_logic_vector(17 downto 0);
enable : in std_logic;
rdl : out std_logic;
clk : in std_logic);
end component;
signal address : std_logic_vector(11 downto 0);
signal instruction : std_logic_vector(17 downto 0);
signal port_id : std_logic_vector(7 downto 0);
signal in_port : std_logic_vector(7 downto 0);
signal out_port : std_logic_vector(7 downto 0);
signal read_strobe : std_logic;
signal write_strobe : std_logic;
signal port000 : std_logic_vector(7 downto 0);
begin
processor: kcpsm6
generic map ( hwbuild => X"00",
interrupt_vector => X"3FF",
scratch_pad_memory_size => 64)
port map(
address => address,
instruction => instruction,
port_id => port_id,
interrupt => '0',
write_strobe => write_strobe,
out_port => out_port,
read_strobe => read_strobe,
in_port => in_port,
sleep => '0',
reset => '0',
clk => clk);
program: c15anp4
generic map( C_FAMILY => "S6",
C_RAM_SIZE_KWORDS => 1,
C_JTAG_LOADER_ENABLE => 0)
port map( address => address,
instruction => instruction,
enable => '1',
clk => clk);
out_ratch : process(clk) is
begin
if clk'event and clk='1' then
if write_strobe='1' then
if port_id ="00000000" then
port000 <= out_port;
end if;
end if;
end if;
end process out_ratch;
in_ratch : process(clk) is
begin
if clk'event and clk='1' then
if read_strobe='1' then
if port_id ="00000000" then
in_port(0) <= not sw1;
end if;
end if;
end if;
end process in_ratch;
oled_ss <= port000(0);
oled_res <= port000(1);
oled_dc <= port000(2);
oled_sck <= port000(3);
oled_sdi <= port000(4);
end RTL;
回路はLX-9 MicroBlazeのPMODのピンに超適当に配線です。
NET "oled_ss" LOC = D18;
NET "oled_res" LOC = D17;
NET "oled_dc" LOC = G14;
NET "oled_sck" LOC = F14;
NET "oled_sdi" LOC = F15;
さて、後はI/Oをバンバン打つだけです。
; =========================================================
; Port000[0] : SS- : Slave Select (Low Active)
; Port000[1] : RES : Reset (Low Active)
; Port000[2] : D/C : Data/Command (Data=High, Command=Low)
; Port000[3] : SCK : Serial Clock (Up Edge Trigger)
; Port000[4] : SDI : Serial Data
; =========================================================
; ---------------------------------------------------------
; Register Name
; ---------------------------------------------------------
; SPI Data Register
NAMEREG s0, SPIDATA
; Port Data Register
NAMEREG s1, PORTDAT
; Delay Counter No.1
NAMEREG s2, DLYCNT1
; Delay Counter No.2
NAMEREG s3, DLYCNT2
; Delay Counter No.3
NAMEREG s4, DLYCNT3
; Send Counter No.1
NAMEREG s5, SNDCNT1
; Send Counter No.2
NAMEREG s6, SNDCNT2
; Temporary Register No.1
NAMEREG s7, TEMP_I
; Temporary Register No.2
NAMEREG s8, TEMP_J
; ---------------------------------------------------------
; Constant Variables
; ---------------------------------------------------------
CONSTANT PORT000, 00
CONSTANT OLED_SS, 01
CONSTANT OLED_RES, 02
CONSTANT OLED_DC, 04
CONSTANT OLED_SCK, 08
CONSTANT OLED_SDI, 10
MAIN:
CALL INIT
TESTLOOP:
CALL INIT_ADDR
CALL FILLR
CALL LONGDELAY
CALL INIT_ADDR
CALL FILLG
CALL LONGDELAY
CALL INIT_ADDR
CALL FILLB
CALL LONGDELAY
CALL INIT_ADDR
CALL FILLW
CALL LONGDELAY
JUMP TESTLOOP
INIT_ADDR:
; ---------------------------------------------------------
; Setup Column Address
; ---------------------------------------------------------
LOAD SPIDATA, 15
CALL SEND_CMD
LOAD SPIDATA, 10
CALL SEND_CMD
LOAD SPIDATA, 4F
CALL SEND_CMD
; ---------------------------------------------------------
; Setup Row Address
; ---------------------------------------------------------
LOAD SPIDATA, 75
CALL SEND_CMD
LOAD SPIDATA, 00
CALL SEND_CMD
LOAD SPIDATA, 2F
CALL SEND_CMD
RETURN
FILLR:
LOAD TEMP_I, 30
FILLR1:
LOAD TEMP_J, 40
FILLR2:
; ---------------------------------------------------------
; Byte0 = B[4:0] + G[5:3]
; Byte1 = G[2:0] + R[4:0]
; ---------------------------------------------------------
LOAD SPIDATA, 00
CALL SEND_DAT
LOAD SPIDATA, 1F
CALL SEND_DAT
SUB TEMP_J, 01
JUMP NZ, FILLR2
SUB TEMP_I, 01
JUMP NZ, FILLR1
RETURN
FILLG:
LOAD TEMP_I, 30
FILLG1:
LOAD TEMP_J, 40
FILLG2:
; ---------------------------------------------------------
; Byte0 = B[4:0] + G[5:3]
; Byte1 = G[2:0] + R[4:0]
; ---------------------------------------------------------
LOAD SPIDATA, 07
CALL SEND_DAT
LOAD SPIDATA, C0
CALL SEND_DAT
SUB TEMP_J, 01
JUMP NZ, FILLG2
SUB TEMP_I, 01
JUMP NZ, FILLG1
RETURN
FILLB:
LOAD TEMP_I, 30
FILLB1:
LOAD TEMP_J, 40
FILLB2:
; ---------------------------------------------------------
; Byte0 = B[4:0] + G[5:3]
; Byte1 = G[2:0] + R[4:0]
; ---------------------------------------------------------
LOAD SPIDATA, F8
CALL SEND_DAT
LOAD SPIDATA, 00
CALL SEND_DAT
SUB TEMP_J, 01
JUMP NZ, FILLB2
SUB TEMP_I, 01
JUMP NZ, FILLB1
RETURN
FILLW:
LOAD TEMP_I, 30
FILLW1:
LOAD TEMP_J, 40
FILLW2:
; ---------------------------------------------------------
; Byte0 = B[4:0] + G[5:3]
; Byte1 = G[2:0] + R[4:0]
; ---------------------------------------------------------
LOAD SPIDATA, FF
CALL SEND_DAT
LOAD SPIDATA, FF
CALL SEND_DAT
SUB TEMP_J, 01
JUMP NZ, FILLW2
SUB TEMP_I, 01
JUMP NZ, FILLW1
RETURN
INIT:
; =========================================================
; SS- : Slave Select (Low Active)
; RES : Reset (Low Active)
; D/C : Data/Command (Data=High, Command=Low)
; SCK : Serial Clock (Up Edge Trigger)
; SDI : Serial Data
; =========================================================
; ---------------------------------------------------------
; Reset Active
; ---------------------------------------------------------
LOAD PORTDAT, 00
OUTPUT PORTDAT, PORT000
CALL DELAY
; ---------------------------------------------------------
; Reset Inactive
; ---------------------------------------------------------
LOAD PORTDAT, OLED_RES
OUTPUT PORTDAT, PORT000
CALL DELAY
; ---------------------------------------------------------
; Setup Column Address
; ---------------------------------------------------------
LOAD SPIDATA, 15
CALL SEND_CMD
LOAD SPIDATA, 10
CALL SEND_CMD
LOAD SPIDATA, 4F
CALL SEND_CMD
; ---------------------------------------------------------
; Setup Row Address
; ---------------------------------------------------------
LOAD SPIDATA, 75
CALL SEND_CMD
LOAD SPIDATA, 00
CALL SEND_CMD
LOAD SPIDATA, 2F
CALL SEND_CMD
; ---------------------------------------------------------
; Setup Contrast Color A
; ---------------------------------------------------------
LOAD SPIDATA, 81
CALL SEND_CMD
LOAD SPIDATA, 19
CALL SEND_CMD
; ---------------------------------------------------------
; Setup Contrast Color B
; ---------------------------------------------------------
LOAD SPIDATA, 82
CALL SEND_CMD
LOAD SPIDATA, 14
CALL SEND_CMD
; ---------------------------------------------------------
; Setup Contrast Color C
; ---------------------------------------------------------
LOAD SPIDATA, 83
CALL SEND_CMD
LOAD SPIDATA, 24
CALL SEND_CMD
; ---------------------------------------------------------
; Master Current Control
; ---------------------------------------------------------
LOAD SPIDATA, 87
CALL SEND_CMD
LOAD SPIDATA, 0F
CALL SEND_CMD
; ---------------------------------------------------------
; Set Display Start Line
; ---------------------------------------------------------
LOAD SPIDATA, A1
CALL SEND_CMD
LOAD SPIDATA, 00
CALL SEND_CMD
; ---------------------------------------------------------
; Set Display Offset
; ---------------------------------------------------------
LOAD SPIDATA, A2
CALL SEND_CMD
LOAD SPIDATA, 10
CALL SEND_CMD
; ---------------------------------------------------------
; Set Display Mode
; ---------------------------------------------------------
LOAD SPIDATA, A4
CALL SEND_CMD
; ---------------------------------------------------------
; Set Multiplex Ratio
; ---------------------------------------------------------
LOAD SPIDATA, A8
CALL SEND_CMD
LOAD SPIDATA, 2F
CALL SEND_CMD
; ---------------------------------------------------------
; Dim Mode Setting
; ---------------------------------------------------------
LOAD SPIDATA, AB
CALL SEND_CMD
LOAD SPIDATA, 12
CALL SEND_CMD
LOAD SPIDATA, 0C
CALL SEND_CMD
LOAD SPIDATA, 14
CALL SEND_CMD
LOAD SPIDATA, 12
CALL SEND_CMD
; ---------------------------------------------------------
; Set Master Configuration
; ---------------------------------------------------------
LOAD SPIDATA, AD
CALL SEND_CMD
LOAD SPIDATA, 8E
CALL SEND_CMD
; ---------------------------------------------------------
; Phase Period Adjustment
; ---------------------------------------------------------
LOAD SPIDATA, B1
CALL SEND_CMD
LOAD SPIDATA, 44
CALL SEND_CMD
; ---------------------------------------------------------
; Display Clock Divider
; ---------------------------------------------------------
LOAD SPIDATA, B3
CALL SEND_CMD
LOAD SPIDATA, A0
CALL SEND_CMD
; ---------------------------------------------------------
; Enable Linear Gray Scale
; ---------------------------------------------------------
LOAD SPIDATA, B9
CALL SEND_CMD
; ---------------------------------------------------------
; Set Precharge Level
; ---------------------------------------------------------
LOAD SPIDATA, BB
CALL SEND_CMD
LOAD SPIDATA, 12
CALL SEND_CMD
; ---------------------------------------------------------
; Set VCOMH
; ---------------------------------------------------------
LOAD SPIDATA, BE
CALL SEND_CMD
LOAD SPIDATA, 28
CALL SEND_CMD
; ---------------------------------------------------------
; Color Depth = 64K
; ---------------------------------------------------------
LOAD SPIDATA, A0
CALL SEND_CMD
LOAD SPIDATA, 70
CALL SEND_CMD
; ---------------------------------------------------------
; Set Display Normal
; ---------------------------------------------------------
LOAD SPIDATA, AF
CALL SEND_CMD
RETURN
SEND_DAT:
; =========================================================
; SS- : Slave Select (Low Active)
; RES : Reset (Low Active)
; D/C : Data/Command (Data=High, Command=Low)
; SCK : Serial Clock (Up Edge Trigger)
; SDI : Serial Data
; =========================================================
SEND_DAT_SLAVE_ACTIVE:
LOAD PORTDAT, 00
OR PORTDAT, OLED_RES
OR PORTDAT, OLED_DC
SEND_DAT_SETUP:
LOAD SNDCNT1, 08
LOAD SNDCNT2, SPIDATA
SEND_DAT_LOOP:
SL0 SNDCNT2
JUMP C, SEND_DAT_H
JUMP SEND_DAT_L
SEND_DAT_H:
; ---------------------------------------------------------
; SDI=HIGH, SCK=LOW
; ---------------------------------------------------------
LOAD PORTDAT, 00
OR PORTDAT, OLED_RES
OR PORTDAT, OLED_DC
OR PORTDAT, OLED_SDI
OUTPUT PORTDAT, PORT000
CALL DELAY
; ---------------------------------------------------------
; SDI=HIGH, SCK=HIGH
; ---------------------------------------------------------
LOAD PORTDAT, 00
OR PORTDAT, OLED_RES
OR PORTDAT, OLED_DC
OR PORTDAT, OLED_SCK
OR PORTDAT, OLED_SDI
OUTPUT PORTDAT, PORT000
CALL DELAY
; ---------------------------------------------------------
; NEXT
; ---------------------------------------------------------
JUMP SEND_DAT_NEXT
SEND_DAT_L:
; ---------------------------------------------------------
; SDI=LOW, SCK=LOW
; ---------------------------------------------------------
LOAD PORTDAT, 00
OR PORTDAT, OLED_RES
OR PORTDAT, OLED_DC
OUTPUT PORTDAT, PORT000
CALL DELAY
; ---------------------------------------------------------
; SDI=LOW, SCK=HIGH
; ---------------------------------------------------------
LOAD PORTDAT, 00
OR PORTDAT, OLED_RES
OR PORTDAT, OLED_DC
OR PORTDAT, OLED_SCK
OUTPUT PORTDAT, PORT000
CALL DELAY
; ---------------------------------------------------------
; NEXT
; ---------------------------------------------------------
JUMP SEND_DAT_NEXT
SEND_DAT_NEXT:
SUB SNDCNT1, 01
JUMP NZ, SEND_DAT_LOOP
SEND_DAT_END:
LOAD PORTDAT, 00
OR PORTDAT, OLED_RES
OR PORTDAT, OLED_DC
OR PORTDAT, OLED_SS
OUTPUT PORTDAT, PORT000
RETURN
SEND_CMD:
; =========================================================
; SS- : Slave Select (Low Active)
; RES : Reset (Low Active)
; D/C : Data/Command (Data=High, Command=Low)
; SCK : Serial Clock (Up Edge Trigger)
; SDI : Serial Data
; =========================================================
SEND_CMD_SLAVE_ACTIVE:
LOAD PORTDAT, 00
OR PORTDAT, OLED_RES
SEND_CMD_SETUP:
LOAD SNDCNT1, 08
LOAD SNDCNT2, SPIDATA
SEND_CMD_LOOP:
SL0 SNDCNT2
JUMP C, SEND_CMD_H
JUMP SEND_CMD_L
SEND_CMD_H:
; ---------------------------------------------------------
; SDI=HIGH, SCK=LOW
; ---------------------------------------------------------
LOAD PORTDAT, 00
OR PORTDAT, OLED_RES
OR PORTDAT, OLED_SDI
OUTPUT PORTDAT, PORT000
CALL DELAY
; ---------------------------------------------------------
; SDI=HIGH, SCK=HIGH
; ---------------------------------------------------------
LOAD PORTDAT, 00
OR PORTDAT, OLED_RES
OR PORTDAT, OLED_SCK
OR PORTDAT, OLED_SDI
OUTPUT PORTDAT, PORT000
CALL DELAY
; ---------------------------------------------------------
; NEXT
; ---------------------------------------------------------
JUMP SEND_CMD_NEXT
SEND_CMD_L:
; ---------------------------------------------------------
; SDI=LOW, SCK=LOW
; ---------------------------------------------------------
LOAD PORTDAT, 00
OR PORTDAT, OLED_RES
OUTPUT PORTDAT, PORT000
CALL DELAY
; ---------------------------------------------------------
; SDI=LOW, SCK=HIGH
; ---------------------------------------------------------
LOAD PORTDAT, 00
OR PORTDAT, OLED_RES
OR PORTDAT, OLED_SCK
OUTPUT PORTDAT, PORT000
CALL DELAY
; ---------------------------------------------------------
; NEXT
; ---------------------------------------------------------
JUMP SEND_CMD_NEXT
SEND_CMD_NEXT:
SUB SNDCNT1, 01
JUMP NZ, SEND_CMD_LOOP
SEND_CMD_END:
LOAD PORTDAT, 00
OR PORTDAT, OLED_RES
OR PORTDAT, OLED_SS
OUTPUT PORTDAT, PORT000
RETURN
DELAY:
LOAD DLYCNT1, 01
DELAY1:
LOAD DLYCNT2, 01
DELAY2:
SUB DLYCNT2, 01
JUMP NZ, DELAY2
SUB DLYCNT1, 01
JUMP NZ, DELAY1
RETURN
LONGDELAY:
LOAD DLYCNT1, 80
LONGDELAY1:
LOAD DLYCNT2, FF
LONGDELAY2:
LOAD DLYCNT3, FF
LONGDELAY3:
SUB DLYCNT3, 01
JUMP NZ, LONGDELAY3
SUB DLYCNT2, 01
JUMP NZ, LONGDELAY2
SUB DLYCNT1, 01
JUMP NZ, LONGDELAY1
RETURN
もう完全にPicoBlazeの使い方としてはおかしいですが、第一段階としては気にしません。
上記のコードで「赤」、「緑」、「青」、「白」と順に表示を繰り返す事ができます。
リソースの使用率ですが、以下のようになりました。
Bit Bangはあんまりなので、SPIモジュールを作ってやってデータを渡すのが良いでしょう。
データを渡して、転送開始入力信号と転送完了出力信号が出てくるようなモジュールです。
フォントなどを内蔵すればもっと手軽に表示が楽しめそう。
上記を雛型にすればこういった拡張も簡単です。
ちなみに、今回はBit Bangの基本動作を外部のロジック・アナライザで確認した後、セットアップとホールド、クロックサイクルを見ながらアセンブラ・コードをチューニングし、波形が期待通りに出力されている事を確認した後で、最後にIS-C15ANP4を接続するという形で作業しました。(ChipScope使えよという感じです。)
PicoBlazeのアセンブラも、最初のバージョンより気が利くようになっていて、一度立ち上げてプログラム・ファイル名を入力しておけば「R」をタイプするだけで再コンパイルできるようになっています。
なので、アセンブルの後で生成されたVHDLををプロジェクトにコピーするバッチファイルを作っておけば、「アセンブル→再インプリメンテーション」の流れをスムーズにこなす事ができます。
こんな感じで(超適当だけど)半田付けを含めて小1時間ほどでPicoBlazeからIS-C15ANP4に絵が出せるようになりました。
念のために書いておくと、IS-C15ANP4には決められた電源シーケンスがあります。
製品に使用する場合、きちんとシーケンスを守るように設計しましょう。
最後にグリーンな表示。
ちなみに、先ほどの初期化コードの場合、2バイトで1ピクセルを表現するようになっています。
BとRが5ビット、Gが6ビットで、1バイト目がB[4:0] + G[5:3]、2バイト目がG[2:0] + R[4:0]です。