Forum: FPGA, VHDL & Co. i2c slave controller


von tobias oddoy (Gast)


Lesenswert?

Ich weiss, dass dieses Thema schon in einem Thread behandelt wurde, aber
die Lösungen waren etwas unbefriedigend für mich, da für mich zu
kompliziertes Interface .
Ich versuche einen einfachen i2c Slave controller für ein fpga zu
programmieren. Dieser soll eigentlich nur dazu dienen Bytes zu
empfangen. Auf Opencores gibt es zwar einen I2C Master
controller, aber der nützt mir recht wenig, da halt Master Controller
und nicht Slave.


Das interface stelle ich mir so vor:
generic :
   i2c_slave_adress
port :
sys_clock  : in
scl_in     : in
sda_in     : in

Data_ready       : out              -- Daten vorhanden
Data_read_clock  : in               -- Lesetakt für daten
Data_read        : in               -- Lesen ausführen
Data             : out              -- Payload

Den ganzen std_logic Kram habe ich mal weggelassen. Hat jemand schon
mal sowas gemacht (evtl. CPLD als Porterweiterung) das auch
funktioniert hat.

Den IP-Core will ich später mal benutzen, um mir debug informationen
von meinem Cypress EZ-USB expansion modul zu holen, da der Hersteller
blöderweise vergessen hat die RXD und TXD Leitungen zu verdraten. Somit
habe ich keinen Zugang zur UART auf dem USB Controller, was sich beim
debuggen echt dämlich macht.

Danke an jeden, der dazu etwas schreiben kann oder zur Hilfe beiträgt.
Schon fertiger VHDL Code ist natürlich sehr willkommen. :D, bitte kein

freakiger Verilog Code, ich möchte das auch in etwa verstehen können
was da passiert, wenn ich Anpassungen am Interface vornehmen muss.

G. Tobias

von Kest (Gast)


Lesenswert?

Ok, hier unten. In der Simulation (zusammen mit dem Master von
Opencores) funktioniert alles einwandfrei. Ich übernehme aber keine
Haftung - war zu schnell reingehackt. Hoffe, Du blickst da durch, wenn
nicht, dann frag', ich versuche mich zu erinnern ;-)

Kest


  library ieee;
    use ieee.std_logic_1164.all;
    use ieee.std_logic_arith.all;
    use IEEE.STD_LOGIC_UNSIGNED.all;
--
------------------------------------------------------------------------ 
-

    entity i2c_core_slave is
        port (
            clk        : in    std_logic;
            nReset     : in    std_logic;
            Dout       : out   std_logic_vector(7 downto 0);
            data_valid : out   std_logic;
            SCL        : in    std_logic;
            SDA        : inout std_logic
            );
    end entity i2c_core_slave;
--
------------------------------------------------------------------------ 
-
    architecture behav of i2c_core_slave is

        type   states is (s_idle, s_start, s_read_a, s_ack);
        signal state, next_state : states;

        signal oSCL, oSDA                          : std_logic
          := '0';
        signal SDAt, SCLb                          : std_logic;
        signal sda_pad_o, sda_pad_i, sda_padoen_oe : std_logic;
        signal rx_reg                              : std_logic_vector(7
downto 0);
        signal rx_done                             : std_logic;
        signal int_reg                             : std_logic_vector(7
downto 0);
        signal stop_sy                             : std_logic
          := '0';
        signal count                               : std_logic_vector(2
downto 0) := "000";
--
------------------------------------------------------------------------ 
-
    begin

       -- SDA       <= sda_pad_o when (sda_padoen_oe = '0') else
'Z';
        sda_pad_i <= SDA;
        SDAt      <= '0'       when (SDA = '0')           else
'1';
        SCLb      <= '0'       when (SCL = '0')           else
'1';

