Sběrnice I2C
Zřejmě nejrozšířenější je používání sběrnice I2C. Hradlové pole pak funguje jako Master a budič jako Slave. Jedná se o dvouvodičovou sběrnici. Vodič SDA sériově přenáší data a po vodiči SCL jsou přenášeny taktovací hodiny. Všechna zařízení mají výstup typu otevřený kolektor. Aktivní signál je pak stáhnutí sběrnice na „0“.
Celý přenos začíná Startem – Start podmínkou, tj. postupným stáhnutím obou vodičů na „0“. Pak je v taktu signálu na SCL uskutečněn přenos 8 datových bitů. Po ukončení této části přenosu zůstává linka SDA uvolněná na „1“. Pokud chce příjemce dat potvrdit jejich přijetí, stáhne na „0“ tuto linku během devátého hodinového pulzu a vytvoří tak potvrzovací bit ACK. Celý přenos je ukončen Stop podmínkou, tj. postupným uvolněním obou vodičů.
Toto je však jen princip se kterým vystačíme u obvodů, které mají samostatně vyvedený vybavovací vstup CE a vývod R/W, který rozhoduje, zda probíhá zápis nebo čtení.
Sběrnice I2C nám však umožňuje adresovat všechna zařízení, které jsou na sběrnici připojena i určit zda, adresované zařízení má data číst nebo zapisovat.
Před odesláním dat je ještě posláno 8 bitů, kde nejnižší bit je R/W a sděluje tedy, zda bude následovat čtení nebo zápis. Další až 3 bity jsou proměnnou adresou konkrétního typu periferie. Bez dalšího tedy lze na sběrnici připojit až osm stejných obvodů. Zbývající minimálně čtyři bity jsou pevně dány dle typu obvodu. Tato část adresy je tedy pevně „zadrátována“. Existují však i obvody, které nemají žádný adresový bit a celá adresa je tedy pevná. Samozřejmě existují také situace, kdy nestačí adresa jako taková, ale musí být vyslána ještě subadresa. To je případ typický pro paměti. Tam je třeba nejdříve vyslat adresu příslušné paměti (tedy vlastního IO), pak subadresu příslušné paměťové buňky a na závěr vlastní data.
Pro úplnost ještě zbývá dodat, že dle hodinového kmitočtu rozlišujeme sběrnici na standard 100 kHz a rychlou 400 kHz.
Obvod SAA1064
Zajímavý budič pro toto použití nabízí firma Philips pod typovým označením SAA1064. Obvod umožňuje řídit staticky dvoumístný a dynamicky čtyřmístný displej.
Při dynamickém řízení je budič zapojen podle obrázku. Výhodou je minimum externích součástek. Jsou to prakticky jen dva tranzistory, které v páru přepínají anody displejů, kondenzátor blokující napájení a kondenzátor, který určuje frekvenci hodin. Proud segmenty je nastavitelný programově a nejsou tedy potřeba žádné omezující odpory. Aby výrobce ušetřil vývody, použil zajímavou adresaci. Velikost napětí na vývodu ADR rozhoduje o jedné ze čtyř adres na které obvod slyší. Základní, tedy 70H, je nastavena při uzemněném vývodu. Další tři možnosti, včetně dalších podrobností, najdete např. v datashetu http://www.grifo.com/PRESS/DOC/Philips/SAA1064.pdf.
Pro komunikaci s obvodem je třeba vědět, že po odeslání adresy s volbou čtení/zápis je zaslána subadresa, která v tomto případě znamená výběr mezi řídícím registrem a paměťovými registry pro pozici displeje 1 až 4. Obvod je však natolik chytrý, že pokud zašleme adresu, subadresu a pak několikerá data v rámci jednoho start/stop cyklu, budou data automaticky uložena na správné místo.
Programové řešení
Protože program je určen pro výuku, byl,stejně jako v minulém dílu, celý rozdělen na několik bloků a ty pak byly propojeny ve formě blokového schématu.
Celý obvod představuje komunikační Master. Na vstup clk je připojen oscilátor 50 MHz a na vstup digit(7-0) jsou připojeny přepínače. Výstupy jsou pak signály SDA a SCL.
Timer5us
Tento blok tvoří časovou základnu pro standardní rychlost komunikace, tj. 100kHz. Tato část programu je v souboru Timer5us.vhd.
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity Timer5us is
Port ( clk : in STD_LOGIC;
tim5us : out STD_LOGIC);
end Timer5us;
architecture Behavioral of Timer5us is
signal tim : STD_LOGIC_VECTOR (7 downto 0) := "00000000" ;
begin
process (clk) begin
if clk"event and clk = "1" then
tim <= tim + 1;
if tim = "11111010" then
tim5us <= "1";
tim <= (others => "0");
else
tim5us <= "0";
end if;
end if;
end process;
end Behavioral;
PosCount
Je osmi bitový čítač, který svým výstupem řídí přepínání následujícího bloku. V downloadu je to soubor PosCount.vhd.
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity PosCount is
Port ( tim : in STD_LOGIC;
clk : in STD_LOGIC;
pos : out STD_LOGIC_VECTOR (7 downto 0));
end PosCount;
architecture Behavioral of PosCount is
signal count: STD_LOGIC_VECTOR (7 downto 0):= "00000000";
begin
process (clk)
begin
if clk="1" and clk"event then
if tim = "1" then
count <= count + 1;
end if;
end if;
end process;
pos <= count;
end Behavioral;
MasterI2C
Modul představuje srdce celého řešení. Vstupem, kromě synchronního clk, jsou osmibitová brána přebírající stav předchozího bloku PosCount a vstup dat, která mají být odesílána. Výstupem jsou pak signály sda a scl. Ty jsou vytvářeny na principu logického posuvu. Každému stavu PosCount odpovídá jediný konkrétní stav obou výstupů. Načítáním čítače pak dochází k postupnému vysílání dat i taktovacích hodin na výstup. V tomto konkrétním případě se postupně vysílá
adresa slave zařízení a režim vysílání |
70h |
subadresa (adresa registru) |
00h |
režim displeje (dynamický a 9 mA na segment) |
37h |
data nejnižší pozice |
Sw(0-7) |
data druhé pozice |
40h |
data třetí pozice |
78h |
data čtvrté pozice |
6Ch |
Data druhé až čtvrté pozice je možné zvolit jakékoliv podle toho, jaké chcete zobrazovat znaky. Samozřejmě bit ACK je vždy v úrovni ‚1‘, ale zpracování tohoto bitu není řešeno. Příslušný soubor je MasterI2C.vhd
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity MasterI2C is
Port ( sda : out STD_LOGIC;
scl : out STD_LOGIC;
clk : in STD_LOGIC;
pos : in STD_LOGIC_VECTOR (7 downto 0);
pos1: in STD_LOGIC_VECTOR (7 downto 0));
end MasterI2C;
architecture Behavioral of MasterI2C is
signal int : STD_LOGIC_VECTOR (1 downto 0);
begin
process (pos, clk, pos1) begin
if clk"event and clk = "1" then
case (pos (7 downto 0)) is
when "00000010" => int <= "01"; --start
when "00000011" => int <= "00"; --adresa 70
when "00000100" => int <= "00";
when "00000101" => int <= "00"; --b7
when "00000110" => int <= "01";
when "00000111" => int <= "00";
when "00001000" => int <= "10"; --b6
when "00001001" => int <= "11";
when "00001010" => int <= "10";
when "00001011" => int <= "10"; --b5
when "00001100" => int <= "11";
when "00001101" => int <= "10";
when "00001110" => int <= "10"; --b4
when "00001111" => int <= "11";
when "00010000" => int <= "10";
when "00010001" => int <= "00"; --b3
when "00010010" => int <= "01";
when "00010011" => int <= "00";
when "00010100" => int <= "00"; --b2
when "00010101" => int <= "01";
when "00010110" => int <= "00";
when "00010111" => int <= "00"; --b1
when "00011000" => int <= "01";
when "00011001" => int <= "00";
when "00011010" => int <= "00"; --b0
when "00011011" => int <= "01";
when "00011100" => int <= "00";
when "00011101" => int <= "10"; --ACK
when "00011110" => int <= "11";
when "00011111" => int <= "10";
when "00100000" => int <= "00";
when "00100001" => int <= "00";
when "00100010" => int <= "00";
when "00100011" => int <= "00";
when "00100100" => int <= "00"; --subadresa 00
when "00100101" => int <= "00";
when "00100110" => int <= "00"; --b7
when "00100111" => int <= "01";
when "00101000" => int <= "00";
when "00101001" => int <= "00"; --b6
when "00101010" => int <= "01";
when "00101011" => int <= "00";
when "00101100" => int <= "00"; --b5
when "00101101" => int <= "01";
when "00101110" => int <= "00";
when "00101111" => int <= "00"; --b4
when "00110000" => int <= "01";
when "00110001" => int <= "00";
when "00110010" => int <= "00"; --b3
when "00110011" => int <= "01";
when "00110100" => int <= "00";
when "00110101" => int <= "00"; --b2
when "00110110" => int <= "01";
when "00110111" => int <= "00";
when "00111000" => int <= "00"; --b1
when "00111001" => int <= "01";
when "00111010" => int <= "00";
when "00111011" => int <= "00"; --b0
when "00111100" => int <= "01";
when "00111101" => int <= "00";
when "00111110" => int <= "10"; --ACK
when "00111111" => int <= "11";
when "01000000" => int <= "10";
when "01000001" => int <= "00";
when "01000010" => int <= "00";
when "01000011" => int <= "00";
when "01000100" => int <= "00";
when "01000101" => int <= "00"; --data 37
when "01000110" => int <= "00";
when "01000111" => int <= "00"; --b7
when "01001000" => int <= "01";
when "01001001" => int <= "00";
when "01001010" => int <= "00"; --b6
when "01001011" => int <= "01";
when "01001100" => int <= "00";
when "01001101" => int <= "10"; --b5
when "01001110" => int <= "11";
when "01001111" => int <= "10";
when "01010000" => int <= "10"; --b4
when "01010001" => int <= "11";
when "01010010" => int <= "10";
when "01010011" => int <= "00"; --b3
when "01010100" => int <= "01";
when "01010101" => int <= "00";
when "01010110" => int <= "10"; --b2
when "01010111" => int <= "11";
when "01011000" => int <= "10";
when "01011001" => int <= "10"; --b1
when "01011010" => int <= "11";
when "01011011" => int <= "10";
when "01011100" => int <= "10"; --b0
when "01011101" => int <= "11";
when "01011110" => int <= "10";
when "01011111" => int <= "10"; --ACK
when "01100000" => int <= "11";
when "01100001" => int <= "10";
when "01100010" => int <= "00"; --data
when "01100011" => int <= "00";
when "01100100" => int <= pos1(7) & "0"; --b7
when "01100101" => int <= pos1(7) & "1";
when "01100110" => int <= pos1(7) & "0";
when "01100111" => int <= pos1(6) & "0"; --b6
when "01101000" => int <= pos1(6) & "1";
when "01101001" => int <= pos1(6) & "0";
when "01101010" => int <= pos1(5) & "0"; --b5
when "01101011" => int <= pos1(5) & "1";
when "01101100" => int <= pos1(5) & "0";
when "01101101" => int <= pos1(4) & "0"; --b4
when "01101110" => int <= pos1(4) & "1";
when "01101111" => int <= pos1(4) & "0";
when "01110000" => int <= pos1(3) & "0"; --b3
when "01110001" => int <= pos1(3) & "1";
when "01110010" => int <= pos1(3) & "0";
when "01110011" => int <= pos1(2) & "0"; --b2
when "01110100" => int <= pos1(2) & "1";
when "01110101" => int <= pos1(2) & "0";
when "01110110" => int <= pos1(1) & "0"; --b1
when "01110111" => int <= pos1(1) & "1";
when "01111000" => int <= pos1(1) & "0";
when "01111001" => int <= pos1(0) & "0"; --b0
when "01111010" => int <= pos1(0) & "1";
when "01111011" => int <= pos1(0) & "0";
when "01111100" => int <= "10"; --ACK
when "01111101" => int <= "11";
when "01111110" => int <= "10";
when "01111111" => int <= "00"; --data
when "10000000" => int <= "00";
when "10000001" => int <= "00"; --b7
when "10000010" => int <= "01";
when "10000011" => int <= "00";
when "10000100" => int <= "10"; --b6
when "10000101" => int <= "11";
when "10000110" => int <= "10";
when "10000111" => int <= "00"; --b5
when "10001000" => int <= "01";
when "10001001" => int <= "00";
when "10001010" => int <= "00"; --b4
when "10001011" => int <= "01";
when "10001100" => int <= "00";
when "10001101" => int <= "00"; --b3
when "10001110" => int <= "01";
when "10001111" => int <= "00";
when "10010000" => int <= "00"; --b2
when "10010001" => int <= "01";
when "10010010" => int <= "00";
when "10010011" => int <= "00"; --b1
when "10010100" => int <= "01";
when "10010101" => int <= "00";
when "10010110" => int <= "00"; --b0
when "10010111" => int <= "01";
when "10011000" => int <= "00";
when "10011001" => int <= "10"; --ACK
when "10011010" => int <= "11";
when "10011011" => int <= "10";
when "10011100" => int <= "00"; --data
when "10011101" => int <= "00";
when "10011110" => int <= "00"; --b7
when "10011111" => int <= "01";
when "10100000" => int <= "00";
when "10100001" => int <= "10"; --b6
when "10100010" => int <= "11";
when "10100011" => int <= "10";
when "10100100" => int <= "10"; --b5
when "10100101" => int <= "11";
when "10100110" => int <= "10";
when "10100111" => int <= "10"; --b4
when "10101000" => int <= "11";
when "10101001" => int <= "10";
when "10101010" => int <= "10"; --b3
when "10101011" => int <= "11";
when "10101100" => int <= "10";
when "10101101" => int <= "00"; --b2
when "10101110" => int <= "01";
when "10101111" => int <= "00";
when "10110000" => int <= "00"; --b1
when "10110001" => int <= "01";
when "10110010" => int <= "00";
when "10110011" => int <= "00"; --b0
when "10110100" => int <= "01";
when "10110101" => int <= "00";
when "10110110" => int <= "10"; --ACK
when "10110111" => int <= "11";
when "10111000" => int <= "10";
when "10111001" => int <= "00"; --data
when "10111010" => int <= "00";
when "10111011" => int <= "10"; --b7
when "10111100" => int <= "11";
when "10111101" => int <= "10";
when "10111110" => int <= "10"; --b6
when "10111111" => int <= "11";
when "11000000" => int <= "10";
when "11000001" => int <= "10"; --b5
when "11000010" => int <= "11";
when "11000011" => int <= "10";
when "11000100" => int <= "00"; --b4
when "11000101" => int <= "01";
when "11000110" => int <= "00";
when "11000111" => int <= "10"; --b3
when "11001000" => int <= "11";
when "11001001" => int <= "10";
when "11001010" => int <= "10"; --b2
when "11001011" => int <= "11";
when "11001100" => int <= "10";
when "11001101" => int <= "00"; --b1
when "11001110" => int <= "01";
when "11001111" => int <= "00";
when "11010000" => int <= "10"; --b0
when "11010001" => int <= "11";
when "11010010" => int <= "10";
when "11010011" => int <= "10"; --ACK
when "11010100" => int <= "11";
when "11010101" => int <= "10";
when "11010110" => int <= "00";
when "11010111" => int <= "01"; --stop
when "11011000" => int <= "11";
when others => int <= "11";
end case;
end if;
scl <= int(0);
sda <= int(1);
end process;
end Behavioral;
BiOpenC
Výstup je řešen pomocí dvojice těchto bloků. Ty představují obousměrné porty s otevřeným kolektorem. Řízení směru je pomocí dvou vstupů. En – vybavení čtení z portu, Oe – vybavení výstupu. V tomto případě však vstupní funkce není využita a tak je pevně nastaven výstup na sběrnici. Příslušný soubor BiOpenC.vhd je k dispozici opět v downloadu.
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity BiOpenC is
Port ( q : inout STD_LOGIC;
vyst: out STD_LOGIC;
vst: in STD_LOGIC;
En : in STD_LOGIC;
Oe : in STD_LOGIC);
end BiOpenC;
architecture Behavioral of BiOpenC is
signal Qa: STD_LOGIC;
signal Qb: STD_LOGIC;
begin
Load: process (En, q) begin --čtení portu
if (En ="1") then Qa <= q;
else Qa <= "0";
end if;
end process Load;
Output: process (Oe, Qb) begin --výstup na port
if Oe = "1" then
if Qb = "0" then q <= "0";
else q <= "Z";
end if;
end if;
end process Output;
vystup: process (Qa) begin
vyst <= Qa;
end process vystup;
vstup: process (vst) begin
Qb <= vst;
end process vstup;
end Behavioral;
Závěr
Navržené řešení ukazuje velmi názorně komunikaci po sběrnici I2C. Simulace souboru MasterI2C.vhd pomohla řadě studentů pochopit její princip. V downloadu je také ucf soubor potřebný při fyzické zkoušce programu. V tomto souboru pak najdete, na které piny konektoru jsou výstupy SDA a ACL vyvedeny.
jiri.kral@ roznovskastredni.cz
Downloads&Odkazy
- První díl seriálu - Hrátky s hradlovými poli - 1
- Druhý díl seriálu - Hrátky s hradlovými poli - 2
- Soubory ke stažení - Hratky_3.zip
- Domovské stránky - http://www.grifo.com/PRESS/DOC/Philips/SAA1064.pdf
- Domovské stránky Xilinx - http://www.xilinx.com/products/spartan3/boards/SP3kitSS.pdf
- Domovské stránky Digilent - http://www.digilentinc.com