arty a7: Add an interface for a TFT LCD touchscreen

This adds an interface for an Arduino-compatible LCD touchscreen.  The
screen module plugs directly on to the Arduino/chipKit shield
connector on the Arty A7.  Unfortunately, the slightly strange way the
resistive touchscreen is brought out (connected to the D0, D1, RS and
CS pins) combined with the 200 ohm protection resisters on the Arty
board mean that some hardware hacks to the module are necessary.  I
rewired mine so that D0 and D1 are on the A4 and A5 pins and the reset
is where D0 was (shield I/O 8).

This interface is suitable for boards with a HX8347 driver chip.  The
timing may not be quite suitable for other driver chips.

The interface is a byte which can be read and written at 0xc8050000,
containing an index register, and a 1-8 byte data register at
0xc8050008.  Reading at offsets 1 to 7 from those addresses yields the
same value as at offset 0.  Writing 64 bits to the data register
writes the bytes at offset 1, 0, 3, 2, 5, 4, 7, 6 in that order to the
driver chip.  This allows pixel data to be transferred using 64-bit
writes, ending up in the frame buffer in the expected order (for
16-bit pixels, the driver chip expects MS byte then LS byte).  32-bit
writes do 1, 0, 3, 2, and 16-bit writes do 1, 0.

The touchscreen support so far is a 1-byte register containing bits to
set RS, D0, D1 and CS high or low or make them tri-state.  There is
nothing to do analog conversions of the signal levels at this stage.

Signed-off-by: Paul Mackerras <paulus@ozlabs.org>
pull/461/head
Paul Mackerras 4 years ago
parent 7f4e0185b5
commit 172eae61cb

@ -0,0 +1,214 @@
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

library work;
use work.wishbone_types.all;

-- Interface for LCD/touchscreen connected to Arduino-compatible socket on Arty A7
entity lcd_touchscreen is
port (
clk : in std_ulogic;
rst : in std_ulogic;
wb_in : in wb_io_master_out;
wb_out : out wb_io_slave_out;
wb_sel : in std_ulogic;
tp : out std_ulogic;

lcd_din : in std_ulogic_vector(7 downto 0);
lcd_dout : out std_ulogic_vector(7 downto 0);
lcd_doe : out std_ulogic;
lcd_doe0 : out std_ulogic;
lcd_doe1 : out std_ulogic;
lcd_rd : out std_ulogic; -- note active low
lcd_wr : out std_ulogic; -- note active low
lcd_rs : out std_ulogic;
lcd_rsoe : out std_ulogic;
lcd_cs : out std_ulogic; -- note active low
lcd_csoe : out std_ulogic;
lcd_rst : out std_ulogic -- note active low
);
end entity lcd_touchscreen;

architecture rtl of lcd_touchscreen is

type state_t is (idle, prep1, prep2, writing, wr_pause, reading, rd_recovery);

signal state : state_t;
signal delay : unsigned(5 downto 0);
signal ack : std_ulogic;
signal idle1 : std_ulogic;
signal idle2 : std_ulogic;

signal rs : std_ulogic;
signal rsoe : std_ulogic;
signal cs : std_ulogic;
signal csoe : std_ulogic;
signal d0 : std_ulogic;
signal doe0 : std_ulogic;
signal doe1 : std_ulogic;
signal d1 : std_ulogic;
signal tsctrl : std_ulogic;

signal wr_data : std_ulogic_vector(31 downto 0);
signal rd_data : std_ulogic_vector(7 downto 0);
signal wr_sel : std_ulogic_vector(3 downto 0);
signal req_wr : std_ulogic;

-- Assume touchscreen is connected as follows:
-- X+ -> A5 (D1), X- -> A3 (CS), Y+ -> A2 (RS), Y- -> A4 (D0)

begin
-- for now; should make sure it is at least 10us wide
lcd_rst <= not rst;

wb_out.dat <= rd_data & rd_data & rd_data & rd_data;
wb_out.ack <= ack;
wb_out.stall <= '0' when state = idle else '1';

lcd_doe0 <= doe0;
lcd_doe1 <= doe1;
lcd_rs <= rs;
lcd_rsoe <= rsoe;
lcd_cs <= cs;
lcd_csoe <= csoe;

tp <= tsctrl;

