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)


Name (required):

Website:

Comment:

Discussione:Sitronix ST7066U controller

106 Rating: 2.3/5 (12 votes cast)

Strumenti personali