--
------------------------------------------------------------------------ 
-
-- Statemachine für i2c-Bus (slave)
--
------------------------------------------------------------------------ 
-
        process (nReset, clk)--, state, SDA, stop_sy, count)
        begin
            if nReset = '0' then
                rx_done       <= '0';
                sda_pad_o     <= '0';
                sda_padoen_oe <= '0';
                data_valid    <= '0';
                next_state    <= s_idle;
                int_reg       <= (others => 'Z');
                Dout          <= (others => 'Z');
            elsif clk'event and clk='1' then


                case state is
                    when s_idle =>
                        data_valid <= '0';
                        rx_done       <= '0';
                        sda_padoen_oe <= '0';

                        if SDA = '0' then
                           -- sda_padoen_oe <= '0';
                            next_state    <= s_start;
                        else
                            next_state <= s_idle;
                           -- sda_padoen_oe <= '0';

                        end if;

                    when s_start =>
                        data_valid    <= '0';
                        rx_done       <= '0';
                        sda_padoen_oe <= '1';

                        if stop_sy = '0' then
                            next_state <= s_read_a;
                        else
                            next_state <= s_idle;
                        end if;

                    when s_read_a =>    -- hier SDA übernehmen und
schiften
                        data_valid <= '0';
                        rx_done       <= '0';


                        if (count = "000" and stop_sy='0') then
                            next_state <= s_ack;
                        else
                            next_state <= s_read_a;
                        end if;

                    when s_ack => -- acknowledge senden (SDA auf 0)
                        data_valid    <= '1';
                        rx_done       <= '1';

                        sda_pad_o     <= '0';
                        sda_padoen_oe <= '0';
                        int_reg       <= rx_reg;
                        Dout          <= rx_reg;
                        next_state    <= s_start;  -- idle
                end case;
            end if;
        end process;

--
------------------------------------------------------------------------ 
-
-- stop erkennen, (a)synchron
--
------------------------------------------------------------------------ 
-
        process (nReset, clk)--, SCLb, SDAt, state, oSDA)
        begin
            if nReset = '0' then
                stop_sy <= '0';
                oSDA    <= '1';
                elsif clk'event and clk='1' then

                if state = s_idle then
                    stop_sy <= '0';
                end if;

                if (oSDA = '0' and SDAt = '1') then  -- also
steigende Flanke
                    oSDA <= '1';
                    if SCLb = '1' then  -- und SCL hoch, => stop
Signal
                        stop_sy <= '1';
                    else
                        stop_sy <= '0';
                    end if;
                elsif (oSDA = '1' and SDAt = '0') then  --
vorsichtshalber auch die fallende erkennen
                    oSDA <= '0';
                end if;
            end if;
        end process;

--
------------------------------------------------------------------------ 
-
-- states weiterschalten
-- auf die fallende SCL-Flanke triggern
-- wenn genug CLKs vorhanden, kann man auch SCL als CLK nehmen, erstmal
aber nicht
--
--
------------------------------------------------------------------------ 
-
        process (clk, nReset,     state, stop_sy, oSCL, SCLb, rx_done)
        begin
            if nReset = '0' then
                state  <= s_idle;
                oSCL   <= '1';
                count  <= "000";
                rx_reg <= (others => 'Z');
            elsif (clk'event and clk = '1') then  -- Register

                if stop_sy = '1' then
                    state <= s_idle;
                end if;

                if state = s_idle then
                    count <= "000";
                end if;

                if (oSCL = '1' and SCLb = '0' ) then  -- fallende
Flanke? -- and stop_sy='0'
                    state <= next_state;                -- nächster
Zustand
                    oSCL  <= '0';       -- aktuellen Wert merken
                elsif (oSCL = '0' and SCLb = '1') then  --
steigende Flanke?
                    if (state = s_idle) or (state = s_ack) then  --
Zähler reseten
                        count <= "000";
                    else
                        if rx_done = '0' then
                            rx_reg <= rx_reg(6 downto 0) & SDAt;  --
shiften
                            count  <= count+"001";      -- 8 Bits
abzählen
                        end if;
                    end if;
                    oSCL <= '1';
                end if;
            end if;
        end process;
--
------------------------------------------------------------------------ 
-
    end architecture behav;

von FPGA-User (Gast)


Lesenswert?

@Kest

was ist mit der Slave-Adresse, kann man die einstellen ?

von Kest (Gast)


Lesenswert?

am Ausgang hast Du einfach die Daten, die ankommen. (Serial auf
Parallel), das war's auch schon. Wenn die richtige Adresse angekommen
ist, dann... tja, ist alles rudimentär, ich weis ;-) Aber immerhin :-)

Kest

von FPGA-User (Gast)


Lesenswert?

also mehr ein "Sniffer", ein Slave müsste ja mit seinem
Acknowledge entsprechend reagieren, abhängig davon, ob
er angesprochen wurde oder nicht.

Besteht allgemeines Interesse an einem Slave in VHDL, der sowohl
beschrieben als auch gelesen werden kann und frei
adressierbar ist + im Sniffer-Mode arbeiten kann ?

Habe gerade ein Projekt, wo sich sowas ganz gut machen
würde, aber ich bräuchte ein paar Tester, die sich den
Code anschauen und simulieren + Bug-Reports liefern.

von Kest (Gast)


Lesenswert?

WAs dazu kommt ist einfach mal eine kleine (wirklich sehr kleine)
Statemachine, die läuft eben los, wenn die Adresse erkannt wird. Ich
wollte sie blos nicht jetzt aus einem Projekt rausreißen, weil man
durcheinander kommt...

Aber Sniffer ist ja schon fast zu 90% ein I2C slave ;-)

Kommt, Leute, macht doch auch was :-)
Übrigens, ein Tipp, diese "kleine" Statemachine, ist bei Opencores,
bei dem I2C Master mitdabei (da wird irgendwie ein Mixrocontroller
emuliert oder so... etwas suchen müsst ihr schon ;-))

Kest

von FPGA-User (Gast)


Lesenswert?

also der Multimaster bei Opencores hat mir nicht so
gefallen, war auch ganz schön umfangreich...
Teile des Slaves habe ich schon fertig, wie gesagt,
man kann auch von dem Slave lesen und nach jedem
Transfer gibts einen Status, also z.B. TX_UNDERRUN,
RX_OVERFLOW, NO_ACK ...
Aber einfach so zum kopieren und abheften gibts den nich :-)))

von tobias oddoy (Gast)


Lesenswert?

Interesse an einem i2c Slave Controller würde ich dann mal anmelden, am
besten mit Richtungsumschaltung, damit man den auch ins FPGA packen
kann, bzw. einen IO Buffer für die FPGA Pins benutzen kann.

sprich
sda_in   : in
sda_out  : out
sda_read : out -- wenn '1',dann sda_in lesen wenn '0' dann sda out
schreiben.

verdrahtung im fpga top level würde dann so aussehen
IOBUF port map (O => sda_in,
               IO => FPGA_pin_sda,
                I => sda_out;
                T => sda_read
              );
Würde mich als Tester und zur Evaluierung anbieten, da ich das sowieso
brauchen werde, bzw. das zum laufen kriegen muss.

Gruß Tobias

von John-Eric (Gast)


Lesenswert?

hallo
würde mich auch dafür interressieren.
wüsste bloß nicht wie ich das mit dem ack hinbekommen sollte.
da ich mich auch bloß privat damit beschäfftige.
wäre warscheinlich ganz hilfreich zum verständnis.
das i2c protokoll habe ich mir schon angeschaut und auch verstanden.
hätte auch nicht das problem mit zu versuchen den zu erstellen.
kann mich nur weiter bringen.
bin schon super glücklich das meine selbstprogrammierte funkuhr super
läuft. die musste ich ja schließlich ganze 4mal umprogrammieren
(grins)
erst alles asynchron, dann hier das forum gefunden und dan musste ich
das noch alles synchron schreiben.
das forum hier hat mir schon sehr geholfen.
mfg

von Kest (Gast)


Lesenswert?

Übrigens, das Code, was ich oben gepostet habe, ist schon mehr als ein
Sniffer - ACK bekommt man jeweils nach jedem empfangenen Byte zurück
(ist mir gerade eingefallen ;-))

