Jste zde

Hrátky s hradlovými poli - 3.

Minulý díl ukazoval řízení sedmi segmentového displeje. Popsané řešení je však nepoužitelné, pokud je displej fyzicky vzdálený od řídícího obvodu. V takovém případě je pak výhodné použít samostatný budič, který z řídícího obvodu získává data po sběrnici. V závěru minulého dílu jsem proto možné řešení slíbil.

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.

library IEEE;
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.

library IEEE;
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

library IEEE;
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.

library IEEE;
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.

Ing. Jiří Král
jiri.kral@ roznovskastredni.cz

Downloads&Odkazy

Hodnocení článku: