J’étais un peu perplexe lors de l’interfaçage de cette partie du projet. La documentation fournie par Terasic était très superficielle, se contentant de montrer les exemples, codés avec un processeur NIOS (donc, par définition, complètement obscur). Pour ma compréhension personnelle du fonctionnement des FPGA, je voulais que toute la logique soit explicitée en VHDL. Après une recherche approfondie des internets, je suis tombé sur cette « Application Note » provenant d’ALSE. Ils ont enlevé quelques parties du code, pour une raison ou une autre, mais avec un peu de déduction, c’est possible de le reconstruire. Sans ce document, je n’aurais pas réussi à faire grand chose. Ils ont déchiffré le datasheet de plus de 200 pages de l’ ILI9341 pour sortir les registres dans lequel il faut absolument écrire pour initialiser l’écran.
Le programme d’ALSE utilise entre autres une machine d’états pour envoyer des commandes par le port d’interface parallèle, en tenant compte de toutes les spécificités du contrôleur. Cela m’a permis tout d’abord de comprendre comment programmer une machine d’état en VHDL (mon cours de circuits logique était loin, et on n’a jamais vu ça!)
Un compteur (tickclk) permet de produire une horloge qui produit un pulse à toutes les 1ms, ce qui permet de passer de commande à commande plutôt lentement.
Un array de commandes contenant à la fois le délai nécessaire après l’écriture de celle-ci sur le port, le contenu (adresse ou data) et la valeur en 16 bits à écrire permet de produire toutes les séquences d’écriture nécessaire pour écrire dans tous les registres souhaités. Ensuite, il suffit de lire le datasheet! Le programme ci-dessous est fortement inspiré de l’exemple d’ALSE et permet d’afficher quelque pixels avec la couleur de notre choix.
— library declaration
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
— entity
entity lcdtest is
port ( Clk : in std_logic;
Rst : in std_logic;
——————–
— LT24 graphic Controller
LT_DBi : out std_logic_vector(15 downto 0); — we use it as OUT only
LT_WR_n : out std_logic; — aka WRX
LT_RD_n : out std_logic; — aka RDX
LT_RS : out std_logic; — aka D/CX
LT_CS_n : out std_logic; — aka CSX
LT_RESET_n : out std_logic; — aka RESX
LT_LCD_ON : out std_logic); — Transistor to drive LCD lighting (1 = on)
end lcdtest;
— architecture
architecture my_lcdtest of lcdtest is
subtype Cmd_t is std_logic_vector (28 downto 0);
— Dly_ms(12) & C/D & Data(16) – 29 bits, could be a record
type Commands_t is array (natural range <>) of Cmd_t;
constant Commands : Commands_t := (
— delay_ms D/Cd_n DB — p.83 datasheet ILI9341
x »001″ & ‘0’ & x »0011″, — Exit Sleep
— x »100″ & ‘0’ & x »0001″, — Software reset
x »800″ & ‘0’ & x »0029″, — Display ON
x »001″ & ‘0’ & x »003A », — Set COLMOD register
x »000″ & ‘1’ & x »0055″, — non-default 16 bits RGB data
x »000″ & ‘0’ & x »0036″, — Memory Access Control
— x »000″ & ‘1’ & x »0008″, — non-default BGR filter !
x »000″ & ‘1’ & x »0028″, — non-default BGR filter + row/col inversion for putting the display in 320×240 mode
x »000″ & ‘0’ & x »00F2″, — Enable 3G register
x »000″ & ‘1’ & x »0000″, — non-default = disable 3 gamma — à voir plus tard si je veux une correction gamma ou pas, pour l’instant on va laisser ça comme ça
x »000″ & ‘0’ & x »002A », — Set Column address
x »000″ & ‘1’ & x »0000″, —
x »000″ & ‘1’ & x »0010″, — 16
x »000″ & ‘0’ & x »002B », — Set Page address
x »000″ & ‘1’ & x »0000″, —
x »000″ & ‘1’ & x »000A », — 10
x »000″ & ‘0’ & x »002C », — Memory Write
x »000″ & ‘1’ & x »FFFF », — WHITE
x »000″ & ‘1’ & x »FFFF », — WHITE
x »000″ & ‘1’ & x »FFFF », — WHITE
x »000″ & ‘1’ & x »FFFF », — WHITE
x »000″ & ‘1’ & x »000F », — color
x »000″ & ‘1’ & x »000F », — color
x »000″ & ‘1’ & x »000F », — color
x »000″ & ‘1’ & x »000F », — color
x »000″ & ‘1’ & x »000F », — color
x »000″ & ‘1’ & x »000F », — color
x »000″ & ‘1’ & x »000F », — color
x »000″ & ‘1’ & x »F000″, — color
x »000″ & ‘1’ & x »F000″, — color
x »000″ & ‘1’ & x »F000″, — color
x »000″ & ‘1’ & x »F000″, — color
x »000″ & ‘1’ & x »F000″, — color
x »000″ & ‘1’ & x »F000″, — color
x »000″ & ‘1’ & x »000F », — color
x »000″ & ‘1’ & x »000F », — color
x »000″ & ‘1’ & x »000F », — color
x »000″ & ‘1’ & x »000F », — color
x »000″ & ‘1’ & x »000F », — color
x »000″ & ‘1’ & x »000F », — color
x »000″ & ‘1’ & x »000F », — color
x »000″ & ‘1’ & x »000F »); — color
type state_type is (Boot,Idle,GetCmd,Dly,Wr1,Wr2,Done,Done1); — All the states of the FSM
signal State : state_type;
signal Index : integer range 0 to Commands’length; — Index of the command set
signal Cntr : integer range 0 to to_integer(x »FFF »); — Count of delay between instructions
signal Cycle : integer range 0 to 255; — Counter for the write cycle
signal Cmd : Cmd_t;
constant Nsetup : integer := 5; — Write cycle, try to reduce it to speed up
constant Nhold : integer := 5;
signal Tick1ms : std_logic;
— —————————
— ILI9341 interface
— —————————
— CSX can be kept low
— Write cycle > 66ns = 100 ns (5c = 2 + 3) can work
— Tdst data setup > 10 ns
— Tdht data hold > 10 ns
— /!\ Data read (not used here) is SLOW (500 ns)
begin
LT_RD_n <= '1'; -- we don't read
tickclk : process(Clk)
variable clockcount : integer range 0 to 49999 := 49999;
begin
if falling_edge(Clk) then
if clockcount = 0 then
clockcount := 49999;
Tick1ms <= '1';
else
clockcount := clockcount – 1;
Tick1ms <= '0';
end if;
end if;
end process tickclk;
state_machine : process(Clk,Rst)
begin
if Rst=’0′ then
State <= Boot;
LT_DBi <= (others=>‘0’);
LT_WR_n <= '1';
LT_CS_n <= '1';
LT_RS <= '0';
LT_RESET_n <= '0';
LT_LCD_ON <= '0';
Index <= 0;
Cntr <= 15;
Cycle <= 0;
Cmd <=
(others=>‘0’);
elsif rising_edge(Clk) then
Cmd <= Commands(Index);
case State is
when Boot =>
LT_RESET_n <= '0';
Index <= 0;
if Tick1ms=’1′ then
if Cntr=0 then
LT_RESET_n <= '1';
Cntr <= 300;
State <= Idle;
else
Cntr <= Cntr - 1;
end if;
end if;
when Idle =>
if Cntr=0 then
LT_LCD_ON <= '1'; -- Light ON
LT_CS_n <= '0';
State <= GetCmd;
elsif Tick1ms=’1′ then
Cntr <= Cntr - 1;
end if;
when GetCmd =>
Cntr <= to_integer(unsigned(Cmd(Cmd_t'high downto Cmd'high-11)));
LT_DBi <= Cmd(LT_DBi'range);
LT_RS <= Cmd(LT_DBi'length);
State <= Dly;
when Dly =>
if Cntr = 0 then
Cycle <= Nsetup-1;
LT_WR_n <= '0';
State <= Wr1;
elsif Tick1ms=’1′ then
Cntr <= Cntr-1;
end if;
when Wr1 => — Setup
if Cycle = 0 then
Cycle <= Nhold-1;
LT_WR_n <= '1'; -- rising edge (trig write)
State <= Wr2;
else
Cycle <= Cycle-1;
end if;
when Wr2 => — Hold
if Cycle = 0 then
State <= Done;
else
Cycle <= Cycle-1;
end if;
when Done =>
if Index=Commands’length-1 then — last command has been sent
LT_CS_n <= '1'; -- de-select the interface
State <= Done; -- deadlock (default)
else
Index <= Index + 1; -- fecth next vector
State <= Done1;
end if;
when Done1 => — compensate double pipeline
State <= GetCmd;
end case;
end if;
end process state_machine;
end my_lcdtest;