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
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;
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
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.
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
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 :-)))
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
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
Ü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
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;
@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).
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
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 !
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.
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.
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?????