Skocz do zawartości

Przykładowy program I2C w VHDL


Pomocna odpowiedź

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 :

                                                                                        image.thumb.png.3c978e4a4f358f495be680012f683227.png

Pozdrawiam.

 

Edytowano przez kroszkanorber
  • Lubię! 2
Link do komentarza
Share on other sites

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ę »
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.