Dumme FPGA-Uhr

  • Tutorial
Hallo an alle!

Ich beschloss, einen weiteren Artikel zu schreiben, der für unerfahrene Entwickler auf dem Gebiet des FPGA nützlich sein würde. Es hat sehr lange gedauert, die Veröffentlichung zu verschieben, das Material selbst wurde vor einigen Monaten erstellt, aber irgendwie kamen sie nicht dazu, sich hinzusetzen und all dies in einen ganzen Artikel zu schreiben. Aber schließlich gab es Freizeit, also lade ich alle Interessierten ein, es zu lesen.


Das Projekt heißt "dumme Uhren", weil es nichts anderes kann, als Uhrzeit und Datum zu zählen und anzuzeigen. Das Projekt richtet sich in erster Linie an Senioren und Anfänger. Es gibt keine ungewöhnlichen Blöcke, Hersteller-IP-Cores werden nicht verwendet, und es gibt auch keine komplizierten Austauschschnittstellen (wie PCIe, Ethernet, USB usw.).
Dieses Mal wird das Projekt über programmierbare logische integrierte Schaltkreise (FPGAs) primitiv und äußerst einfach sein, und ich werde versuchen, über alle Schwierigkeiten zu sprechen, mit denen ich bei der Ausführung der Aufgabe zu kämpfen hatte.

Ausgangsdaten


Debugging Board: Trotzdem kleines und unprätentiöses Devkit auf Basis des Spartan3E FPGA. Seine Eigenschaften:

Hauptmerkmale:
  • FPGA Spartan3E ( XC3S500E-4PQ208C ) - 500K Logikgatter,
  • Taktquelle CLK = 50 MHz,
  • Externer Speicher 64M SDRAM,
  • SPI-Flash (M25P80) zum Speichern der FPGA-Firmware,
  • LED Matrix 8x8, LED Linie 8 Stk.,
  • 8 Schalter und 5 Tasten,
  • Anschlüsse für LED-Displays,
  • VGA-Anschluss für das Display
  • Timer DS1302 ,
  • LCD1602 Anzeige ,
  • PS / 2-Anschlüsse usw.

Aus dieser Liste benötigen wir direkt das FPGA selbst, das all dieses Geschäft verwalten wird. Außerdem werden zwei Geräte (DS1302-Chip und LCD1602-Display) und mehrere Triggerschalter benötigt.

DS1302 - Timer mit serieller Übertragungsschnittstelle (hergestellt von Maxim Integrated),
LCD1602 - LCD-Anzeige mit der Möglichkeit, zwei Zeilen mit 16 Zeichen anzuzeigen (hergestellt von Noname).

Das Folgende ist ein Blockdiagramm der Verbindungen:

Fahren wir mit der Beschreibung der verwendeten Chips fort.

Timer


Der DS1302 ist ein kleiner Chip, der drei Kommunikationsleitungen verwendet, um eine serielle Schnittstelle zu bilden.
  • CE - Clock Enable, aktiviert das SCLK Line Clocking.
  • SCLK - Taktsignal (maximale Frequenz = 2 MHz).
  • E / A - Ein Eingabe- / Ausgabeport, über den Daten und Befehle in sequentieller Form übertragen werden.

Das Hauptmerkmal des Chips ist, dass der Zeitablauf nicht stoppt, selbst wenn der FPGA und das Display vom Stromnetz getrennt sind. Mit dem Timer können Sie Sekunden, Minuten, Stunden, Wochentage, Daten, Monate und auch Jahre zählen, dh einen vollständigen Satz von "Stunden".

Außerdem speichert die Mikroschaltung die letzten "zusammengefügten" Werte für Uhrzeit und Datum. Aber der Chip selbst ist "dumm", das heißt, er muss alle Parameter einstellen, einschließlich des Wochentags. Im Timer befindet sich ein Zähler, der die Zeit- und Datumswerte korrekt erhöht, wenn alles korrekt in den Chip geflasht wurde.
Ein auf eine Frequenz f = 32,768 kHz abgestimmter Quarzoszillator wird von außen an die Mikroschaltung angeschlossen.

Die folgende Abbildung zeigt die Schnittstelle für den Datenaustausch zum Lesen und Schreiben:


