Interfaçage « rapide » entre la caméra D5M et l’écran LCD LT-24 à l’aide d’un FPGA DE0-Nano

Afin de contrôler complètement la caméra, j’ai dû utiliser le port I2C. Après quelques recherches sur les internets, j’ai trouvé un programme VHDL qui fournit la logique d’interfaçage, prenant en entrée un port parallèle et générant un signal I2C de sortie, avec un clock à la bonne fréquence. Merci à Scott Larson pour cet excellent travail, je n’aurais jamais écrit ça moi-même.

Tout ce qu’il me fallait faire (et c’était quand même passablement compliqué), c’était de créer une machine d’état pour écrire dans les registres souhaités de la caméra. Pour cela, je me suis inspiré de la machine d’état pour contrôler l’écran LCD, ainsi que de l’exemple fournit sur le site donné ci-haut.

———————————
— Data feeder
———————————
— library declaration
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
— entity
entity datafeeder is
    port ( feed_clk : in std_logic;
        i2c_busy : in std_logic;
        i2c_data_rd : in std_logic_vector(7 downto 0);
        feed_rst_n : in std_logic;
        ——————————–
        i2c_reset_n : out std_logic;
        i2c_ena : out std_logic;
        i2c_addr : out std_logic_vector(6 downto 0);
        i2c_rw : out std_logic;
        i2c_data_wr : out std_logic_vector(7 downto 0);
        data : out std_logic_vector (15 downto 0));
