From 172eae61cba4a11037f10e53a0f3f40ebdc7844a Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Wed, 15 Dec 2021 19:59:15 +1100 Subject: [PATCH] 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 --- fpga/arty-lcd-ts.vhdl | 214 ++++++++++++++++++++++++++++++++++++++++++ fpga/arty_a7.xdc | 20 ++++ fpga/top-arty.vhdl | 59 ++++++++++++ microwatt.core | 15 ++- soc.vhdl | 8 ++ 5 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 fpga/arty-lcd-ts.vhdl diff --git a/fpga/arty-lcd-ts.vhdl b/fpga/arty-lcd-ts.vhdl new file mode 100644 index 0000000..614bfa8 --- /dev/null +++ b/fpga/arty-lcd-ts.vhdl @@ -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; diff --git a/fpga/arty_a7.xdc b/fpga/arty_a7.xdc index c52d072..7f6a0a5 100644 --- a/fpga/arty_a7.xdc +++ b/fpga/arty_a7.xdc @@ -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 ################################################################################ diff --git a/fpga/top-arty.vhdl b/fpga/top-arty.vhdl index b4ba15a..cf5f775 100644 --- a/fpga/top-arty.vhdl +++ b/fpga/top-arty.vhdl @@ -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) diff --git a/microwatt.core b/microwatt.core index c1f6184..b54248e 100644 --- a/microwatt.core +++ b/microwatt.core @@ -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 diff --git a/soc.vhdl b/soc.vhdl index fc30014..539b26e 100644 --- a/soc.vhdl +++ b/soc.vhdl @@ -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;