Wie funktioniert das?
a) Um einen Wert aus dem Timer zu schreiben oder zu lesen, muss der CE-Eingang zum Zeitpunkt der Übertragung auf einen hohen Pegel (logisch 1) gesetzt werden.
b) Abhängig von der Lese- oder Schreiboperation müssen 15 oder 16 auf einen hohen CE-Pegel gesetzt werden Taktimpulse. Der Vorgang des Schreibens von Daten von dem E / A-Eingang erfolgt entlang der ansteigenden Flanke des SCLK-Taktsignals, und der Datenlesevorgang erfolgt entlang der abfallenden Flanke des SCLK-Signals. Die Daten werden in der Reihenfolge vom niederwertigen zum hochwertigen Bit übertragen.
c) Das erste Byte im Übertragungsprozess ist immer überlastet und wird immer in den Timer „geschrieben“. Dies bedeutet, dass er den Typ und die Richtung des übertragenen Befehls auswählt. Das Nullbit R / W ist für die Art der Operation verantwortlich: "Lesen" - auf hohem Pegel, "Schreiben" - auf niedrigem Signalpegel. In den Bitfeldern 1 bis 5 wird die Adresse des Befehls oder des internen Timerspeichers eingestellt. Das sechste Bit definiert die Arbeit mit Timer-Registern oder dem internen Speicher: "Speicher" - auf einem hohen logischen Niveau, "Timer" - auf einem niedrigen logischen Niveau. Das siebte Bit sollte immer auf 1 gesetzt werden (wenn das siebte Bit zufällig 0 ist, reagiert der Timer nicht auf den Befehl).
d) Zweites Byte - Daten, die in den Timer geschrieben oder aus ihm gelesen werden.

Die folgende Abbildung zeigt die Tabelle der Zeitgeberbefehle:


Wie wird mit der Tabelle gearbeitet?
Die Tabelle zeigt, dass der Timer die Daten im binären Dezimalformat (BCD) verarbeitet . In diesem Zusammenhang muss das FPGA künftig einen Datenkonverter von binär nach binär dezimal schreiben.
Beispiel: Um den Wert von Sekunden von einem Timer zu lesen, muss ein Befehlsbyte 0x81 gebildet werden. Um den Wert von Minuten aufzuzeichnen, muss ein Befehlsbyte 0x82 gebildet werden.

Bei der Arbeit mit dem Timer-Chip gibt es eine leichte Subtilität, weshalb ich den ganzen Tag nach Problemen gesucht und sogar ein paar Mal den Timer-Controller auf dem FPGA neu geschrieben habe. Deshalb fordere ich alle auf - lesen Sie die Datenblätter sorgfältig durch. :)
Um das Datum und die Uhrzeit auf den Chip zu schreiben, müssen Sie zuerst die Aufzeichnung aktivieren. Siehe in der Tabelle unter den Adressen 0x8F und 0x8EWP- Bit (Schreibschutz)? Standardmäßig ist das Bit auf 0 gesetzt, was bedeutet, dass Schreiboperationen verboten sind. Um Datum und Uhrzeit in die Mikroschaltung zu schreiben, müssen Sie WP zunächst in den Zustand einer logischen Einheit versetzen. Dies geschieht, indem zwei Bytes nacheinander gesendet werden (Befehl und Daten): 0x8E 0x80 . Danach wird der Timer gehorsam und Sie können Informationen an sich selbst schreiben.

Achten Sie auch auf 7 Bits im Sekundenregister - CH (Clock Halt). Der Timer stoppt, wenn sich dieses Bit in einem hohen Zustand befindet. Wenn Sie zum ersten Mal in das Sekundenregister schreiben, müssen Sie daher das höchstwertige Bit zurücksetzen.

LCD-Anzeige


LCD1602 ist ein normales LCD-Display, mit dem Sie zwei Zeilen mit jeweils 16 Zeichen anzeigen können. Es verwendet eine parallele Austauschschnittstelle. Die Schnittstelle ist primitiv und einfach.
  • DB [7: 0] - Paralleler Befehls- / Datenbus.
  • RS - Registerauswahl, Auswahlsignal: '0' - Befehle, '1' - Daten.
  • R / W - Datenübertragungsrichtung ('0' - Schreiben / '1' - Lesen).
  • E- enable, Synchronisation, wird zum Zeitpunkt der Übertragung entlang der DB-Leitung auf eine logische Einheit gesetzt




