Sitronix ST7066U controller
Da PiemonteWireless.
Gestire il controller LCD della board FPGA Xilinx Spartan 3AN
La scheda FPGA Xilinx Spartan 3AN (o simile come il modello 3E) possiede un LCD composto da 2 linee per 16 caratteri.
Lo schermo LCD e' gestito da un controller Sitronix ST7066U, che è equivalente a:
- Samsung S6A0069X or KS0066U
- Hitachi HD44780
- SMOS SED1278
Ho da poco ricevuto questa scheda e, poichè assolutamente nuovo a questo mondo, ho cominciato con qualche semplice esercizio in VHDL quali accendere led, settare interrutori etc...
Riporto qui il codice VHDL che ho implementato per la gestione del controller LCD, ritenendolo interessante ed utile per un principante (in fondo troverete il file ZIP che contiene i file qui esposti).
---------------------------------------------------------------------------------- -- Company: Piemonte Wireless (www.piemontewireless.net) -- Engineer: Simone Rotondo -- -- Create Date: 03:00:16 04/09/2008 -- Design Name: -- Module Name: lcd_init - init_arch -- Project Name: -- Target Devices: -- Tool versions: -- Description: Manage LCD controller "Sitronix ST7066U" (I've it on my Spartan 3AN) -- For datasheet look at http://www.sitronix.com.tw/sitronix/SASpecDoc.nsf/FileDownload/ST7066U614654/$FILE/ST7066Uv22.pdf -- -- Dependencies: -- -- Revision: -- Revision 0.02 - Initial testing with this LCD controller -- Additional Comments: -- ---------------------------------------------------------------------------------- library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.numeric_std.ALL; entity lcd_init is port( rst: in STD_LOGIC; clk: in STD_LOGIC; in_en: in STD_LOGIC; in_db: in std_logic_vector(7 downto 0); -- DB<7-0> control: out std_logic_vector(2 downto 0); -- LCD_RS, LCD_RW, LCD_E lcd_db: out std_logic_vector(7 downto 0) -- DB<7-0> ); end lcd_init; architecture init_arch of lcd_init is type state_type is ( waiting, init1,init2,init3,init4,init5,init6,init7,init8,writeX,writeXY,donothing ); signal state, state_next: state_type := waiting; signal count, count_next: unsigned(19 downto 0) := "00000000000000000000"; signal char_pos, char_pos_next: unsigned(2 downto 0) := "000"; constant TIME1: integer := 750000; -- (750000 clk = 15000000ns = 15ms) constant TIME2: integer := 12; -- (12 clk = 240ns) constant TIME3: integer := 210000; -- (210000 clk = 4200000ns = 4.2ms) constant TIME4: integer := 5000; -- (5000 clk = 100000ns = 100us) constant TIME5: integer := 2000; -- (2000 clk = 40000ns = 40us) constant TIME6: integer := 82500; -- (82500 clk = 1650000ns = 1.65ms) begin run : process (state,count,char_pos,in_db) is begin case state is -- <Initialization> --- attendere per un periodo iniziale di almeno 15ms (con alimentazione a 5V) --- o di almeno 40ms (con alimentazione a 2,7V) ---- (15ms) when waiting => lcd_db <= "00000000"; state_next <= waiting; control <= "000"; -- RS, RW, E if (count >= TIME1) then state_next <= init1; count_next <= (others=>'0'); end if; --- inviare il comando per la predisposizione di un'interfaccia a 8bit o a 4bit senza --- curarsi della forma del visualizzatore e della matrice del carattere; il codice operativo --- puo' essere dunque ambiguo su tutti i 4bit meno significativi, cioè sarà del tipo 0011xxxx; --- l'imposizione di un ulteriore ritardo di 4,1ms; ---- (240ns, 4.2ms) when init1 => lcd_db <= "00110000"; state_next <= init1; if (count = (TIME3+TIME2ÿ then state_next <= init2; count_next <= (others=>'0'); control <= "000"; elsif (count > TIME2 AND count < (TIME3+TIME2ÿ then control <= "000"; else control <= "001"; -- EN=1 end if; --- come sopra; un ulteriore ritardo di 100us; ---- (240ns, 100us) when init2 => lcd_db <= "00110000"; state_next <= init2; if (count = (TIME4+TIME2ÿ then state_next <= init3; count_next <= (others=>'0'); control <= "000"; elsif (count > TIME2 AND count < (TIME4+TIME2ÿ then control <= "000"; else control <= "001"; -- EN=1 end if; --- come sopra; un ulteriore ritardo di 40us; ---- (240ns, 40us) when init3 => lcd_db <= "00110000"; state_next <= init3; if (count = (TIME5+TIME2ÿ then state_next <= init4; count_next <= (others=>'0'); control <= "000"; elsif (count > TIME2 AND count < (TIME5+TIME2ÿ then control <= "000"; else control <= "001"; -- EN=1 end if; --- dopo queste 3 istruzioni di sincronizzazione (durante le quali R/W=0 e RS=0 e non e' --- ammesso verificare il Busy Flag) sono fornite in sequenza quelle che impostano il --- controller per le nostre effettive esigenze; ---- (240ns, 40us) when init4 => lcd_db <= "00111100"; -- "Function Set" DL=8bit, NL=2line, Font=5x11 state_next <= init4; if (count = (TIME5+TIME2ÿ then state_next <= init5; count_next <= (others=>'0'); control <= "000"; elsif (count > TIME2 AND count < (TIME5+TIME2ÿ then control <= "000"; else control <= "001"; end if; -- settare il display On, e disabilitare il cursore e la sua posizione (lampeggio) ---- (240ns, 40us) when init5 => lcd_db <= "00001100"; -- "Display On/Off" Display=on, Cursor=off, cursorBlink=off state_next <= init5; if (count = (TIME5+TIME2ÿ then state_next <= init6; count_next <= (others=>'0'); control <= "000"; elsif (count > TIME2 AND count < (TIME5+TIME2ÿ then control <= "000"; else control <= "001"; end if; -- cancellare il display e settare il cursore alla posizione iniziale; -- impostare un ritardo di almeno 1.65ms ---- (240ns, 1.65ms) when init6 => lcd_db <= "00000001"; -- "Clear Display" state_next <= init6; if (count = (TIME6+TIME2ÿ then state_next <= init7; count_next <= (others=>'0'); control <= "000"; elsif (count > TIME2 AND count < (TIME6+TIME2ÿ then control <= "000"; else control <= "001"; -- EN=1 end if; -- impostare il movimento automatico del cursore (verso destra) dopo un data write; ---- (240ns, 40us) when init7 => lcd_db <= "00000100"; -- "Entry Mode" I/D=1(right), S=1(active) state_next <= init7; if (count = (TIME5+TIME2ÿ then state_next <= init8; count_next <= (others=>'0'); control <= "000"; elsif (count > TIME2 AND count < (TIME5+TIME2ÿ then control <= "000"; else control <= "001"; -- EN=1 end if; -- settare a 00H il puntatore alla DDRAM ---- (240ns, 40us) when init8 => lcd_db <= "10000000"; -- "Set DDRAM Address" A<6-0>=00H state_next <= init8; if (count = (TIME5+TIME2ÿ then state_next <= writeX; count_next <= (others=>'0'); control <= "000"; elsif (count > TIME2 AND count < (TIME5+TIME2ÿ then control <= "000"; else control <= "001"; -- EN=1 end if; -- </Initialization> -- <Write to DDRAM> -- Shifta il puntatore alla DDRAM ---- (240ns, 40us) when writeX => lcd_db <= "10000" & std_logic_vector(char_pos(2 downto 0ÿ; -- S/C=0(cursor), R/L=1(right) state_next <= writeX; if (count = (TIME5+TIME2ÿ then state_next <= writeXY; count_next <= (others=>'0'); control <= "000"; elsif (count > TIME2 AND count < (TIME5+TIME2ÿ then control <= "000"; else control <= "001"; -- EN=1 end if; -- scrivo il codice carattere nella DDRAM (il codice carattere e' nella CGROM) ---- (240ns, 40us) when writeXY => lcd_db <= in_db; -- "Write Data to RAM" char=S state_next <= writeXY; if (count = (TIME5+TIME2ÿ then state_next <= donothing; count_next <= (others=>'0'); control <= "000"; elsif (count > TIME2 AND count < (TIME5+TIME2ÿ then control <= "100"; -- RS=1 else control <= "101"; -- RS=1, EN=1 end if; when donothing => lcd_db <= "00000000"; state_next <= donothing; control <= "000"; -- RS, RW, E if (count >= TIME6) then state_next <= donothing; count_next <= (others=>'0'); end if; -- </Write to DDRAM> end case; end process run; count_next <= count + 1; char_pos_next <= char_pos + 1; timing : process (clk, rst, count_next, state_next) is begin if (rst = '1') then state <= waiting; count <= (others=>'0'); elsif (rising_edge(clkÿ then if (in_en = '1') then state <= writeX; count <= (others=>'0'); char_pos <= char_pos_next; else state <= state_next; count <= count_next; end if; end if; end process timing; end init_arch;
Un esempio di utilizzo:
---------------------------------------------------------------------------------- -- Company: Piemonte Wireless (www.piemontewireless.net) -- Engineer: Simone Rotondo -- -- Create Date: 02:56:49 04/09/2008 -- Design Name: -- Module Name: lcd_controller_usage - rtl_arch -- Project Name: -- Target Devices: -- Tool versions: -- Description: Manage LCD controller "Sitronix ST7066U" (I've it on my Spartan 3AN) -- For datasheet look at http://www.sitronix.com.tw/sitronix/SASpecDoc.nsf/FileDownload/ST7066U614654/$FILE/ST7066Uv22.pdf -- -- Dependencies: -- -- Revision: -- Revision 0.02 - Initial testing with this LCD controller -- Additional Comments: -- ---------------------------------------------------------------------------------- library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity lcd_controller_usage is port( clk: in STD_LOGIC; rst: in STD_LOGIC; sw: in std_logic_vector(3 downto 0); control: out std_logic_vector(2 downto 0); -- LCD_RS, LCD_RW, LCD_E lcd_db: out std_logic_vector(7 downto 0) -- DB<7-0> ); end lcd_controller_usage; architecture rtl_arch of lcd_controller_usage is signal q_reg, q_next: std_logic_vector(26 downto 0) := "000000000000000000000000000"; signal lcd_en: STD_LOGIC := '0'; signal in_db: std_logic_vector(7 downto 0) := "00100000"; begin lcd_unit: entity work.lcd_init port map( clk=>clk, rst=>rst, control=>control, lcd_db=>lcd_db, in_en=>lcd_en, in_db=>in_db ); process (clk) begin if (rising_edge(clkÿ then q_reg <= q_next; end if; end process; q_next <= q_reg + 1; process (q_reg,sw) begin lcd_en <= '0'; in_db <= "0100" & sw; if (q_reg = "100000000000000000000000000") then lcd_en <= '1'; end if; end process; end rtl_arch;
Riporto anche il constraint file della Spartan 3AN:
# Period constraint for 50MHz operation of control logic NET "clk" PERIOD = 20.0ns HIGH 50%; # I/O constraints # soldered 50MHz Clock NET "clk" LOC = "E12" | IOSTANDARD = LVTTL; # Directional Push-Buttons (BTN) NET "rst" LOC = "U15" | IOSTANDARD = LVCMOS33 | PULLDOWN ; # Character Display (LCD) NET "lcd_db<0>" LOC = "Y13" | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = SLOW ; NET "lcd_db<1>" LOC = "AB18" | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = SLOW ; NET "lcd_db<2>" LOC = "AB17" | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = SLOW ; NET "lcd_db<3>" LOC = "AB12" | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = SLOW ; NET "lcd_db<4>" LOC = "AA12" | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = SLOW ; NET "lcd_db<5>" LOC = "Y16" | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = SLOW ; NET "lcd_db<6>" LOC = "AB16" | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = SLOW ; NET "lcd_db<7>" LOC = "Y15" | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = SLOW ; NET "control<2>" LOC = "Y14" | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = SLOW ; NET "control<1>" LOC = "W13" | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = SLOW ; NET "control<0>" LOC = "AB4" | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = SLOW ; # Mechanical Switches (SW) NET "sw<0>" LOC = "V8" | IOSTANDARD = LVCMOS33 ; NET "sw<1>" LOC = "U10" | IOSTANDARD = LVCMOS33 ; NET "sw<2>" LOC = "U8" | IOSTANDARD = LVCMOS33 ; NET "sw<3>" LOC = "T9" | IOSTANDARD = LVCMOS33 ;
File ZIP con i files mostrati sopra.
In coclusione, a tutti coloro che sono da poco nel campo dei Field Programmable Gate Array (e possiedono una scheda Xilinx Spartan 3) consiglio questo ottimo libro:
- FPGA Prototyping by VHDL Examples: Xilinx Spartan-3 Version (di Pong P. Chu)