Ich bin der Meinung, dass solche kleine Sachen man selber reinhacken
kann. Dabei lernt man sehr schnell dazu - bzw. welche Fehler dann
auftauchen. Klar, wenn man nur fertigen SAchen nimmt, ist schon
praktisch, aber na ja... jedem das seine. Für mich wäre sowas
interessant, wenn man einfach alles anschaut, wie das andere gemacht
haben.

Ich, für mein Teil, optimiere nur selten - programmiert, getestet,
synthetisiser, getestet, geändert, synthetisiert, getestet - okay. Wenn
da irgendwelche Latches sind oder so, beachte ich nicht. Nur, wenn ich
dann Zeit habe - das ist aber oft nicht der Fall :-/
Ich gucke also sehr gerne mal die Sachen von Dir an, FPGA_User :-)

Kest

Kest

von high_speed (Gast)


Lesenswert?

Fremden Code zu lesen ist meist nicht sehr einfach.
Da ist es meist viel einfacher es selber zu schreiben.
Ich habe erst einmal den Code von Kest ein bisschen aufgeräumt.

Hier jetzt ein paar Tipps um die Lesbarkeit des Codes zu verbessern:

1. Bei der Signaldeklaration für jedes Signal oder zum mindestens für
gleiche Signale eine Zeile verwenden.
2. Bei Signalen, die nicht auf die Verwendung schließen lassen, noch
einen Kommentar anfügen.

Eine Vereinfachung habe ich auch noch gefunden:
Neu:   stop_sy <= SCLb;     -- und SCL hoch, => stop Signal

Alt:  if SCLb = '1' then
         stop_sy <= '1';
      else
         stop_sy <= '0';
      end if;
1
library ieee;
2
use ieee.std_logic_1164.all;
3
use ieee.std_logic_arith.all;
4
use IEEE.STD_LOGIC_UNSIGNED.all;
5
---------------------------------------------------------------------------
6
7
entity i2c_core_slave is
8
   port (
9
         clk          : in    std_logic;
10
         nReset       : in    std_logic;
11
         Dout         : out   std_logic_vector(7 downto 0);
12
         data_valid   : out   std_logic;
13
         SCL          : in    std_logic;
14
         SDA          : inout std_logic
15
        );
16
end entity i2c_core_slave;
17
18
---------------------------------------------------------------------------
19
20
architecture behav of i2c_core_slave is
21
22
   type   states is (s_idle, s_start, s_read_a, s_ack);
23
   signal state, next_state : states;
24
25
   signal oSCL              : std_logic  := '0';  
26
   signal oSDA              : std_logic  := '0'; 
27
   signal SCLb              : std_logic;
28
   signal SDAt              : std_logic;
29
30
   signal sda_pad_i         : std_logic;
31
   signal sda_pad_o         : std_logic;
32
   signal sda_padoen_oe     : std_logic;
33
34
   signal rx_reg            : std_logic_vector(7 downto 0);
35
   signal rx_done           : std_logic;
36
   signal int_reg           : std_logic_vector(7 downto 0);
37
   signal stop_sy           : std_logic := '0';
38
   signal count             : std_logic_vector(2 downto 0) := "000";
39
40
---------------------------------------------------------------------------
41
  
42
begin
43
44
   -- SDA        <= sda_pad_o when (sda_padoen_oe = '0') else 'Z';
45
   sda_pad_i  <= SDA;
46
   SDAt       <= '0'       when (SDA = '0')           else '1';
47
   SCLb       <= '0'       when (SCL = '0')           else '1';
48
49
---------------------------------------------------------------------------
50
-- Statemachine für i2c-Bus (slave)
51
---------------------------------------------------------------------------
52
53
   process (nReset, clk)  --, state, SDA, stop_sy, count)
54
   begin
55
      if nReset = '0' then
56
         rx_done       <= '0';
57
         sda_pad_o     <= '0';
58
         sda_padoen_oe <= '0';
59
         data_valid    <= '0';
60
         next_state    <= s_idle;
61
         int_reg       <= (others => 'Z');
62
         Dout          <= (others => 'Z');
63
      elsif clk'event and clk='1' then
64
65
         case state is
66
67
         when s_idle =>