end datafeeder;
— architecture
architecture my_datafeeder of datafeeder is
    signal busy_prev : std_logic;
    constant slave_addr : std_logic_vector(6 downto 0) := « 1011101 »; — The address of d5m camera is BA write and BB read, the first 7 bits are 1011101 or 5D
    type state_type is (start,getcmd,delay,writereg,readreg,donewr,done); — All the states of the FSM
    signal state : state_type;
    subtype Cmd_t is std_logic_vector (23 downto 0);
    type Commands_t is array (natural range <>) of Cmd_t;
    constant Commands : Commands_t := (     — 8 MSB bits for register address and 16 LSB bits for data
    x »0B » & x »0003″, –Restart, pause acquisition before changing settings
    — Change pixclock to 96MHz (Nope. Max Throughput of pipeline is at 50Mhz for 320×240 picture on LCD
    –x »10″ & x »0051″, –PLL control, (power on : last bit = 1)
    –x »11″ & x »6018″, –PLL config 1, (m factor : 96, n factor – 1 : 24)
    –x »12″ & x »0001″, –PLL config 2, (p factor – 1 : 1)
    –x »10″ & x »0053″, –PLL control, (power on and enable on, last 2 bits)
    — Change image size to 320×240
    x »01″ & x »0042″, –Row start, add 12 to default 54 = 66
    x »02″ & x »0020″, –Column start, add 16 to default 16 = 32
    x »03″ & x »077F », –Row size, 240*8 – 1 = 1919
    x »04″ & x »09FF », –Column size, 320*8 -1 = 2559
    x »22″ & x »0003″, –Row address mode, bin1, skip4, skip 4 pour une image de 640×480 en pattern bayer
    x »23″ & x »0003″, –Column address mode, bin1, skip4, le bin rajoute du flou en déplacement
    x »0B » & x »0001″);–Restart, clear pause_restart bit
    signal Index : integer range 0 to Commands’length; — Index of the command set
    signal Cmd : Cmd_t;
    signal reg_addr : std_logic_vector(7 downto 0);
    signal reg_data : std_logic_vector(15 downto 0);
    signal rw : std_logic;
begin
    state_machine : process(feed_clk,feed_rst_n)
    variable busy_cnt : integer range 0 to 3;
    variable clockcount : integer range 0 to 49999 := 49999; — Delay 1ms between each command
    begin
        if (feed_rst_n = ‘0’) then
            i2c_reset_n <= '0';
            Index <= 0;
            rw <= '0';
            state <= start;
        elsif rising_edge(feed_clk) then
            Cmd <= Commands(Index);
            case state is
                when start =>
                    i2c_reset_n <= '1';
                    i2c_ena <= '0';
                    state <= getcmd;
                when getcmd =>
                    reg_addr <= Cmd(23 downto 16);
                    reg_data <= Cmd(15 downto 0);
                    state <= delay;
                when delay =>
                    if rw = ‘0’ then
                        if clockcount = 0 then
                            state <= writereg;
                        end if;
                        clockcount := clockcount – 1;
                    else
                        state <= readreg;
                    end if;
                when writereg => –state for conducting this transaction
                    busy_prev <= i2c_busy; --capture the value of the previous i2c busy signal
                    if(busy_prev = ‘0’ AND i2c_busy = ‘1’) then –i2c busy just went high
                    busy_cnt := busy_cnt + 1; –counts the times busy has gone from low to high during transaction
                    end if;
                    case busy_cnt is –busy_cnt keeps track of which command we are on
                        when 0 => –no command latched in yet
                            i2c_ena <= '1'; --initiate the transaction
                            i2c_addr <= slave_addr; --set the address of the slave
                            i2c_rw <= '0'; --command 1 is a write
                            i2c_data_wr <= reg_addr; --write register address
                        when 1 =>
                            i2c_rw <= '0'; --command 2 is a write
                            i2c_data_wr <= reg_data(15 downto 8); --write msb bits of data to register
                        when 2 =>
                            i2c_data_wr <= reg_data(7 downto 0); --write lsb bits of data to register
                        when 3 =>
                            i2c_ena <='0'; --disable i2c module, all commands are passed to latch
                            if (i2c_busy = ‘0’) then –i2c module has finihsed
                                busy_cnt := 0; –reset busy counter
                                state <= done;
                            end if;
                        when others => null;
                    end case;
                when readreg =>
                    busy_prev <= i2c_busy; --capture the value of the previous i2c busy signal
                    if(busy_prev = ‘0’ AND i2c_busy = ‘1’) then –i2c busy just went high
                        busy_cnt := busy_cnt + 1; –counts the times busy has gone from low to high during transaction
                    end if;
                    case busy_cnt is
                        when 0 => –no command latched in yet
                            i2c_ena <= '1'; --initiate the transaction
                            i2c_addr <= slave_addr; --set the address of the slave
                            i2c_rw <= '0'; --command 1 is a write
                            i2c_data_wr <= reg_addr; --data to be written
                        when 1 =>
                            i2c_rw <= '1';
                        when 2 =>
                            i2c_rw <= '1';
                            if (i2c_busy = ‘0’) then
                                data(15 downto 8) <= i2c_data_rd;
                            end if;
                        when 3 =>
                            i2c_ena <= '0';
                            if (i2c_busy = ‘0’) then
                                data(7 downto 0) <= i2c_data_rd;
                                busy_cnt := 0;
                                state <= done;
                            end if;
                        when others => null;
                    end case;
                when done =>
                    if Index=Commands’length then — last command has been sent
                        –state <= donewr;
                        state <= done;
                        data <= x"FF00";
                    else
                        Index <= Index + 1; -- fecth next vector
                        state <= getcmd;
                    end if;
                when donewr => — read in loop all registers written
                    rw <= '1';
                    Index <= 0;
                    state <= getcmd;
            end case;
        end if;
    end process state_machine;
end my_datafeeder;

Par la suite, j’ai modifié le processus choosepix, vu que le format de l’image produit par la caméra est déjà le bon. Je prends une image de 640×480 en bayer, vu que je veux une 320×240 rbg, je dois prendre deux fois plus de pixels sur la caméra pour ramener les couleurs ensemble.

chosepix : process(Xpos_in,Ypos_in)
begin
    if ((Xpos_in < 640) and (Ypos_in < 480)) then -- for a 320x240 picture
        Xposchosen <= Xpos_in mod 2; --Read bayer pattern (skip 1 col)
        Yposchosen <= Ypos_in mod 2; --Read bayer pattern (skip 1 row)
    else
        Xposchosen <= (others=>‘1’);
        Yposchosen <= (others=>‘1’);
    end if;
end process chosepix;

J’ai dû changer pixelmix aussi parce qu’il y avait un bug avec le fifo, qui faisait que je lisais la sortie avant le coup d’horloge, donc l’image rouge était décalé horizontalement d’un pixel. Pour le résoudre, j’ai créé un latch et créé un front montant sur la pin rd du fifo avant la lecture de la mémoire :

pixelmix : process(D5M_PIXCLK,Yposchosen,Xposchosen,q_in)
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
        end if;
    else
        pixImage_out(15 downto 11) <= q_in; -- red value of output pixel
        rdreq_out <= '0';
    end if;
end process pixelmix;

Finalement, je ne sais pas pourquoi, mais tout ça a rajouté un petit délai lors de l’écriture des pixels sur l’écran LCD, je l’ai rajout en faisant un not sur la clock newpixclk pour l’inverser, mais ce n’est vraiment pas très beau. En tout cas, ça fonctionne!!