Popularny post kroszkanorber Napisano Styczeń 10, 2025 Popularny post Udostępnij Napisano Styczeń 10, 2025 (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 Styczeń 10, 2025 przez kroszkanorber 3
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ę »