68
            data_valid    <= '0';
69
            rx_done       <= '0';
70
            sda_padoen_oe <= '0';                        
71
                                   
72
            if SDA = '0' then
73
               -- sda_padoen_oe <= '0';
74
               next_state    <= s_start;
75
            else
76
               next_state <= s_idle;
77
               -- sda_padoen_oe <= '0';                  
78
            end if;
79
80
         when s_start =>
81
            data_valid    <= '0';
82
            rx_done       <= '0';
83
            sda_padoen_oe <= '1';
84
85
            if stop_sy = '0' then
86
               next_state <= s_read_a;
87
            else
88
               next_state <= s_idle;
89
            end if;
90
91
         when s_read_a =>         -- hier SDA übernehmen und schiften
92
            data_valid  <= '0';
93
            rx_done     <= '0';                        
94
95
            if (count = "000" and stop_sy='0') then 
96
               next_state <= s_ack;
97
            else
98
               next_state <= s_read_a;
99
            end if;
100
101
         when s_ack =>            -- acknowledge senden (SDA auf 0)
102
            data_valid    <= '1';
103
            rx_done       <= '1';                        
104
            sda_pad_o     <= '0';
105
            sda_padoen_oe <= '0';
106
            int_reg       <= rx_reg;
107
            Dout          <= rx_reg;
108
            next_state    <= s_start;  -- idle
109
110
         end case;
111
112
      end if;
113
114
   end process;
115
116
---------------------------------------------------------------------------
117
-- stop erkennen, (a)synchron
118
---------------------------------------------------------------------------
119
120
   process (nReset, clk)
121
   begin
122
      if nReset = '0' then
123
         stop_sy <= '0';
124
         oSDA    <= '1';
125
      elsif clk'event and clk='1' then
126
 
127
         if state = s_idle then
128
            stop_sy <= '0';                      --??? Reihenfolge
129
         end if;
130
131
         if (oSDA = '0' and SDAt = '1') then     -- also steigende
132
Flanke
133
            oSDA    <= '1';
134
            stop_sy <= SCLb;                     -- und SCL hoch, =>
135
stop Signal
136
         elsif (oSDA = '1' and SDAt = '0') then  -- vorsichtshalber
137
auch die fallende erkennen
138
            oSDA    <= '0';
139
         end if;
140
      end if;
141
   end process;
142
143
---------------------------------------------------------------------------
144
-- states weiterschalten
145
-- auf die fallende SCL-Flanke triggern
146
-- wenn genug CLKs vorhanden, kann man auch SCL als CLK nehmen, erstmal
147
aber nicht
148
---------------------------------------------------------------------------
149
150
   process (clk, nReset)
151
   begin
152
      if nReset = '0' then
153
         state  <= s_idle;
154
         oSCL   <= '1';
155
         count  <= "000";
156
         rx_reg <= (others => 'Z');
