Connexion directe d’une caméra D5M avec un écran LCD LT-24 à l’aide d’un FPGA DE0-Nano

Pour réussir cet exploit, je suis parti de mes deux réussites précédentes, l’interfaçage de la caméra ainsi que l’interfaçage de l’écran LCD. Ce serait simple de joindre les deux bouts, n’est-ce pas? Eh bien, pas vraiment. La caméra produit une image de 2592×1944 pixels, en patron Bayer (alternance de rouge, vert, bleu). L’écran a 320×240 pixels et son format de couleur est 16bits (5 premiers bits bleus, 6 bits du milieu vert et 5 derniers bits rouges). Il faut donc adapter la taille de l’image, et garder une couleur en mémoire pour toute une rangée, afin de l’appliquer au bon pixel de la ligne suivante.

J’ai donc créé l’entité image_transform afin de faire ces deux étapes, en partant des signaux produits par mon pixel_interface, qui me donne Xpos et Ypos, les signaux donnant la position du pixel sur l’écran.

Un premier processus, choosepix, permet de sauter 7 pixels sur 8 et de produire deux signaux, Xposchosen et Yposchosen correspondant au modulo, ce qui fait que le cycle du compte de 0 à 7 se répète 320×240 fois.

Pour la première ligne du patron de Bayer, j’enregistre seulement le pixel rouge, avec le processus pixelsave. Celui-ci utilise un FIFO créé avec l’utilitaire de Quartus II (megafunction), j’ai essayé de le programmer moi-même, sans grand succès, j’ai donc décidé de remettre cette partie de plaisir à plus tard et de me fier aux ingénieurs d’ALTERA pour cette partie-là. C’est probablement la mémoire la plus simple d’utilisation, elle a très bien fait le travail. Cela m’a permit de découvrir la différence entre les éléments logiques LE (les blocs lego de base pour générer n’importe quel circuit logique) et l’embeded memory (bloc lego de base pour générer n’importe quelle mémoire). J’ai pas trop poussé ma compréhension de cette chose, mais c’est très intéressant. Pour l’instant, c’est simplement bon à savoir, car ça permet d’économiser les registres logiques (mais bon, je suis encore très loin de remplir le FPGA de toute manière).

Dans la deuxième ligne, à l’aide du processus pixelmix, je reconstruis le pixel en 16 bits, en lisant d’abord le bleu, puis le vert, puis la mémoire du rouge correspondant à la première adresse de la valeur précédente. Je vide ainsi toute la mémoire en repassant sur la ligne précédente.

Finalement, l’écran LCD demande une clock à chaque fois que je veux écrire un nouveau pixel dans sa mémoire, je génère la fréquence et la phase appropriée à l’aide du processus pixclkgen, à partir des signaux Xposchosen et Yposchosen. J’ai rajouté l’état suivant à la machine d’état de l’écran LCD, qui apparaît après l’initialisation complète :

when CamLiveFeed =>
    LT_DBi <= pixImage_in; -- Write pixel to LCD data port
    LT_WR_n <= newpixclk_in; -- toggle write pin with pixel clock
    State <= CamLiveFeed; -- deadlock

Voici mon entité image_transform :

———————–
— Image transformation
———————–
— library declaration
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
— entity
entity image_transform is
    port( D5M_PIXCLK : in std_logic;
        pixel_in : in std_logic_vector(4 downto 0);
        Xpos_in : in unsigned(11 downto 0);
        Ypos_in : in unsigned(10 downto 0);
        q_in : in std_logic_vector(4 downto 0);
        ———————————–
        aclr_out : out std_logic;
        data_out : out std_logic_vector(4 downto 0);
        rdclk_out : out std_logic;
        rdreq_out : out std_logic;
        wrclk_out : out std_logic;
        wrreq_out : out std_logic;
        pixImage_out : out std_logic_vector(15 downto 0);
        newpixclk_out : out std_logic);
end image_transform;
— architecture
architecture my_image_transform of image_transform is
    signal Xposchosen : unsigned(11 downto 0);
    signal Yposchosen : unsigned(10 downto 0);
begin
    chosepix : process(Xpos_in,Ypos_in)
    begin
        if ((Xpos_in < 2560) and (Ypos_in < 1920)) then -- for a 320x240 picture
            Xposchosen <= Xpos_in mod 8;
            Yposchosen <= Ypos_in mod 8;
        else
            Xposchosen <= (others=>‘1’);
            Yposchosen <= (others=>‘1’);
        end if;
    end process chosepix;
    wrclk_out <= not D5M_PIXCLK; -- write and read fifo at falling edge of the clock
    rdclk_out <= not D5M_PIXCLK;
    pixelsave : process(D5M_PIXCLK,Yposchosen,Xposchosen) — save first row each 8 rows and skip 8 pixels on x axis to match output resolution
    begin
        if ((Yposchosen = 0) and (Xposchosen = 1)) then
            if (rising_edge(D5M_PIXCLK) ) then
                wrreq_out <= '1';
                data_out <= pixel_in; -- put the first pixel row in memory buffer
            end if;
        else
            wrreq_out <= '0';
        end if;
    end process pixelsave;
    pixelmix : process(D5M_PIXCLK,Yposchosen,Xposchosen)
    begin
        if (rising_edge(D5M_PIXCLK) and (Yposchosen = 1) and (Xposchosen = 0)) then
                pixImage_out(4 downto 0) <= pixel_in; -- blue value of output pixel
        end if;
        if ((Yposchosen = 1) and (Xposchosen = 1)) then
            if (rising_edge(D5M_PIXCLK)) then
                rdreq_out <= '1';
                pixImage_out(10 downto 5) <= pixel_in & '0'; -- green value of output pixel
                pixImage_out(15 downto 11) <= q_in; -- red value of output pixel
            end if;
        else
            rdreq_out <= '0';
        end if;
        if (Yposchosen = 2) then
            aclr_out <= '1';
        else
            aclr_out <= '0';
        end if;
    end process pixelmix;
    pixclckgen : process(D5M_PIXCLK) — generates a clock at the pixel_out rate
    begin
        if (rising_edge(D5M_PIXCLK) and (Xposchosen < 4) and (Yposchosen = 1)) then
            newpixclk_out <= '0';
        elsif (rising_edge(D5M_PIXCLK) and (Xposchosen >= 4) and (Yposchosen = 1)) then
            newpixclk_out <= '1';
        end if;
    end process pixclckgen;
end my_image_transform;

L’entité du top permet de tout connecter en série, je ne l’inclurai pas ici.