Ich habe das Display im Aufnahmemodus benutzt, dh der Port R / W = 0 immer. In diesem Zusammenhang wird der Datenaustausch etwas vereinfacht, ebenso wie der Controller selbst, der auf dem FPGA implementiert ist.

Wie arbeite ich mit dem Display?
Vor jeder ersten Anzeige muss sie mit einer Reihe von Befehlen initialisiert werden, die im Datenblatt angegeben sind.
Für meine Implementierung des Display-Controllers war dies ein Satz von vier Befehlen, die bei jedem Start der Firmware nacheinander ausgeführt wurden. Es müssen mehrere Initialisierungsbytes an die DB- Zeile gesendet werden :
0x00 (oder 0x01) - löscht die Anzeige.
0x38 - Legt die Anzahl der Zeilen und die Schriftgröße fest (ich benötige 2 Zeilen à 16 Zeichen mit einer maximalen Zeichengröße).
0x0C- setzt den Cursor auf ein / aus. ("Ein" in meinem Fall).
0x06 - Stellt die Richtung der Cursorbewegung und -verschiebung auf dem Display ein (nach rechts, d. H. Das Inkrement des Verschiebungszählers).
In diesem Fall müssen sich die RS- und R / W- Signale in einem logischen Nullzustand befinden.

Nach dem Initialisierungsvorgang werden Befehle und Daten der Reihe nach entlang der DB-Leitung übertragen. Der Befehl bestimmt die Position des Symbols auf dem Display, die Daten bestimmen das Symbol aus der folgenden Tabelle.

Um beispielsweise das Zeichen " Q " zu schreiben, müssen Sie die Nummer 0x51 in die DB-Zeile schreiben. Somit reduziert sich die gesamte Datenübertragung zum Display auf die Wahl von Position und Symbol.

FPGA-Projekt


Fahren wir mit der Implementierung des Projekts auf dem FPGA fort. Die im Projekt verwendete Programmiersprache ist VHDL. Es unterscheidet sich vom Rest durch seine Einfachheit und gleichzeitig durch die Sperrigkeit der Strukturen.
Damit die Uhr anfängt zu ticken und das Display das Datum und die Uhrzeit anzeigt, müssen Sie mehrere Aktionen ausführen, die im Prinzip für jedes Projekt auf dem FPGA ausgeführt werden. Es ist erforderlich:
  • zwei unabhängige Steuerungen - für DS1302 und für LCD1602.
  • zwei Testpakete - zum Einstellen der Anfangszeit und zum Initialisieren der Anzeige.
  • eine Datei der obersten Ebene, die all dies miteinander verbindet.
  • FPGA Pinout-Datei (UCF-Datei).

UCF
Beginnen wir mit einem einfachen. Definieren Sie die Pins, die im Projekt verwendet werden sollen.
## CLK 50 MHz
NET "CLK" 	LOC = "P183" | IOSTANDARD = LVCMOS33 ;
## Switches
NET "RESET"	LOC = "P148" | IOSTANDARD = LVTTL | PULLUP ; ## SW<0>
NET "START"	LOC = "P130" | IOSTANDARD = LVTTL | PULLUP ; ## SW<3> 
NET "RESTART"	LOC = "P124" | IOSTANDARD = LVTTL | PULLUP ; ## SW<4>  
NET "TEST_LCD"	LOC = "P118" | IOSTANDARD = LVTTL | PULLUP ; ## SW<5> 
## LCD ports
NET "LCD_RS" 	LOC = "P77" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "LCD_RW" 	LOC = "P68" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "LCD_EN" 	LOC = "P65" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "LCD_DT<0>" LOC = "P49" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "LCD_DT<1>" LOC = "P48" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "LCD_DT<2>" LOC = "P40" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "LCD_DT<3>" LOC = "P50" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "LCD_DT<4>" LOC = "P62" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "LCD_DT<5>" LOC = "P98" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "LCD_DT<6>" LOC = "P64" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "LCD_DT<7>" LOC = "P63" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
# SERIAL PORTS VIA HEADEX  
NET "T_CE" 	LOC = "P147" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 8 ;
NET "T_CK" 	LOC = "P146" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 8 ;
NET "T_DT" 	LOC = "P145" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 8 ;