157
      elsif (clk'event and clk = '1') then    -- Register
158
159
         if stop_sy = '1' then
160
            state <= s_idle;
161
         end if;
162
163
         if state = s_idle then
164
            count <= "000";
165
         end if;
166
167
         if (oSCL = '1' and SCLb = '0' ) then    -- fallende
168
Flanke? -- and stop_sy='0'
169
            state <= next_state;                 -- nächster Zustand
170
            oSCL  <= '0';                        -- aktuellen Wert
171
merken
172
         elsif (oSCL = '0' and SCLb = '1') then  -- steigende
173
Flanke?
174
            if (state = s_idle) or (state = s_ack) then  -- Zähler
175
reseten
176
                count <= "000";
177
            else
178
               if rx_done = '0' then
179
                  rx_reg <= rx_reg(6 downto 0) & SDAt;  -- shiften
180
                  count  <= count+"001";         -- 8 Bits abzählen
181
               end if;
182
            end if;
183
            oSCL <= '1';
184
         end if;
185
      end if;
186
   end process;
187
188
---------------------------------------------------------------------------
189
190
end architecture behav;

MfG
Holger

von FPGA-User (Gast)


Lesenswert?

@Tobias

welchen Vorteil siehst Du in der Auftrennung von SDA in SDA_in
und SDA_out ? Die FPGA-Pins sind doch bidirektional, die
Synthese erkennt automatisch, wie der Input-Buffer und der
Output-Tristate-Buffer an den Core angeschlossen werden müssen.
von außen kommen ja auch nur 2 Leitungen, SDA und SCL.

Egal, werde bis Freitag mal den Slave-Core präsentieren,
geplant ist TX und RX doppelt gepuffert, also man kann schon
das nächste Byte einschreiben während das vorherige über I2C
rausgeht, FIFOs sollten sich ohne große Kopfstände anschließen
lassen (in der 1 Version evt. noch nicht), es soll keine
FPGA-spezifischen Elemente geben, Transfer bricht ab, wenn
kein ACK vom Master kommt (bei Read vom Slave).

von Kest (Gast)


Lesenswert?

Die Auftrennung finde ich nicht mal so dumm :-o

Ich kann nur aus Erfahrung sagen, dass bidirektionale Pins oft stress
machen (sogar auch in der Simulation). Da sind dann plötzlich
kurzschlüsse oder was weis ich. Hab mal was gemacht, und dann mit dem
Opencores-Core von i2c getestet und es klapte nicht (in der
Simulation). In der Hardware hat man dann entsprechend Pulldown/Up
Widerstände und da hat es schon funktioniert. Na ja... wir nähern uns
langsam dem Wishbone ;-))

Kest

von tobias oddoy (Gast)


Lesenswert?

Hast ja recht mit den Bidirektionalen Pins, habe halt festgestellt, wenn
man so eine Auftrennung vornimmt, lassen sich leichter Fehler erkennen,
weil man besser erkennen kann, woher die Signale kommen. Ansonsten
sieht man bei der Simulation nur ein 'X' und man weiss nicht, woher
der Kurzschluss kommt. Außerdem muss man ja nicht die Hersteller
Synthese Bibliotheken verwenden, sondern kann den IO Buffer auch "per
hand" erstellen und ist dann völlig plattformunabhängig.
Hauptsache es funktioniert

process .......
  if T = '1' then
      IO <= 'Z';
      O  <= IO ;
  else
      I  <= O;
      O  <= IO;
  end if;
end process;

Übrigens Danke für deine Hilfe !

von FPGA-User (Gast)


Lesenswert?

meinen Slave habe ich in der Codesammlung abgelegt,
SDA ist erstmal nicht aufgetrennt,
hab versucht, alles so verständlich wie möglich zu schreiben,
für den einen oder anderen sind bestimmt ein paar neue
VHDL-Features zu entdecken.
bitte mal damit rumspielen, wer Zeit hat.

von Andreas Müller (Gast)


Lesenswert?

Hi Jungs,

ich hab mir mal das I2C Projekt auf Opencore angeschaut und find es
recht kompliziert. Hat jemand ein besseres Projekt was er mir mal geben
kann? Hab versucht es komplett mit Automaten zu realisieren und
irgendwie ist da noch der Wurm drin. Wäre euch echt dankbar.

von anfänger in vhdl (Gast)


Lesenswert?

was bedeutet oder was beschreiben genau die signale sda-pad-i und sda 
pad-o und sda-padoen-oe und int-reg

von Tommy T. (tommy776)


Lesenswert?

hmm,immer noch niemand geantwortet! :(

aber mal eine andere frage:
wenn ich register des sensors auslese (register 0x0C bis 0x0F),die im 
default-zustand mit werten belegt sind, dann steht nach dem senden des 
repeated-start und der anschließenden Registeradresse sowie den 4 oben 
genannten adressen immer ein ACK.das sehe ich auf dem oszi.
wenn ich nun die achsenregister auslese fehlt das ACK nach dem 
repeated-start(+sensoradresse) und nach dem lesen des jeweiligen 
LOW-byte der x-bzw. y-achse!
im avr-studio steht aber im statusregister nach 0x10 (steht für repeated 
start) als nächstes der status (0x40) -> slave has been transmitted,ACK 
has been received!

kann das sein?????

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.