kroszkanorber Napisano 10 stycznia Udostępnij Napisano 10 stycznia (edytowany) Witam. Wrzucam kod dla magistrali I2C. Parę słów wyjaśnień dla początkujących, czyli takich jak ja 🙂 Komunikacja polega na wysłaniu sekwencji bajtów. 1 bajt adresu urządzenia, 2 bajt adresu dla danych, 3 bajt danych zapis, 4 bajt danych odczyt. Nie chcę wchodzić w szczegóły ponieważ wszystko opisane jest w notach katalogowych. Moim zdaniem najważniejsze są dwa punkty dla prawidłowego zapisu danych, 1. sygnał potwierdzenia prawidłowo odebranego bajtu przez urządzenie (ack) odczytuje się w wysokim stanie 'SCL' , próba odczytu tego sygnału w stanie niskim skończy się zablokowaniem magistrali przez kontroler. Przyczyna jest prosta, ten sygnał tam nie występuje 2. czas trwania zapisu lub kasowania danych w urządzeniu po poleceniu (stop) jest opisany w nocie jako twr (Write Cycle Time). Więc jeśli nie odczekamy tego czasu po poleceniu (stop) i zaczniemy zapisywać następne dane poleceniem (start) to się nie dziwcie, że nic się nie zapisze XD...Przy odczycie danych można nie używać twr i podać na magistralę pełną prędkość (nie polecam). Sekwencję start, restart, stop można podać w dowolnym momencie. INSTR zapisujemy polecenia (działa jak sygnał WE) , port danych DO, DI , sygnał EN Zezwolenie na zapis instrukcji lub jak kto woli koniec zapisu odczytu. Linią INSTR możemy wybrać jedno z 8 poleceń: START, RESTART, STOP, DATAWR, DATARD, ACKWR, ACKRD, NACK. Program testowałem na układzie FPGA SPARTAN3A, działa 🙂 Testowałem EEPROM ATMEL o max taktowaniu do 1 MHz. Częstotliwość można zmienić zmieniając ilość bitów w liczniku Cnt. Przy obecnych ustawieniach licznik generuje ok 700kHz. Zwiększając liczbę bitów o 1 dzielimy częstotliwość przez 2. library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; use IEEE.NUMERIC_STD.ALL; entity I2C is Port ( Clk : in STD_LOGIC; -- 100Mhz Reset : in STD_LOGIC; -- aktywny 0 SCL : inout STD_LOGIC; SDA : inout STD_LOGIC; DO : in STD_LOGIC_VECTOR (7 downto 0); -- dane odebierane od urządzenia i2c DI : out STD_LOGIC_VECTOR (7 downto 0); -- dane zapisywane do urządzenia i2c EN : out STD_LOGIC; -- gotowość na odbiór instrukcji = '0' INSTR : in STD_LOGIC_VECTOR (4 downto 0) -- zakodowane instrukcje ); end I2C; architecture Behavioral of I2C is signal Cnt : std_logic_vector (4 downto 0); --dzielnik częstotliwości 100Mhz/32(5 bitów)/4(CntS) = 781 250 Hz signal Cnt5ms : std_logic_vector (19 downto 0); --licznik długości twr = 5ms signal CntD : std_logic_vector (2 downto 0); --licznik dla danych signal CntS : std_logic_vector (1 downto 0); --licznik dla {SCL = not (Scl(0) xor Scl(1)} signal Dout : std_logic_vector (7 downto 0); signal Din : std_logic_vector (7 downto 0); signal SDA_out : std_logic; signal SCL_out : std_logic; signal Clk1 : std_logic; signal SclXor : std_logic; type I2C_type is (idle, -- stan gotowości na przyjęcie instrukcji start, restart, dataout, datain, ackwr, ackrd, nack, stop, twr ); signal I2C : I2C_type := idle; begin process(Clk, i2c, Reset) begin if Reset = '0' then Cnt <= (others => '0'); Cnt5ms <= (others => '0'); elsif i2c = idle then Cnt <= (others => '0'); Cnt5ms <= (others => '0'); else if falling_edge (Clk) then if i2c = twr then Cnt5ms <= Cnt5ms + 1; else Cnt <= Cnt + 1; end if; end if; end if; end process; Clk1 <= Clk when i2c = idle else (Cnt(Cnt'left) or Cnt5ms(Cnt5ms'left)); --wybór sygnału zegarowego En <= '0' when i2c = idle else '1'; process(Clk1, Reset, i2c, SDA, CntS) begin if Reset = '0' then Din <= (others => '0'); else if falling_edge (Clk1) then if i2c = datain then if CntS = "11" then Din <= Din(6 downto 0) & SDA; end if; end if; end if; end if; end process; DI <= Din; process(Clk1, Reset, INSTR, DO, i2c) begin if Reset = '0' then Dout <= (others => '1'); else if falling_edge (Clk1) then if i2c = idle then if INSTR = 8 then --start Dout <= (others => '0'); elsif INSTR = 9 then --restart Dout <= "10000000"; elsif INSTR = 10 then --data out Dout <= DO; elsif INSTR = 11 then --data in Dout <= (others => '1'); elsif INSTR = 12 then --stop Dout <= "01111111"; elsif INSTR = 13 then --ackrd Dout <= (others => '0'); elsif INSTR = 14 then --no ack Dout <= (others => '1'); elsif INSTR = 15 then --ackwr Dout <= (others => '1'); end if; end if; end if; end if; end process; with CntD select SDA_out <= Dout(6) when "001", Dout(5) when "010", Dout(4) when "011", Dout(3) when "100", Dout(2) when "101", Dout(1) when "110", Dout(0) when "111", Dout(7) when others; process(Clk1, Reset, DO, SDA, INSTR) begin if Reset = '0' then CntS <= "11"; CntD <= "000"; i2c <= idle; else if falling_edge (Clk1) then case I2C is ----------------------------------------- when idle => if INSTR = 8 then --start CntS <= "00"; CntD <= "000"; i2c <= start; elsif INSTR = 9 then --restart CntS <= "10"; CntD <= "000"; i2c <= restart; elsif INSTR = 10 then --data out CntS <= "10"; CntD <= "000"; i2c <= dataout; elsif INSTR = 11 then --data in CntS <= "10"; CntD <= "000"; i2c <= datain; elsif INSTR = 12 then --stop CntS <= "10"; CntD <= "000"; i2c <= stop; elsif INSTR = 13 then --ackrd CntS <= "10"; CntD <= "000"; i2c <= ackrd; elsif INSTR = 14 then --no ack CntS <= "10"; CntD <= "000"; i2c <= nack; elsif INSTR = 15 then --ackwr CntS <= "10"; CntD <= "000"; i2c <= ackwr; else i2c <= idle; end if; ----------------------------------------- when start => if CntS = "01" then -- limit licznika dla SCL I2C <= idle; -- skok do idle bez zmiany licznika dla SCL else CntS <= CntS + 1; end if; ----------------------------------------- when restart => if CntD = "001" then I2C <= idle; elsif CntS = "11" then CntD <= CntD + 1; else CntS <= CntS + 1; end if; ----------------------------------------- when dataout => if CntD = "111" then if CntS = "01" then I2C <= idle; else CntS <= CntS + 1; end if; else if CntS = "01" then CntD <= CntD + 1; CntS <= CntS + 1; else CntS <= CntS + 1; end if; end if; ----------------------------------------- when datain => if CntD = "111" then if CntS = "01" then I2C <= idle; else CntS <= CntS + 1; end if; else if CntS = "01" then CntD <= CntD + 1; CntS <= CntS + 1; else CntS <= CntS + 1; end if; end if; ----------------------------------------- when ackwr => if CntS = "01" then i2c <= idle; else if CntS = "11" then if SDA = '0' then CntS <= CntS + 1; end if; else CntS <= CntS + 1; end if; end if; ----------------------------------------- when ackrd => if CntS = "01" then i2c <= idle; else CntS <= CntS + 1; end if; ----------------------------------------- when nack => if CntS = "01" then i2c <= idle; else CntS <= CntS + 1; end if; ----------------------------------------- when stop => if CntD = "001" then I2C <= twr; elsif CntS = "11" then CntD <= CntD + 1; else CntS <= CntS + 1; end if; ----------------------------------------- when twr => I2C <= idle; ----------------------------------------- end case; end if; end if; end process; SclXor <= CntS(0) xor CntS(1); SCL_out <= not SclXor; SDA <= '0' when SDA_out = '0' else 'Z'; SCL <= '0' when SCL_out = '0' else 'Z'; DI <= Din; end Behavioral; ciekawostka: napisałem dla sprawdzenia małą aplikację dla assembler do obsługi kontrolera zaimplementowanego w FPGA . Ułatwia testowanie układu 😉 zrzut ekranu : Pozdrawiam. Edytowano 10 stycznia przez kroszkanorber 2 Link do komentarza Share on other sites More sharing options...
Pomocna odpowiedź
Bądź aktywny - zaloguj się lub utwórz konto!
Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony
Utwórz konto w ~20 sekund!
Zarejestruj nowe konto, to proste!
Zarejestruj się »Zaloguj się
Posiadasz własne konto? Użyj go!
Zaloguj się »