process (clk)
begin
if rising_edge(clk) then
ack <= '0';
idle2 <= idle1;
if rst = '1' then
state <= idle;
delay <= to_unsigned(0, 6);
rd_data <= (others => '0');
lcd_rd <= '1';
lcd_wr <= '1';
cs <= '1';
csoe <= '1';
rs <= '0';
rsoe <= '1';
lcd_doe <= '0';
doe0 <= '0';
doe1 <= '0';
d0 <= '0';
d1 <= '0';
idle1 <= '0';
idle2 <= '0';
tsctrl <= '0';
elsif delay /= "000000" then
delay <= delay - 1;
else
case state is
when idle =>
req_wr <= wb_in.we;
wr_data <= wb_in.dat;
wr_sel <= wb_in.sel;
if idle2 = '1' then
-- delay this one cycle after entering idle
lcd_doe <= '0';
doe0 <= '0';
doe1 <= '0';
end if;
idle1 <= '0';
if wb_in.cyc = '1' and wb_in.stb = '1' and wb_sel = '1' then
if wb_in.sel = "0000" then
ack <= '1';
else
if wb_in.we = '1' or wb_in.adr(2) = '1' then
ack <= '1';
end if;
if wb_in.adr(2) = '0' then
tsctrl <= '0';
csoe <= '1';
cs <= '0'; -- active low
rsoe <= '1';
rs <= wb_in.adr(1);
doe0 <= '0';
doe1 <= '0';
state <= prep1;
else
tsctrl <= '1';
idle2 <= '0';
rd_data <= rsoe & rs & doe0 & d0 & doe1 & d1 & csoe & cs;
if wb_in.we = '1' and wb_in.sel(0) = '1' then
rsoe <= wb_in.dat(7);
rs <= wb_in.dat(6);
doe0 <= wb_in.dat(5);
d0 <= wb_in.dat(4);
lcd_dout(0) <= wb_in.dat(4);
doe1 <= wb_in.dat(3);
d1 <= wb_in.dat(2);
lcd_dout(1) <= wb_in.dat(2);
csoe <= wb_in.dat(1);
cs <= wb_in.dat(0);
end if;
end if;
end if;
else
if tsctrl = '0' then
cs <= '1';
end if;
end if;
when prep1 =>
lcd_doe <= req_wr;
doe0 <= req_wr;
doe1 <= req_wr;
if req_wr = '1' then
if wr_sel(1 downto 0) /= "00" then
if wr_sel(1) = '1' then
lcd_dout <= wr_data(15 downto 8);
wr_sel(1) <= '0';
else
lcd_dout <= wr_data(7 downto 0);
wr_sel(0) <= '0';
end if;
else
if wr_sel(3) = '1' then
lcd_dout <= wr_data(31 downto 24);
wr_sel(3) <= '0';
else
lcd_dout <= wr_data(23 downto 16);
wr_sel(2) <= '0';
end if;
end if;
end if;
state <= prep2;
when prep2 =>
if req_wr = '1' then
lcd_wr <= '0'; -- active low
state <= writing;
delay <= to_unsigned(1, 6);
else
lcd_rd <= '0';
state <= reading;
delay <= to_unsigned(35, 6);
end if;
when writing =>
-- last cycle of writing state
lcd_wr <= '1';
if wr_sel = "0000" then
state <= idle;
idle1 <= '1';
else
state <= wr_pause;
end if;
when wr_pause =>
state <= prep1;
when reading =>
-- last cycle of reading state
lcd_rd <= '1';
rd_data <= lcd_din;
ack <= '1';
state <= rd_recovery;
delay <= to_unsigned(6, 6);
when rd_recovery =>
state <= idle;
end case;
end if;
end if;
end process;

end architecture;

@ -166,6 +166,26 @@ set_property IOB true [get_cells -hierarchical -filter {NAME =~*.litesdcard2/sdp
set_property -dict { PACKAGE_PIN D2 IOSTANDARD LVCMOS33 PULLUP TRUE } [get_ports { i2c_rtc_d }];
set_property -dict { PACKAGE_PIN H2 IOSTANDARD LVCMOS33 PULLUP TRUE } [get_ports { i2c_rtc_c }];

################################################################################
# TFT LCD shield (arduino-compatible)
# hacked to swap the LCD_RST and LCD_D0 lines, and put LCD_D1 on A5
################################################################################

set_property -dict { PACKAGE_PIN N15 IOSTANDARD LVCMOS33 PULLDOWN TRUE } [get_ports { lcd_rst }];
#set_property -dict { PACKAGE_PIN M16 IOSTANDARD LVCMOS33 } [get_ports { lcd_tp }];
set_property -dict { PACKAGE_PIN P14 IOSTANDARD LVCMOS33 } [get_ports { lcd_d[2] }];
set_property -dict { PACKAGE_PIN T11 IOSTANDARD LVCMOS33 } [get_ports { lcd_d[3] }];
set_property -dict { PACKAGE_PIN R12 IOSTANDARD LVCMOS33 } [get_ports { lcd_d[4] }];
set_property -dict { PACKAGE_PIN T14 IOSTANDARD LVCMOS33 } [get_ports { lcd_d[5] }];
set_property -dict { PACKAGE_PIN T15 IOSTANDARD LVCMOS33 } [get_ports { lcd_d[6] }];
set_property -dict { PACKAGE_PIN T16 IOSTANDARD LVCMOS33 } [get_ports { lcd_d[7] }];
set_property -dict { PACKAGE_PIN F5 IOSTANDARD LVCMOS33 PULLUP TRUE } [get_ports { lcd_rd }]; # A0
set_property -dict { PACKAGE_PIN D8 IOSTANDARD LVCMOS33 PULLUP TRUE } [get_ports { lcd_wr }]; # A1
set_property -dict { PACKAGE_PIN C7 IOSTANDARD LVCMOS33 } [get_ports { lcd_rs }]; # A2
set_property -dict { PACKAGE_PIN E7 IOSTANDARD LVCMOS33 } [get_ports { lcd_cs }]; # A3
set_property -dict { PACKAGE_PIN D7 IOSTANDARD LVCMOS33 } [get_ports { lcd_d[0] }]; # A4
set_property -dict { PACKAGE_PIN D5 IOSTANDARD LVCMOS33 } [get_ports { lcd_d[1] }]; # A5

################################################################################
# Arduino/chipKIT shield connector
################################################################################

@ -30,6 +30,7 @@ entity toplevel is
HAS_UART1 : boolean := true;
USE_LITESDCARD : boolean := false;
HAS_GPIO : boolean := true;
USE_LCD : boolean := true;
NGPIO : natural := 32
);
port(
@ -140,6 +141,14 @@ entity toplevel is
i2c_rtc_d : inout std_ulogic;
i2c_rtc_c : inout std_ulogic;

-- LCD display interface
lcd_d : inout std_ulogic_vector(7 downto 0);
lcd_rs : out std_ulogic;
lcd_cs : out std_ulogic;
lcd_rd : out std_ulogic;
lcd_wr : out std_ulogic;
lcd_rst : out std_ulogic;

-- DRAM wires
ddram_a : out std_ulogic_vector(13 downto 0);
ddram_ba : out std_ulogic_vector(2 downto 0);
@ -185,6 +194,7 @@ architecture behaviour of toplevel is
signal wb_ext_is_dram_init : std_ulogic;
signal wb_ext_is_eth : std_ulogic;
signal wb_ext_is_sdcard : std_ulogic;
signal wb_ext_is_lcd : std_ulogic;

-- DRAM main data wishbone connection
signal wb_dram_in : wishbone_master_out;
@ -213,6 +223,9 @@ architecture behaviour of toplevel is
-- for conversion from non-pipelined wishbone to pipelined
signal wb_sddma_stb_sent : std_ulogic;

-- LCD touchscreen connection
signal wb_lcd_out : wb_io_slave_out := wb_io_slave_out_init;

-- Status LED
signal led_b_pwm : std_ulogic_vector(3 downto 0) := (others => '0');
signal led_r_pwm : std_ulogic_vector(3 downto 0) := (others => '0');
@ -289,6 +302,7 @@ begin
HAS_UART1 => HAS_UART1,
HAS_SD_CARD => USE_LITESDCARD,
HAS_SD_CARD2 => USE_LITESDCARD,
HAS_LCD => USE_LCD,
HAS_GPIO => HAS_GPIO,
NGPIO => NGPIO
)
@ -336,6 +350,7 @@ begin
wb_ext_is_dram_init => wb_ext_is_dram_init,
wb_ext_is_eth => wb_ext_is_eth,
wb_ext_is_sdcard => wb_ext_is_sdcard,
wb_ext_is_lcd => wb_ext_is_lcd,

-- DMA wishbone
wishbone_dma_in => wb_sddma_in,
@ -828,10 +843,54 @@ begin
disk_activity <= sdc0_activity or sdc1_activity;
end generate;

-- LCD touchscreen on arduino-compatible pins
has_lcd : if USE_LCD generate
signal lcd_dout : std_ulogic_vector(7 downto 0);
signal lcd_doe : std_ulogic;
signal lcd_doe0 : std_ulogic;
signal lcd_doe1 : std_ulogic;
signal lcd_rso : std_ulogic;
signal lcd_rsoe : std_ulogic;
signal lcd_cso : std_ulogic;
signal lcd_csoe : std_ulogic;
signal tp : std_ulogic;
begin
lcd0 : entity work.lcd_touchscreen
port map (
clk => system_clk,
rst => soc_rst,
wb_in => wb_ext_io_in,
wb_out => wb_lcd_out,
wb_sel => wb_ext_is_lcd,
tp => tp,

lcd_din => lcd_d,
lcd_dout => lcd_dout,
lcd_doe => lcd_doe,
lcd_doe0 => lcd_doe0,
lcd_doe1 => lcd_doe1,
lcd_rd => lcd_rd,
lcd_wr => lcd_wr,
lcd_rs => lcd_rso,
lcd_rsoe => lcd_rsoe,
lcd_cs => lcd_cso,
lcd_csoe => lcd_csoe,
lcd_rst => lcd_rst
);
-- lcd_d(0), lcd_d(1), lcd_rs, lcd_cs are used for the touchscreen
-- interface and hence have individual output enables.
lcd_d(0) <= lcd_dout(0) when lcd_doe0 = '1' else 'Z';
lcd_d(1) <= lcd_dout(1) when lcd_doe1 = '1' else 'Z';
lcd_d(7 downto 2) <= lcd_dout(7 downto 2) when lcd_doe = '1' else (others => 'Z');
lcd_rs <= lcd_rso when lcd_rsoe = '1' else 'Z';
lcd_cs <= lcd_cso when lcd_csoe = '1' else 'Z';
end generate;

-- Mux WB response on the IO bus
wb_ext_io_out <= wb_eth_out when wb_ext_is_eth = '1' else
wb_sdcard_out when wb_ext_is_sdcard = '1' and wb_ext_io_in.adr(13) = '0' else
wb_sdcard2_out when wb_ext_is_sdcard = '1' and wb_ext_io_in.adr(13) = '1' else
wb_lcd_out when wb_ext_is_lcd = '1' else
wb_dram_ctrl_out;

status_led_colour : process(all)

@ -139,6 +139,10 @@ filesets:
uart16550:
depend : [":microwatt:uart16550"]

lcdts:
files:
- fpga/arty-lcd-ts.vhdl : {file_type : vhdlSource-2008}

targets:
nexys_a7:
default_tool: vivado
@ -315,7 +319,7 @@ targets:

arty_a7-100-nodram:
default_tool: vivado
filesets: [core, arty_a7, soc, fpga, debug_xilinx, uart16550, xilinx_specific, litesdcard]
filesets: [core, arty_a7, soc, fpga, debug_xilinx, uart16550, xilinx_specific, litesdcard, lcdts]
parameters :
- memory_size
- ram_init_file
@ -336,7 +340,7 @@ targets:

arty_a7-100:
default_tool: vivado
filesets: [core, arty_a7, soc, fpga, debug_xilinx, litedram, liteeth, uart16550, xilinx_specific, litesdcard]
filesets: [core, arty_a7, soc, fpga, debug_xilinx, litedram, liteeth, uart16550, xilinx_specific, litesdcard, lcdts]
parameters:
- cpus
- memory_size
@ -344,6 +348,7 @@ targets:
- use_litedram=true
- use_liteeth=true
- use_litesdcard
- use_lcd
- disable_flatten_core
- no_bram
- spi_flash_offset=4194304
@ -570,6 +575,12 @@ parameters:
paramtype : generic
default : false

use_lcd:
datatype : bool
description : Use LCD touchscreen interface
paramtype : generic
default : false

uart_is_16550:
datatype : bool
description : Use 16550-compatible UART from OpenCores

@ -34,6 +34,7 @@ use work.wishbone_types.all;
-- 0xc8020000: LiteEth CSRs (*)
-- 0xc8030000: LiteEth MMIO (*)
-- 0xc8040000: LiteSDCard CSRs
-- 0xc8050000: LCD touchscreen interface

-- (*) LiteEth must be a single aligned 32KB block as the CSRs and MMIOs
-- are actually decoded as a single wishbone which LiteEth will
@ -95,6 +96,7 @@ entity soc is
DCACHE_TLB_NUM_WAYS : natural := 2;
HAS_SD_CARD : boolean := false;
HAS_SD_CARD2 : boolean := false;
HAS_LCD : boolean := false;
HAS_GPIO : boolean := false;
NGPIO : natural := 32
);
@ -116,6 +118,7 @@ entity soc is
wb_ext_is_dram_init : out std_ulogic;
wb_ext_is_eth : out std_ulogic;
wb_ext_is_sdcard : out std_ulogic;
wb_ext_is_lcd : out std_ulogic;

-- external DMA wishbone with 32-bit data/address
wishbone_dma_in : out wb_io_slave_out := wb_io_slave_out_init;
@ -686,6 +689,7 @@ begin
wb_ext_is_dram_csr <= '0';
wb_ext_is_eth <= '0';
wb_ext_is_sdcard <= '0';
wb_ext_is_lcd <= '0';
end if;
if do_cyc = '1' then
-- Decode I/O address
@ -715,6 +719,10 @@ begin
slave_io := SLAVE_IO_EXTERNAL;
io_cycle_external <= '1';
wb_ext_is_sdcard <= '1';
elsif std_match(match, x"--05-") and HAS_LCD then
slave_io := SLAVE_IO_EXTERNAL;
io_cycle_external <= '1';
wb_ext_is_lcd <= '1';
else
io_cycle_none <= '1';
end if;

Loading…
Cancel
Save