Seltsamer Fehler beim Interfacing with Shift Register (CPLD) via SPI

Ich habe ein 8-Bit Parallel in Serial Out (PISO) Schieberegister in VHDL auf meinem Max V CPLD implementiert. Ich verwende SPI als Schnittstelle zum CPLD mit meinem AVR. Die Schaltung funktioniert aber nur teilweise. Angenommen, ich habe einen Eingang, der nur ein gesetztes Bit enthält, dh 00010000 oder 10000000 usw. In diesem Fall liest der AVR die Daten korrekt ein.

Wenn die Eingabe in das PISO-Register jedoch 10100000 ist, liest der AVR die Daten als 11100000 ein. Dies spiegelt sich auch in meinem Oszilloskop wider. Zur Veranschaulichung habe ich das Bild angehängt.

Geben Sie hier die Bildbeschreibung ein

Die obere Wellenform ist die serielle Ausgabe von meinem Schieberegister und die untere Wellenform ist die Uhr. Sollte der serielle Ausgang nicht im 2. Taktzyklus 0 werden?

Wenn die Daten 10000001 sind, werden sie als 10000011 eingelesen. Was könnte dies verursachen? Da ich dachte, es könnte mein VHDL-Code sein, habe ich sogar die Codes anderer Leute aus dem Internet kopiert, um zu sehen, ob es funktioniert, aber leider bekomme ich die gleichen Ergebnisse.

Das Folgende ist der Code für meinen AVR

int main(void)
{
DDRB = (1 << DD_MOSI) | (1 << DD_SCK) | (1 << 2) | (1 << 0);

SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0)  | (1 << SPR1);
DDRD = 0xFF;
PORTD = 0x00;

char datain;

while(1)
{
    PORTB = 1; // This loads the shift register.
    _delay_ms(10);
    PORTB = 0x00;
    _delay_ms(10);

    SPDR = 0b01011101;

    while(!(SPSR & (1 << SPIF)));
    datain = SPDR;


    _delay_ms(10);

    PORTD = datain;
}


return 1;
}

Und das ist der VHDL-Code, den ich verwende. Beachten Sie, dass ich die Eingabe absichtlich weggelassen habe. Ich codiere den Eingang fest, da ich dann nicht mit zusätzlichen Drähten herumspielen muss. Was ich gehofft hatte, würde weniger Raum für Fehler bieten.

library ieee; 
use ieee.std_logic_1164.all; 
entity PISO is 
port(C, CS_N : in std_logic; 
  --D   : in std_logic_vector(7 downto 0); 
   SO  : out std_logic); 
end PISO; 
architecture archi of PISO is 
signal tmp: std_logic_vector(7 downto 0); 
begin  
process (C, CS_N) 
   begin 
     if CS_N = '1' then 
         tmp <= "10000001"; 
     elsif rising_edge(C) then 
         tmp <= tmp(tmp'high - 1 downto tmp'low) & '0'; 
     end if; 
end process; 
SO <= tmp(7); 
end archi;

Beachten Sie, dass VHDL/CPLDs für mich ziemlich neu sind und ich immer wieder auf etwas in Bezug auf Timings stoße und wie kritisch sie sind. Ich habe versucht, sie zu lesen, aber ich scheine es nicht ganz hinbekommen zu haben. Ich verstehe Gate-Verzögerungen usw., aber wie soll ich das CPLD so einrichten, dass dies kein Problem darstellt? Meine Uhr geht nicht sehr schnell. Ich betreibe es mit der niedrigstmöglichen Frequenz, dh 62,5 kHz. Könnte dieser Fehler etwas mit Steckbrettern zu tun haben? Mir sind die Photoresist-Kupferkaschierungen ausgegangen, daher kann ich noch keine Leiterplatte herstellen. Der Max V befindet sich auf einem Entwicklungsboard.

Ich freue mich über jede Art von Hinweisen zur Lösung dieses Problems. Ich habe es in den letzten 8 Stunden oder so vergeblich versucht.

HINZUGEFÜGTE INFO: Als ich das CPLD programmierte, testete ich es zunächst über die Tasten auf meiner Entwicklungsplatine. Das zeigte überhaupt keine Probleme und das Register spuckte Bits seriell gut aus. Nicht nur das, ich habe es sogar über den AVR getaktet. Das hat auch super funktioniert. Erst nachdem ich bestätigt hatte, dass es in Ordnung war, begann ich mit der Arbeit an der SPI-Schnittstelle, bei der die Dinge schief liefen.


Neue Wellenformen:

Ok, los geht's. Bei allen Screenshots war die Seriennummer gleich (10000001). Ich habe eine leichte Verbesserung vorgenommen, sodass 10000001 nicht mehr als 10000011 ausgegeben wird, sondern 10000010 ist. Als ich vorher debuggte, habe ich versehentlich das CPHA-Bit auf 1 gesetzt. Als ich es auf 0 zurücksetzte, waren die Daten 100000010 anstelle von 10000011 Natürlich immer noch nicht richtig. Hier sind die Wellenformen aus dem Oszilloskop.

Geben Sie hier die Bildbeschreibung ein

Obere Wellenform: Serial Out

Untere Wellenform: Uhr. Ausgelöst bei steigender Flanke.

Geben Sie hier die Bildbeschreibung ein

Obere Wellenform: Chip Select (Active Low)

Untere Wellenform: Uhr. Ausgelöst bei steigender Flanke.

Geben Sie hier die Bildbeschreibung ein

Obere Wellenform: Chip Select

Untere Wellenform: Uhr. Ausgelöst bei fallender Flanke.

Geben Sie hier die Bildbeschreibung ein

Obere Wellenform: Serial Out.

Untere Wellenform: Uhr. Ausgelöst bei fallender Flanke.

EDIT: Vergessen hinzuzufügen, das ist jetzt mein bereinigter AVR-Code.

while(1)
{
    bit_clear(PORTB,BIT(CS)); // Low Chip Select
    SPDR = 0xFF;
    while(!(SPSR & (1 << SPIF)));
    datain = SPDR;
    PORTD = datain;
    bit_set(PORTB,BIT(CS)); // High Chip select
    _delay_us(100); // Keep high for 100us. This made it easier to see it on o-scope.
}
Da Sie gesagt haben, dass es zuvor funktioniert hat, frage ich mich, an welcher Kante der AVR SPI-Daten taktet? Wenn der AVR Daten an der steigenden Flanke taktet, müssen Sie sie an der fallenden Flanke ändern. Das Ändern während des Lesens wird solche wackeligen Sachen machen.
All dieses Gerede darüber, was der AVR sieht, ist ein Ablenkungsmanöver, das ablenkt und Zeit verschwendet. Wenn die Daten auf dem Umfang falsch sind, dann hat eindeutig der Absender und nicht der Empfänger das Problem. Vergessen Sie daher den AVR und korrigieren Sie Ihren CPLD-Code.

Antworten (1)

Schauen wir uns Ihren VHDL-Code an. Bitte denken Sie daran, dass ich nicht versuche, kritisch oder schroff zu sein – ich versuche nur, Sie schnell auf den neuesten Stand zu bringen. Ihr Code ist derzeit dieser:

process (C, ALOAD) 
begin 
  if (ALOAD='1') then 
    tmp <= "10000001"; 
  elsif (C'event and C='1') then 
    tmp <= tmp(6 downto 0) & '0'; 
    SO <= tmp(7);  
  end if;     
end process; 

Sollte aber so aussehen:

process (C, ALOAD)
begin
  if ALOAD='1' then 
    tmp <= "10000001";
  elsif rising_edge(C) then
    tmp <= tmp(tmp'high-1 downto tmp'low) & "0";  -- '0' and "0" are equivalent in this context
  end if;
end process;

SO <= tmp(tmp'high);

Die Änderungen, die ich vorgenommen habe, sind sicherlich geringfügig, aber wichtig.

Das erste ist meine Verwendung von "rising_edge()". Dies ist etwas Neues für VHDL und wird in einigen VHDL-Büchern nicht behandelt. Dies wird als besser angesehen als die Verwendung von „event. Ebenso gibt es eine fallende_Kante().

Die nächste Sache ist meine Verwendung von „hoch“ und „niedrig“, anstatt die Werte fest zu codieren. Dies spielt für diesen Code wirklich keine Rolle, aber wenn Sie anfangen, größere Dinge zu tun, werden Ihnen diese sehr helfen. Zum Beispiel könnten Sie einfach die Definition von tmp größer machen und der Rest des Codes passt sich einfach automatisch an (mit Ausnahme der Initialisierung auf "10000001").

Ich sollte auch darauf hinweisen, dass von einem asynchronen Zurücksetzen oder Laden in FPGAs abgeraten wird, aber in CPLDs in Ordnung ist.

Beachten Sie auch, dass ich die Zuweisung von SO nach außerhalb des Prozesses verschoben habe. Dies kann oder kann nicht das sein, was Sie beabsichtigt haben. So, wie Sie es haben, gibt es ein zusätzliches Flip-Flop, das von tmp(7) nach SO geht. Normalerweise ist dies bei SPI nicht erwünscht, da die SPI-Uhr am Ende der Übertragung verschwinden könnte und Sie das letzte Bit nie herausbekommen. Auf der anderen Seite bekommen Sie mit der Art und Weise, wie ich es gemacht habe, das erste Bit heraus, wenn ALOAD='1', nicht an der steigenden Flanke von C.

Leider beantwortet dies nicht wirklich Ihre Frage, warum Sie schlechte Daten in den AVR bekommen. Ihre Frage enthält einfach nicht genügend Informationen. Hier ist die Art von Dingen, die ich mir ansehen oder über die ich mir Sorgen machen würde:

Ihr O-Scope-Bild zeigt keine anderen Spi-Signale wie CS. CS ist entscheidend für das Verständnis, wohin die Bits gehen sollen. Auch zu wissen, was der SPI-Modus ist, wird dabei helfen.

Auf dem O-Scope-Bild ist der erste Taktzyklus, bei dem SO = '1' ist, NICHT das erste Bit der SPI-Übertragung. Sie haben das Schieberegister 10-20 ms davor geladen, und Ihre Taktperiode beträgt ungefähr 20 uS. Sie hatten also mindestens 1 Taktzyklus vor SO='1' und wahrscheinlich mehr. Hier gehen also einige seltsame Dinge vor – wir haben nicht genügend Informationen, um das Verhalten zu verstehen.

Sie verwenden ALOAD, um das Schieberegister zu laden, aber normalerweise würden Sie CS_N (aktiv niedrig) verwenden. Während CS_N = '1' führen Sie ein asynchrones Laden des Schieberegisters durch, während CS_N = '0' Sie es herausschieben. Die Verwendung von ALOAD wie hier ist in Ordnung, aber wahrscheinlich nicht das, was Sie wollten, und funktioniert nicht mit scheinbar seltsamen SPI-Uhren (vom vorherigen Punkt).

Also, hier ist, was Sie tun sollten ...

Bereinigen Sie das VHDL ein wenig. Reposte die aktualisierte Version. Da Ihr Oszilloskop nur 2 Kanäle hat, schließen Sie einen Kanal an CS_N und den anderen Kanal an CLK an. Trigger auf die fallende Flanke von CLK. Erfassen Sie eine Wellenform, die 2 Takte vor der fallenden Flanke von CLK und 5 Takte danach zeigt. Entfernen Sie CLK, ohne die Einstellungen am Oszilloskop zu ändern , und setzen Sie die Sonde auf SO. Nehmen Sie ein weiteres Bild auf. Wiederholen Sie dies für die ansteigende Flanke von CLK. Also insgesamt 4 Wellenformbilder.

Wenn Sie das tun, können wir neu bewerten, was falsch sein könnte.

Bearbeiten: Aktualisiert, um die aktualisierte Frage widerzuspiegeln.

Ich sehe zwei Probleme: Erstens, wenn der AVR an der steigenden clk-Flanke abtastet, sollten Sie Ihr Schieberegister von der fallenden Flanke abtakten. Wie Supercat erwähnt, erhalten Sie dadurch +/- 0,5 Taktperioden Setup & Hold, die in Ihren AVR gehen.

Und zweitens: Wie Sie bereits erwähnt haben, erhalten Sie 10000010 anstelle von 10000001. Ich glaube nicht, dass Ihr VHDL-Code in diesem Fall fehlerhaft ist, aber er kommt offensichtlich falsch aus dem CPLD. Wenn ich raten müsste, würde ich vermuten, dass das Problem mit einigen Signalintegritätsproblemen bei Ihrem CLK zusammenhängt. Mit Ihrem Oszilloskop ist das schwer zu sagen, aber es sieht so aus, als hätten Sie bei diesem Signal mehr als ein Volt Über- und Unterschwingen (und damit würde viel Klingeln einhergehen). Dieses Klingeln könnte, wenn es schlimm genug ist, dazu führen, dass das CPLD "doppelt taktet" - was bedeutet, dass das Schieberegister zweimal für eine einzelne Taktflanke ausgeführt wird. Und wenn es _wirklich_schlecht_ ist, könnte es dazu führen, dass das CPLD hängen bleibt und buchstäblich explodiert (ich habe es gesehen).

Hier sind einige Experimente zum Ausprobieren:

  1. Verwenden Sie anstelle von 10000001 10101010 oder 01010101. Dadurch können Sie sehen, welches Bit doppelt getaktet wird und ob es immer dasselbe Bit ist.

  2. Zoomen Sie mit dem Zielfernrohr auf die Ränder der Uhr. Stellen Sie sicher, dass sich Ihre Oszilloskopsonden dabei am CPLD und nicht am AVR befinden. Ja, es macht einen RIESIGEN Unterschied.

Unter der Annahme, dass die Uhr über-/unterschritten wird und klingelt, besteht die Lösung darin, einen ordnungsgemäßen Signalabschluss auf der Leitung hinzuzufügen. Ich würde mit einem 50 Ohm Vorwiderstand am AVR anfangen. Hinweis: Dadurch werden die Taktflanken verlangsamt, aber da Sie das CPLD auf der fallenden Flanke takten, haben Sie viel Zeit zur Verfügung.

Wie läuft die Uhr von AVR zu CPLD? Ein langer Draht zwischen Leiterplatten? Das wäre meine Vermutung.

Vielen Dank, David. Das Buch, das ich habe, ist alt und behandelt nicht die neuen Dinge, über die Sie gesprochen haben. Ich sehe den Vorteil der Verwendung von 'High/'Low'-Attributen. In ähnlicher Weise bereinigt die steigende Flanke auch die Dinge und verbessert die Lesbarkeit. Allerdings bin ich verwirrt über CS. Das CPLD ist das einzige Gerät, das ich an den SPI-Bus angeschlossen habe (neben dem ISP-Programmierer) und da ich keine Chips auswähle, habe ich CS als Ausgang eingestellt, aber ich mache nichts damit. Ist mein nächster Schritt, es so anzuschließen, dass es das Register lädt?
Außerdem habe ich den SPI-Modus auf Standard gelassen. Da sich der CPLD an der steigenden Flanke verschiebt, dachte ich, dass der Standardmodus ausreicht. Liege ich diesbezüglich falsch?
@Saad SPI Chip select macht mehr als nur "den Chip auswählen". Es umrahmt die gesamte SPI-Übertragung (sagt, wann sie beginnt und endet). Andernfalls erhalten Sie nur einen Strom von Bits und Takten, und Sie wissen nicht, wann ein Byte/Wort/Übertragung beginnt und das andere endet. Einige Anwendungen "brauchen" kein CS, aber in den meisten Fällen würde es uns sagen, was der AVR erwartet. Der SPI-Modus definiert zwei Dinge: Polarität der Uhr und Timing des CS. Diese sind wichtig, wenn Sie herausfinden möchten, was der AVR tut oder erwartet.
Bei SPI ist es üblich, dass Geräte Daten an einer Taktflanke herausschieben und Daten an der anderen zwischenspeichern. Dies ermöglicht im Allgemeinen einen +/- halben Taktversatz zwischen den Takt- und Datenleitungen. Einige Hardwaregeräte (z. B. 74HC595 und 74HC165) speichern Daten immer an derselben Taktflanke, wenn sie ihren Ausgang ändern, und einige SPI-Geräte ermöglichen die unabhängige Konfiguration von Sende- und Empfangstaktflanken. Wenn ein Slave das erste Bit auf /CS und die folgenden Bits mit jeder führenden Taktflanke ausgibt, kann ein Master, der den Takt erzeugt, einen vollen Takt der Umlaufverzögerung anstelle von 1/2 Takt tolerieren.
@ David Kessner: Ich frage mich, warum ich nicht viele SPI-artige Geräte gesehen habe, die andere Framing-Mittel als ein /CS-Kabel verwenden? Bei SPI gibt es im Allgemeinen nicht mehr als zwei MISO-Flanken zwischen Taktflanken. Wenn sich MISO mehrmals ändert, während die Uhr in einem Zustand bleibt, könnten solche übermäßigen Übergänge einen Start eines Pakets signalisieren. Es gibt viele mögliche Übergänge, die man verwenden könnte.
@ David Kessner: Ich habe meine ursprüngliche Frage mit den neuen Wellenformen aktualisiert. Ich hoffe, sie sind deutlich genug.
@supercat SPI ist ein einfaches Protokoll, das in geringem Umfang einfach zu implementieren ist, wenn es logisch ist. Ein Teil dieser Einfachheit ist die grundlegende Taktung. Wenn Sie das tun, was Sie vorschlagen, wird die Schnittstellenlogik viel komplexer. An diesem Punkt können Sie dann auch I2C verwenden. Saad, ich schaue mir dein Update später an, wenn ich nicht auf meinem IPhone tippe.
@David Kessner: Stimmt, SPI ist einfach, aber die Menge an Logik, die erforderlich ist, um das Framing ohne /CS zu handhaben, ist ziemlich winzig. Zum Beispiel könnte man ein Gerät entwerfen, das mit niedrig geschnalltem /CS verwendbar wäre, indem man zwei Flip-Flops und ein "ODER"-Gatter hinzufügt. Beide Flip-Flops haben ein Async-Reset, das mit CLK verbunden ist, und einen Takt, der mit MOSI verbunden ist. FF0 hat Daten, die hoch geschnallt sind, und FF1 hat einen Dateneingang, der mit FF0 verbunden ist. Die Ausgabe von FF0 wird mit /CS in ODER-verknüpft, um das effektive CS zu erhalten.
@saad Meine Antwort wurde aktualisiert.
@ David Kessner: Der 50-Ohm-Widerstand hat geholfen. 10000001 kommt richtig heraus. Aber 10000011 kommt als 11000111 heraus. Ich konnte das Problem also nicht vollständig lösen. Hier ist eine Sache, die mir aufgefallen ist - die Taktfrequenz ist nicht sehr konstant. Sie ändert sich von 61,3 kHz auf 61,9 kHz und geht manchmal sogar über 62 kHz hinaus. Könnte dies zu Problemen führen, wie wir sie sehen? Selbst wenn das Oszilloskop ausgelöst wird und die Wellenform auf dem Bildschirm sitzt, kann ich sehen, wie sie flackert und sich nach rechts bewegt und dann schnell in ihre ursprüngliche Position zurückkehrt. Dies geschieht konsequent.
Ich verwende keinen externen Kristall, sondern verlasse mich auf die interne Uhr der MCU. Würde ein externer Quarz bei diesem Problem helfen? Zweitens, denken Sie, wenn ich die gleiche Schaltung auf einem Veroboard / Perfboard (im Moment aus Kupferplatten) bauen würde, würde es helfen? Obwohl sich das CPLD auf einer anderen Platine befindet, konnte ich sicherstellen, dass die Drähte kürzer sind als jetzt. Warum sollte die Uhr der MCU so stark überschießen?
@Saad Die Frequenzänderungen sollten keine Rolle spielen, da Sie die Setup / Hold-Zeiten nicht verletzen. Probier mal 100 Ohm. Überprüfen Sie auch die Signalintegrität der anderen Signale, da sie wahrscheinlich durcheinander gebracht werden. Denken Sie daran, dass der Vorwiderstand zum Treiber geht, nicht zum Empfänger. Die Kabellänge sollte kein Problem sein, außer dass sie andere Probleme wie Signalterminierung und Impedanzfehlanpassungen verstärkt.
Die Signale sehen in meinen Augen am CPLD OK aus. Weniger als 0,2 V Überschwingen. Ich bin mir nicht sicher, ob das in Systemen akzeptabel ist. Ich für meinen Teil bin mit meinem Latein am Ende. Ich habe übrigens 180 Ohm ausprobiert, und es schien keinen Unterschied zu machen.