In der UCF-Datei werden der in der Datei der obersten Ebene verwendete Portname, der Pin, an den er angeschlossen ist, sowie andere Parameter (elektrischer Typ, aktuelle Ebene, Treibergeschwindigkeit, Pull-up usw.) festgelegt. Die Signale zum Anschließen eines Timers und eines LCD-Displays wurden oben beschrieben, daher werde ich eine Beschreibung der fehlenden Ports geben, die an die Schalter angeschlossen sind.
  • RESET - '0' - globales Zurücksetzen des FPGA.
  • START - '1' - Start für das LCD-Display (startet den Initialisierungs- und Datenübertragungsprozess).
  • NEUSTART - '0' - startet den Timer neu (setzt Datum und Uhrzeit auf den im FPGA-Speicher definierten Zustand).
  • TEST_LCD - Port für Testzwecke, '0' - zeigt Daten vom Timer an, '1' zeigt eine statische Meldung an ("hallo habr").

Die zweite Falle, auf die ich gestoßen bin - die Entwickler des Devkits haben vergessen, den Timer-Chip mit dem FPGA zu verbinden. Aber zum Glück haben sie die serielle Schnittstelle zu den Pins gebracht, die ich mit Leitern an andere Pins angeschlossen habe, die direkt mit dem FPGA verbunden sind.

HDL
Als nächstes werden eine Zeitsteuerung und eine Anzeigesteuerung geschrieben. Es macht keinen Sinn, den vollständigen Text zu VHDL zu zitieren, aber ich werde über die Prinzipien des Datenaustauschs sprechen. Beim ersten Start (wenn alle Steuersignale gesetzt und zurückgesetzt wurden) wird das LCD initialisiert und die in der Datei der obersten Ebene definierten Datums- und Zeitkonstanten werden im Timer aufgezeichnet. Das Folgende ist der Funktionscode zum Konvertieren von Daten von binären in binäre Dezimalzahlen:
---------------- INTEGER TO STD_LOGIC_VECTOR TO BCD CONVERTER ----------------
constant n 				: integer:=8;
constant q 				: integer:=2;
function to_bcd ( bin : std_logic_vector((n-1) downto 0) ) return std_logic_vector is
	variable i : integer:=0;
	variable j : integer:=1;
	variable bcd : std_logic_vector(((4*q)-1) downto 0) := (others => '0');
	variable bint : std_logic_vector((n-1) downto 0) := bin;
begin
	for i in 0 to n-1 loop -- repeating 8 times.
		bcd(((4*q)-1) downto 1) := bcd(((4*q)-2) downto 0); --shifting the bits.
		bcd(0) := bint(n-1);
		bint((n-1) downto 1) := bint((n-2) downto 0);
		bint(0) :='0';	
		l1: for j in 1 to q loop
			if(i < n-1 and bcd(((4*j)-1) downto ((4*j)-4)) > "0100") then --add 3 if BCD digit is greater than 4.
				bcd(((4*j)-1) downto ((4*j)-4)) := bcd(((4*j)-1) downto ((4*j)-4)) + "0011";
			end if;
		end loop l1;
	end loop;
	return bcd;
end to_bcd; 

Die folgende Abbildung zeigt die Zeitdiagramme, die während des Debuggens der Zeitsteuerung erhalten wurden. Fettgedruckte Linien kennzeichnen die serielle Übertragungsschnittstelle. Die drei Zeilen von data_i, data_o, data_t sind mit dem IOBUF-E / A-Port in der Datei der obersten Ebene verbunden.


Innerhalb des FPGA werden in einer Endlosschleife Timer-Register abgefragt, die für das Einstellen der Zeit (0x80-0x8D) verantwortlich sind. Das Abrufen von Registern erfolgt im Kreis, beginnend mit Jahren und endend mit Sekunden. Nach jedem Lesen von dem Zeitgeber werden die Daten in die FPGA-Register eingegeben, die charakteristische Namen tragen (Sekunde, Minute usw.). Nach dem Empfang eines Erlaubnissignals von der LCD-Steuerung werden Daten von der Timer-Steuerung an die Steuereinheit für die Anzeigesteuerung übertragen. Abhängig vom Typ des Timer-Registers und seinem Wert werden die Daten in das für die Anzeige erforderliche Format konvertiert und die gewünschte Position auf der Anzeige durch Einstellen der entsprechenden Adresse festgelegt. Der LCD-Display-Controller empfängt Daten vom Timer und schreibt sie in den 8x32-RAM-Speicher mit zwei Ports im FPGA, der auf der verteilten Chiplogik (SLICEM-Zellen) ausgeführt wird. Dann liest die Steuerung alle Informationen aus dem Speicher und aktualisiert dadurch die Symbole auf dem LCD. Das gesamte Projekt arbeitet mit einer relativ hohen Frequenz von 50 MHz, so dass es unrealistisch ist, Verzögerungen beim Zählen von Sekunden mit dem Auge zu erkennen.

Die folgende Abbildung zeigt die Platzierung des Projekts im FPGA-Chip.


Die grünen Linien zeigen die Verbindungen der E / A-Ports zu den internen FPGA-Ressourcen an, der blaue Bereich ist der Bereich der Logikgatter, die roten Rechtecke sind eine Reihe integrierter 18x18-Multiplikatoren und RAMB16K-Blockspeicher, die orangefarbenen Quadrate sind DCM-Syntheseblöcke (Digital Clocking Manager). Lila hervorgehobene Rechtecke sind "p-Block", innerhalb dessen Grenzen diese oder jene Projektkomponente geschieden wird, wenn die Option "Hierarchie beibehalten" aktiviert ist. Die weiß hervorgehobenen Bereiche sind die Zellen der Steuerungen und Testmodule. Die restlichen p-Blöcke sind von meinem alten FPGA-Projekt geblieben . Ich habe beschlossen, sie nicht zu löschen (was ist, wenn jemand Sapper spielen möchte?).

Ergebnis:


Daher wurde im Laufe der Arbeit die FPGA-Firmware erstellt, um den Timer mit dem LCD-Display zu verbinden.
Mit der CAD-Software Aldec Active-HDL wurde eine temporäre Simulation des Projekts sowie das gesamte Schreiben von Code und Modellen durchgeführt. In der Xilinx ISE Design Suite- Umgebung wurde das Projekt auf dem Spartan3E-FPGA synthetisiert und verfolgt. Ebenfalls verwendete Hilfswerkzeuge: PlanAhead - Wird für die Pinbelegung, die Position der Projektknoten im Chip und das Tracing verwendet. ChipScope Pro - wird zum Debuggen in realer Hardware verwendet, indem spezielle Debugging-Kerne in das FPGA geladen werden.


Automatische Projektdokumentation
Es kam vor, dass bei der Erstellung dieses Projekts bei unserer Arbeit ein weiterer Versuch unternommen wurde, die Quellcodedokumentation einzuführen. Eine Lösung schlug die Verwendung des automatisierten Dokumentationstools Doxygen vor . Mir gefällt sehr gut, wie Hierarchiediagramme in dieser Anwendung erstellt werden. Für HDL-Projekte ist dies ein sehr wichtiger Punkt. Auf einem habr gibt es einige Artikel , die diesem Werkzeug gewidmet sind . In Verbindung mit dem HTML Help Workshop können Sie mit Doxygen nur eine Dokumentationsdatei im CHM- Format generieren .

Leider ist der Implementierungsversuch fehlgeschlagen, aber die Erfahrung mit dem Tool ist geblieben. Deshalb habe ich mich für das Projekt der „blöden“ FPGA-Uhren für die automatische Dokumentation entschieden. Auf dem Github im Ordner mit dem Projekt befindet sich die Konfigurationsdatei für DoxyWizard und das Ergebnis seiner Arbeit. Hier ist eine Seite von dem, was im Dokumentationsprozess passiert ist.


Noch andere Bilder ...
Schematische Darstellung des Projekts:


Debugging-Prozess in ChipScope:


Der Quellcode auf Github ist LCD Timer auf FPGA .

Videodemonstration des Projekts:

Wundern Sie sich nicht über das angezeigte Datum. Ich habe das Material für eine lange Zeit gedreht und vorbereitet, und die Hände des Artikels kamen gerade zum Artikel. Remaking ist faul ... :)

Ähnliche Projekte:

Jetzt auch beliebt: