Beschreibung eines Morsedecoders und -encoders mittels VHDL
und Implementierung auf einem FPGA-Entwicklungsboard
-- --- .-. ... . -.. . ..- -. -.. . -. -.-. --- -.. . .-. ...
1 Einführung
Nach dem mooreschen Gesetz verdoppelt sich alle 18 Monate die Komplexität integrierter Schaltkreise ohne zusätzlichen Kostenaufwand im Vergleich zur vorherigen Systemgeneration. Dies führt dazu, dass sich nicht nur die Komponentendichte auf einem IC erhöht, sondern dass auch neue Funktionen verfügbar werden.
Um diesen Effekt jedoch wirtschaftlich nutzen zu können, ist die Industrie gezwungen, innerhalb kürzester Zeit neue Systeme zu entwickeln, damit sie langfristig betrachtet wettbewerbsfähig bleiben kann.
Als Antwort auf dieses Problem werden digitale Systeme heutzutage mithilfe von Softwaretools entworfen, simuliert und getestet, bevor sie auf den Markt gebracht werden.
Eine dieser Möglichkeiten zur einfachen Konstruktion eines digitalen Systems bietet die Hardwarebeschreibungssprache VHDL (Very High Speed Integrated Circuit Hardware Description Language) mit der dazugehörigen Entwicklungsumgebung Quartus II der Firma Altera. Dabei wird mit einem Code, vergleichbar mit einer Programmiersprache, das zu entwickelnde System hardwaretechnisch beschrieben. Mit einem Synthesetool wird daraus eine Netzliste generiert, die wiederum über ein Routingtool auf einem FPGA (Field Programmable Gate Array) so implementiert wird, dass sich eine lauähige Version dieses Systems ergibt. Das hat den Vorteil, dass innerhalb kürzester Zeit neue Systeme eingesetzt und im Nachhinein angepasst und verbessert werden können. Die Möglichkeit fehlerhafte Chips in hohen Stückzahlen zu produzieren wird somit ausgeschlossen.
In diesem Projekt soll genau dieses Verfahren am Beispiel eines neu entwickelten Systems zur Kodierung und Dekodierung von Morsesignalen eingesetzt und geübt werden.
1.1 Zielsetzung
Ziel dieses Projektes ist es, einen Morsedecoder und -encoder mit der Hardwarebeschreibungssprache VHDL zu spezifizieren. Als Grundlage für diese Aufgabe dient das Buch Kompaktkurs VHDL von Paul Molitor und Jörg Ritter. [MR13]
Zusätzlich sollen in diesem Projekt mittels einer Tastatureingabe über einen PS2-Anschluss die eingelesenen Zeichen als Morsecode ausgegeben werden können. Dafür sollen die Morsezeichen durch ein Audiosignal repräsentiert werden.
Weiterhin soll die Wortgeschwindigkeit, mit der ein Morsesignal erzeugt wird, einstellbar sein.
Ebenfalls sollen die decodierten Morsesignale auf einer Anzeige, wie zum Beispiel einer Siebensegment-Anzeige oder einem Liquid Crystal Display (kurz: LCD), dargestellt werden.
Die Umsetzung dieses Projektes erfolgt in der Entwicklungsumgebung Quartus II und wird auf dem Altera Entwicklungsboard DE2-115 implementiert, auf dem ein Cyclone IV-FPGA verbaut ist. [Ter13]
2 Materialien
Im Verlaufe dieses Projektes kamen nur vier Materialien zum Einsatz. Zum einen handelt es sich dabei um das Entwicklungsboard DE2-115 der Firma Altera mit einem Cyclone IV-FPGA [Ter13], einer Tastatur mit einem PS/2-Anschluss, sowie eines Audioausgabesystems und zum anderen um die Entwicklungsumgebung Quartus II in der Version 18.0.
2.1 Hardware
Das in diesem Projekt verwendete Entwicklerboard DE2-115 der Firma Altera verfügt neben dem Cyclone IV-FPGA, auf dem der Morsedecoder / -encoder implementiert werden soll, über eine Vielzahl von Peripheriegeräten [Ter13]. Zur Erstellung des Projektes werden sowohl der 50MHz-Oszillator, einige der LEDs, Taster und Siebensegment-Anzeigen verwendet. Zusätzlich kommen der PS/2-Anschluss zur Ansteuerung einer externen Tastatur, ein LCD [Hit98], zur Anzeige der eingegebenen und decodierten Morsezeichen und der integrierte Audio Codec [Wol05], mit dem über den angeschlossenen Line-Out Ausgang die codierten Morsezeichen in Form eines 500Hz Audiosignals ausgegeben werden können, zum Einsatz.
Ebenfalls wurden an verschiedenen Stellen über den in Quartus II integrierten Platform Designer Speicherbausteine in Form von RAM und ROM verwendet.
2.2 Software
Die verwendete Software Quartus II ist ebenfalls ein Produkt von Altera und dient als Entwicklungsumgebung für FPGAs.
Neben dem einfachen Editor zur übersichtlichen Darstellung des HDL-Codes ist in der Software ein Compiler mit einem Synthesetool enthalten, die den Code in eine Netzliste umsetzen. Diese Netzliste wird beim Routing auf das jeweilige FPGA angepasst und kann in einem weiteren Schritt über den Programmer an das FPGA über einen USB-Blaster übertragen werden.
Ebenfalls ist in der Entwicklungsumgebung ein Platform Designer enthalten, der es einem erlaubt, einige der auf dem Board enthaltenen Peripheriekomponenten zu einem vordefinierten System zusammenzusetzen und die Funktionen dieses Systems über eine Hardwarebeschreibungssprache zu spezifizieren.
Das in diesem Projekt entwickelte System wird mit der Hardwarebeschreibungssprache VHDL definiert.
3 VHDL
VHDL ist eine Hardwarebeschreibungssprache und ermöglicht es mithilfe eines Codes ein Hardwaresystem zu beschreiben. Dabei werden die einzelnen Komponenten in sogenannten Entities beschrieben. In der Definition einer Entity muss immer angegeben werden, welche Signale diese Entity benötigt (INPUTS) und welche Signale ausgegeben werden können (OUTPUTS). Dies bestimmt bei der Erzeugung der Netzliste das Aussehen der entsprechenden Komponente.
Anstatt einfache Variablen zu verwenden, müssen im VHDL-Code Signale definiert werden. Auf diesen Signalen können nur binäre Werte übergeben werden, da sie nichts anderes sind als Leitungen, die einen LOW- oder HIGH-Pegel übertragen können. Sollen allerdings andere Werte übergeben werden, wie z.B. ganze Zahlen, so müssen mehrere dieser Leitungen als Integer oder Naturals zusammengefasst werden. Signale sind in einer Entity vergleichbar mit globalen Variablen.
Entities sind in der objektorientierten Programmierung vergleichbar mit Klassen. Innerhalb dieser Entities muss eine Architecture erstellt werden, die wiederum das eigentliche Verhalten der Komponente beschreibt. Innerhalb einer Entity kann es nur eine Architecture geben. Die Architecture selbst kann allerdings in verschiedene Prozesse unterteilt sein, die sich wiederum mit Methoden in der objektorientierten Programmierung vergleichen lassen.
Innerhalb dieser Prozesse steht der Großteil des eigentlichen Codes. Es muss jedoch beachtet werden, dass zwei Prozesse nicht gleichzeitig auf ein Signal zugreifen können um es zu verändern, da bei gleichzeitigem Zugriff von zwei oder mehr Prozessen das Synthesetool nicht weiß, welcher zugewiesene Zustand dem aktuell gewünschten Zustand entspricht.
Alternativ lassen sich innerhalb eines Prozesses Variablen erzeugen, die nur innerhalb dieses Prozesses gültig sind und somit wie lokale Variablen behandelt werden können. Prozesse laufen immer parallel zueinander ab. [Jor18]
4 Gesamtsystem
Das Gesamtsystem besteht aus insgesamt neun Entities, die sich grob in fünf übergeordnete Gruppen einsortieren lassen, wie in Abbildung 4.1 gezeigt.
Im Folgenden werden die einzelnen Entities näher beschrieben und deren Funktionsweise erläutert.
Abbildung 4.1: Blockschaltbild des Gesamtsystems bestehend aus den fünf Komponenten: Takterzeugung, Peripherie, Morseencoder, Morsedecoder und Tonerzeugung mit den jeweils untergeordneten Entities und den Beziehungen untereinander. |
4.1 Clock_Divider
Die Clock_Divider-Entity dient zur Erzeugung aller benötigten Taktsignale, die in den verschiedenen Systembausteinen verwendet werden. Dafür wird, wie in Abbildung 4.2 gezeigt, ein Zähler initialisiert, dessen maximaler Zählerstand gleichzeitig die Frequenz des zu erzeugenden Taktsignals angibt. Bei jeder positiven Taktflanke des zugrundeliegenden 50MHz-Signals, das auf dem Altera Entwicklungsboard DE2-115 zur Verfügung steht, wird der Zähler inkrementiert. Beim Erreichen des halben, maximalen Zählerstandes wird ein STD_ULOGIC-Signal invertiert, wodurch ein symmetrisches Taktsignal erzeugt wird. Insgesamt werden durch diese Methode zwei Taktsignale erzeugt: ein 400Hz-Signal, das für die Ansteuerung eines LCD verwendet wird und ein 12MHz-Signal, welches der Ansteuerung des Audio-Codecs dient. [Wol05]
Abbildung 4.2: Flussdiagramm der Prozesse Clock_Unit_LCD und Clock_Unit_12MHz der Entity Clock_Divider
LIBRARY IEEE; USE IEEE.std_logic_1164.ALL; ENTITY clock_divider IS PORT ( clk : IN STD_ULOGIC; reset : IN STD_ULOGIC; choiceWPM : IN STD_ULOGIC_VECTOR (1 DOWNTO 0); --Auswahlsignal für die Wortgeschwindkeit (ausgewählt durch die letzten beiden Switches) wpm6 : OUT STD_ULOGIC_VECTOR (6 DOWNTO 0); --1. Anzeige für die Wortgeschwindigkeit wpm7 : OUT STD_ULOGIC_VECTOR (6 DOWNTO 0); --2. Anzeige für die Wortgeschwindigkeit clockNC : OUT STD_ULOGIC; --Clock-Signal für eine eingestellte Wortgeschwindigkeit für das Morsesignal clock_400hz_en : OUT STD_ULOGIC; --LCD_Initialising: Das LCD_EN-Signal schaltet mit 400Hz um clock_12MHz_en : OUT STD_ULOGIC --12MHz-Signal für die Ansteuerung des Audio-Codecs ); END clock_divider; ARCHITECTURE structure OF clock_divider IS SIGNAL cnt : NATURAL; --Zähler für den Clock Divider zur Erstellung der Morsegeschwindigkeit SIGNAL cnt_400 : NATURAL; --Zähler zur Erzeugung des 400Hz LCD-Signals SIGNAL CLK_PERIODE: NATURAL RANGE 50*10**6/60 TO 50*10**6/15 := 50*10**6/15; --maximale Zählerstände für die Erstellung der Morsegeschwindigkeit SIGNAL clk_400 : STD_ULOGIC := '0'; --400Hz Taktsignal für das LCD SIGNAL cnt_12MHz : NATURAL; --Zähler zur Erzeugung des 12MHz Audio-Codec-Signals SIGNAL clk_12MHz : STD_ULOGIC := '0'; --12Mhz Taktsignal für den Audio-Codec BEGIN ------------------------------------------------------------------------------------------------ --Prozess um mit Hilfe von einem Zähler das Clock-Signal von 50MHz auf 400Hz herunterzutakten!-- ------------------------------------------------------------------------------------------------ Clock_Unit_LCD: PROCESS (reset, clk) BEGIN IF reset = '0' THEN cnt_400 <= 0; clk_400 <= '0'; ELSIF clk'EVENT AND clk = '1' THEN cnt_400 <= cnt_400 + 1; IF cnt_400 >= (50*10**6 / 400) / 2 THEN clk_400 <= NOT clk_400; cnt_400 <= 0; END IF; clock_400hz_en <= clk_400; END IF; END PROCESS; ------------------------------------------------------------------------------------------------ --Prozess um mit Hilfe von einem Zähler das Clock-Signal von 50MHz auf 12MHz herunterzutakten!-- ------------------------------------------------------------------------------------------------ Clock_12MHz: PROCESS (reset, clk) BEGIN IF reset = '0' THEN cnt_12MHz <= 0; clk_12MHz <= '0'; ELSIF clk'EVENT AND clk = '1' THEN cnt_12MHz <= cnt_12MHz + 1; IF cnt_12MHz >= ((50*10**6) / (12*10**6)) / 2 THEN clk_12MHz <= NOT clk_12MHz; cnt_12MHz <= 0; END IF; clock_12MHz_en <= clk_12MHz; END IF; END PROCESS;
4.1.1 Wortgeschwindigkeit
Neben der bereits beschriebenen Variante zur Signalinvertierung für ein 400Hz- und 12MHz-Signal, wird ein Taktsignal zum Codieren und Decodieren eines Morsesignals benötigt. Da dafür allerdings verschiedene Morsegeschwindigkeiten eingesetzt werden können, muss das Taktsignal im laufenden Betrieb anpassbar sein. Als Auswahlmöglichkeit stehen dafür zwei Schalter auf dem Entwicklungsboard zur Verfügung, durch die das erzeugte Taktsignal auf eine Frequenz von 15Hz, 39Hz oder 60Hz eingestellt werden kann. Diese Geschwindigkeiten korrespondieren mit einer Morsegeschwindkeit von 5Wpm (Wörter pro Minute), 13Wpm und 20Wpm.
Das hierbei erzeugte Signal wird ebenfalls durch einen Zähler realisiert, allerdings ist das erhaltene Signal asymmetrisch und wird nur für eine Periodendauer des 50MHz-Signals aktiviert. [MR13]
Abbildung 4.3: Flussdiagramm des Prozesses Clock_Unit der Entity Clock_Divider
------------------------------------------------------------------------------------------- --Prozess um mit Schaltern eine bestimmte Morsefrequenz / Wortgeschwindigkeit auszuwählen-- ------------------------------------------------------------------------------------------- clockUnit: PROCESS (reset, clk, choiceWPM) BEGIN --Auswahl über die Morsegeschwindigkeit mit 3 Geschwindigkeiten (5WPM = 15Hz, 13WPM = 33Hz, 20WPM = 51Hz) CASE choiceWPM IS WHEN "01" => CLK_PERIODE <= 50*10**6/39; --39Hz-Periode für 13WPM (13/5 * 15Hz = 39Hz) wpm6 <= "0110000"; -- "3" wpm7 <= "1111001"; -- "1" WHEN "10" => CLK_PERIODE <= 50*10**6/60; --60Hz-Periode für 20WPM (20/5 * 15Hz = 60Hz) wpm6 <= "1000000"; -- "0" wpm7 <= "0100100"; -- "2" WHEN OTHERS => CLK_PERIODE <= 50*10**6/15; --15Hz-Periode für 5WPM (66.666 ms - 200ms) (3 Perioden < 240 ms) (Standard-WPM) wpm6 <= "0010010"; -- "5" wpm7 <= "1000000"; -- "0" END CASE; --Generiert die Frequenz entsprechend der Auswahl der Morsegeschwindigkeit als asymmetrishes Signal IF reset='0' THEN cnt <= CLK_PERIODE; clockNC <= '0'; ELSIF clk'EVENT AND clk = '1' THEN IF cnt = 0 THEN clockNC <= '1'; cnt <= CLK_PERIODE; ELSE clockNC <= '0'; cnt <= cnt - 1; END IF; END IF; END PROCESS; END structure;
4.2 Morse / Decoder
Die Entity Morse stellt in diesem Projekt die Top-Level-Entity dar und verbindet die grundlegenden Komponenten des Systems miteinander. Neben der einfachen Verknüpfung wird in dieser Entität allerdings zusätzlich ein Tokensignal erstellt, dass in Abhängigkeit der Länge eines Tastendrucks des designierten Morsetasters entweder "01" bei einer kurzen Betätigung dem Signal von rechts hinzufügt, oder "11" bei einem langen Tastendruck. Wird der Taster allerdings länger nicht mehr betätigt, so wird das Tokensignal als vollständig erkannt, an weitere Systemkomponenten, wie z.B. die LCD-Entity, übergeben und anschließend zurückgesetzt, indem das Signal mit Nullen gefüllt wird. Durch die Unterscheidung von langen und kurzen Tastendrücken kann so ein Morsesignal decodiert werden. [MR13]
Zusätzlich zu der Decodierung wird ein einmaliger Softwarereset beim Einschalten des Systems durchgeführt, der dazu dienen soll alle Komponenten zu Beginn auf einen einheitlichen Betriebszustand zu setzen.
Abbildung 4.4: Flussdiagramm des Prozesses Decoder der Entity Morse
LIBRARY IEEE; USE IEEE.std_logic_1164.ALL; ENTITY morse IS PORT ( clk : IN STD_ULOGIC; --Systemtakt btn_Taster : IN STD_ULOGIC; --Morsetaster Btn. 3 ext_Taster : IN STD_ULOGIC := 'X'; --Externer Taster über GPIO 0 (PIN_AB22) (oben links) reset_btn : IN STD_ULOGIC; --Resetschalter choiceWPM : IN STD_ULOGIC_VECTOR (1 DOWNTO 0); --Schaltersignal zur Auswahl der gewünschten Wortgeschwindigkeit wpm6, wpm7 : OUT STD_ULOGIC_VECTOR (6 DOWNTO 0); --7-Segment-Anzeigen zur Darstellung der aktuellen
--Wortgeschwindigkeit modus : OUT STD_ULOGIC_VECTOR (6 DOWNTO 0); --7-Segment-Anzeige zur Darstellung des aktuellen Betriebsmodus
--(d = Decoder, E = Encoder) led3 : OUT STD_ULOGIC_vector (2 DOWNTO 0); --drei LEDs zur Darstellung der länge einer Morsetasterbetätigung data_cntr : OUT STD_ULOGIC_VECTOR (3 DOWNTO 0); --Steuersignale des LCD, wie z.B. LCD_EN, LCD_RW etc. data_out : OUT STD_ULOGIC_VECTOR (7 DOWNTO 0); --Datensignale für das LCD (sowohl druckbare-, als auch
--Steuerzeichen) enable : IN STD_ULOGIC; --Switch Signal zum Umschalten zwischen dem Decoder und Encoder data_keyboard : INOUT STD_ULOGIC_VECTOR (7 DOWNTO 0); --Datensignal das angibt, welche Taste auf der Tastatur gedrückt
--wurde keyclock : IN STD_ULOGIC; --Taktsignal der PS/2-Tastatur keydata : IN STD_ULOGIC; --serielles Datensignal der PS/2-Tastatur (besteht aus Startbit, 8
--Datenbits, Paritybit und Stop-Bit) done_pressing : INOUT STD_ULOGIC; --gibt an ob die Tastaturtaste losgelassen wurde und die Datenübertragung
--beendet wurde ready_LED : OUT STD_ULOGIC; --Anzeige ob der Audio-Codec bereit ist um benutzt zu werden word : IN STD_ULOGIC; --Signal zur Aktivierung des "Wort"-Modus im Keyboard_Encoder mem_addr_leds : OUT STD_ULOGIC_VECTOR (7 DOWNTO 0); --LEDs zur Darstellung der aktuellen Speicheradresse des RAMs in
--der Entity "Keyboard_Encoder" --WM8731 pins = Pins benötigt für den Audio-Codec-- AUD_BCLK : OUT STD_ULOGIC; --Audio-Codec Bit-Stream Clock AUD_XCK : OUT STD_ULOGIC; --Audio-Codec Chip Clock AUD_ADCLRCK : OUT STD_ULOGIC; --Audio-Codec ADC LR Clock AUD_ADCDAT : IN STD_ULOGIC; --Audio-Codec ADC Data AUD_DACLRCK : OUT STD_ULOGIC; --Audio-Codec DAC LR Clock AUD_DACDAT : OUT STD_ULOGIC; --Audio-Codec DAC Data I2C_DATA : INOUT STD_ULOGIC; --I²C-Datensignal I2C_CLOCK : OUT STD_ULOGIC --I²C-Taktsignal ); END morse; ARCHITECTURE structure OF morse IS COMPONENT press_duration PORT ( clock, reset, enable, button : IN STD_ULOGIC; short_press, long_press, long_pause : OUT STD_ULOGIC ); END COMPONENT; COMPONENT LCD PORT ( clock_400hz_en, reset : IN STD_ULOGIC; data_input : IN STD_ULOGIC_VECTOR (13 DOWNTO 0); data_keyboard : IN STD_ULOGIC_VECTOR (7 DOWNTO 0); data_cntr : OUT STD_ULOGIC_VECTOR (3 DOWNTO 0); data_out : OUT STD_ULOGIC_VECTOR (7 DOWNTO 0); enable_char : IN STD_ULOGIC; enable : IN STD_ULOGIC; done_pressing : IN STD_ULOGIC ); END COMPONENT; COMPONENT clock_divider PORT (clk : IN STD_ULOGIC; reset : IN STD_ULOGIC; choiceWPM : IN STD_ULOGIC_VECTOR (1 DOWNTO 0); wpm6 : OUT STD_ULOGIC_VECTOR (6 DOWNTO 0); wpm7 : OUT STD_ULOGIC_VECTOR (6 DOWNTO 0); clockNC : OUT STD_ULOGIC; clock_400hz_en : OUT STD_ULOGIC; clock_12MHz_en : OUT STD_ULOGIC ); END COMPONENT; COMPONENT keyboard PORT ( clk : IN STD_ULOGIC; reset : IN STD_ULOGIC; keyclock : IN STD_ULOGIC; keydata : IN STD_ULOGIC; enable : IN STD_ULOGIC; data_keyboard : INOUT STD_ULOGIC_VECTOR (7 DOWNTO 0); done_pressing : INOUT STD_ULOGIC ); END COMPONENT; COMPONENT Audio_Codec PORT ( AUD_BCLK : OUT STD_ULOGIC; AUD_XCK : OUT STD_ULOGIC; AUD_ADCLRCK : OUT STD_ULOGIC; AUD_ADCDAT : IN STD_ULOGIC; AUD_DACLRCK : OUT STD_ULOGIC; AUD_DACDAT : OUT STD_ULOGIC; clock_50 : IN STD_ULOGIC; key : IN STD_ULOGIC; reset : IN STD_ULOGIC; FPGA_I2C_SCLK : OUT STD_ULOGIC; FPGA_I2C_SDAT : INOUT STD_ULOGIC; enable : IN STD_ULOGIC; sound : IN STD_ULOGIC; clock_12MHz_en : IN STD_ULOGIC ); END COMPONENT; COMPONENT Keyboard_Encoder PORT ( clock_50 : IN STD_ULOGIC; clockNC : IN STD_ULOGIC; reset : IN STD_ULOGIC; done_pressing : IN STD_ULOGIC; data_keyboard : IN STD_ULOGIC_VECTOR (7 DOWNTO 0); Audio_Out : OUT STD_ULOGIC; word : IN STD_ULOGIC; mem_addr_leds : OUT STD_ULOGIC_VECTOR (7 DOWNTO 0) ); END COMPONENT; SIGNAL short, long, pause : STD_ULOGIC; SIGNAL token : STD_ULOGIC_vector(13 DOWNTO 0); SIGNAL clockNC : STD_ULOGIC; SIGNAL clock_400hz_en : STD_ULOGIC; SIGNAL clock_12MHz_en : STD_ULOGIC; SIGNAL sound : STD_ULOGIC; SIGNAL reset : STD_ULOGIC := '1'; SIGNAL soft_reset : STD_ULOGIC := '1'; SIGNAL button : STD_ULOGIC := '1'; BEGIN PressDur: COMPONENT press_duration PORT MAP ( clock => clk, reset => reset, enable => clockNC, button => button, short_press => short, long_press => long, long_pause => pause ); Display: COMPONENT LCD PORT MAP ( clock_400hz_en => clock_400hz_en, reset => reset, data_input => token, data_cntr => data_cntr, data_out => data_out, enable_char => pause, enable => enable, data_keyboard => data_keyboard, done_pressing => done_pressing -- leds => leds ); ClockUnit: COMPONENT clock_divider PORT MAP ( clk => clk, reset => reset, choiceWPM => choiceWPM, wpm6 => wpm6, wpm7 => wpm7, clockNC => clockNC, clock_400hz_en => clock_400hz_en, clock_12MHz_en => clock_12MHz_en ); Keys: COMPONENT keyboard PORT MAP ( clk => clk, reset => reset, keyclock => keyclock, keydata => keydata, enable => enable, data_keyboard => data_keyboard, done_pressing => done_pressing ); Audio: COMPONENT Audio_Codec PORT MAP ( AUD_BCLK => AUD_BCLK, AUD_XCK => AUD_XCK, AUD_ADCLRCK => AUD_ADCLRCK, AUD_ADCDAT => AUD_ADCDAT, AUD_DACLRCK => AUD_DACLRCK, AUD_DACDAT => AUD_DACDAT, clock_50 => clk, key => button, reset => reset, FPGA_I2C_SCLK => I2C_CLOCK, FPGA_I2C_SDAT => I2C_DATA, enable => enable, sound => sound, clock_12MHz_en => clock_12MHz_en ); Encoder: COMPONENT Keyboard_Encoder PORT MAP ( clock_50 => clk, clockNC => clockNC, reset => reset, done_pressing => done_pressing, data_keyboard => data_keyboard, Audio_Out => sound, word => word, mem_addr_leds => mem_addr_leds ); modus <= "0100001" WHEN enable = '0' ELSE "0000110"; --Anzeige des aktuellen Modus (d = Decoder, E = Encoder) reset <= '0' WHEN reset_btn = '0' OR soft_reset = '1' ELSE '1'; ready_LED <= reset; --Status-LED zur Anzeige wann ein Reset stattfindet button <= '0' WHEN btn_Taster = '0' OR ext_Taster = '0' ELSE '1'; --Möglichkeit den integrierten Taster zu verwenden, oder einen
--externen Taster ('0'); ELSIF clk'EVENT AND clk = '1' THEN IF enable = '0' THEN IF clockNC='1' THEN IF short='1' THEN token <= token(11 DOWNTO 0) & "01"; -- kurzer Impuls ELSIF long='1' THEN token <= token(11 DOWNTO 0) & "11"; -- langer Impuls ELSIF pause='1' THEN -- Ende des Zeichens erreicht -- Anzeige kann angesteuert werden token <= (OTHERS =>'0'); END IF; END IF; END IF; END IF; END PROCESS; led3(0) <= short AND NOT enable; -- kurzer Impuls erkannt led3(1) <= long AND NOT enable; -- langer Impuls erkannt led3(2) <= pause AND NOT enable; -- Pausen-Impuls erkannt END structure;
4.2.1 Press_Duration
Die eigentliche Überprüfung ob es sich bei einem Tastendruck um eine kurze oder lange Betätigung handelt, findet in der Entity Press_Duration statt. Dafür wird bei einer positiven Taktflanke, des in der Entity Clock_Divider ermittelten Morsetaktes, das invertierte Signal des Morsetasters in ein elf Bit breites Signal von rechts hineingeschoben. Der Wert dieses Shiftsignals bestimmt daraufhin die Länge des Tastendrucks und übergibt das Ergebnis an die Entity Morse. Wurde der Taster für ein bis maximal drei Taktzyklen gedrückt, so handelt es sich hierbei um ein kurzes Signal. Bei einer Länge von fünf bis maximal neun Taktzyklen wird das Signal als ein langer Tastendruck registriert. Wird der Taster für zehn oder mehr Taktzyklen nicht betätigt, so handelt es sich hierbei um eine Pause und wird als solche gekennzeichnet. [MR13]
Abbildung 4.5: Flussdiagramm des Prozesses Shiftreg der Entity Press_Duration
LIBRARY IEEE; USE IEEE.std_logic_1164.ALL; ENTITY press_duration IS PORT ( clock : IN STD_ULOGIC; --50MHz Systemtakt reset : IN STD_ULOGIC; --Reset-Signal enable : IN STD_ULOGIC; --Wortgeschwindigkeit button : IN STD_ULOGIC; --Morsetaster-Signal short_press : OUT STD_ULOGIC; --repräsentiert einen kurzen Tastendruck long_press : OUT STD_ULOGIC; --repräsentiert einen langen Tastendruck long_pause : OUT STD_ULOGIC --repräsentiert eine lange Pause ); END press_duration; ARCHITECTURE structure OF press_duration IS SIGNAL sh : STD_ULOGIC_VECTOR (10 DOWNTO 0); --Shift-Signal BEGIN long_pause <= '1' WHEN sh(9 DOWNTO 0)="0000000000" ELSE '0'; --lange Pause wenn das Shift-Signal nur aus Nullen besteht (Taster nicht gedrückt) short_press <= NOT sh(0) AND sh(1) AND NOT (sh(2) AND sh(3) AND sh(4)); --kurzer Tastendruck wenn für einen Takt bis max. drei Takte der Taster gedrückt
--wurde long_press <= '1' WHEN sh(5 DOWNTO 0)="111110" and sh(10 DOWNTO 6)/="11111" ELSE '0'; --langer Tastendruck, wenn für 5 bis max. 9 Takte der Taster gedrückt
--wurde ------------------------------------------------------------------------------------------------------------- --Einshiften des invertierten Button-Signals in ein Shift-Signal zur Überprüfung der Länge des Tastendrucks-- ------------------------------------------------------------------------------------------------------------- SHIFTREG: PROCESS (clock,reset) BEGIN IF reset='0' THEN sh <= (OTHERS=>'0'); ELSIF clock'EVENT AND clock = '1' THEN IF enable='1' THEN sh <= sh(9 DOWNTO 0) & NOT button; END IF; END IF; END PROCESS; END structure;
4.3 Keyboard
Die Keyboard-Entity besteht aus drei Prozessen. Der erste Prozess "Sync" dient allein der Synchronisation des PS/2-Taktsignals und des PS/2-Datensignals, die beide von der Tastatur gesendet werden, sobald eine Taste gedrückt oder losgelassen wird. Bei einer positiven Taktanke des internen 50MHz-Taktsignals werden die beiden eben erwähnten Signale in die neuen Signale "sync_keyclock" und "sync_keydata" überführt. Dadurch ist der aktuelle Status beider normalerweise zeitlich verschobenen Signale aneinander angepasst worden, was es ermöglicht, das übertragene und synchronisierte PS/2-Datensignal im Prozess "Key_Recognition" zu identifizieren und den entsprechenden Hexcode der gedrückten Taste in das Signal "key_pressed" zu schreiben.
Der dritte Prozess "Key_Data_Saving" sorgt dafür, dass der ermittelte Hexcode erst dann an andere Systemkomponenten übergeben wird, wenn eine Taste auf der Tastatur nicht mehr gedrückt wird. Da die PS/2-Signale einen Makecode senden, sobald eine Taste gedrückt wird, wird ein Loslassen der Taste durch einen Breakcode quittiert. Dabei wird zunächst ein "F0" über die Datenleitung des PS/2-Busses gesendet, bevor der eigentliche Hexcode übermittelt wird, der eine spezielle Taste identiziert. Um also zu ermitteln, ob eine Taste nicht mehr gedrückt wird, wird zunächst darauf gewartet, bis ein "F0" in dem acht Bit breiten Signal "key_pressed" gelesen werden kann. Anschließend wird ein Zähler inkrementiert, der immer zurückgesetzt wird, sobald das PS/2-Taktsignal auf LOW steht. Ist das Signal hingegen auf einem HIGH-Potenzial, so wird der Zähler so lange inkrementiert bis er einen Endstand erreicht, den er nur erreichen kann, wenn das PS/2-Taktsignal länger auf HIGH steht, als es zwischen einer negativen und positiven Flanke des kontinuierlichen Taktsignals des Make- oder Breakcodes der Fall ist. Wird der Zählerendstand erreicht, so bedeutet dies im Umkehrschluss, dass die Datenübermittlung abgeschlossen ist und der aktuell übermittelte Wert in "key_pressed" auch gleichzeitig der Endwert der Datenübertragung ist und somit die zuletzt losgelassene Taste repräsentiert. Zusätzlich wird zu diesem Zeitpunkt das Signal "done_pressing" auf ein logisches HIGH gesetzt und bedeutet dem restlichen System, dass eine Taste der Tastatur nun nicht mehr gedrückt wird und dass die Datenübertragung abgeschlossen wurde. [Lar18] [Ter13]
Abbildung 4.6: Flussdiagramm des Prozesses Sync der Entity Keyboard
LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY keyboard IS PORT ( clk : IN STD_ULOGIC; --50Mhz Systemtakt reset : IN STD_ULOGIC; --Reset-Signal keyclock : IN STD_ULOGIC; --PS/2 Taktsignal keydata : IN STD_ULOGIC; --PS/2 Datensignal enable : IN STD_ULOGIC; --Switch Signal zum Umschalten zwischen dem Decoder und Encoder data_keyboard : INOUT STD_ULOGIC_VECTOR (7 DOWNTO 0); --8-Bit breites Datensignal, das die zuletzt losgelassene Taste der PS/2- --Tastatur repräsentiert done_pressing : INOUT STD_ULOGIC --gibt an ob die Tastaturtaste losgelassen wurde und die Datenübertragung beendet wurde ); END keyboard; ARCHITECTURE structure OF keyboard IS SIGNAL keyval : STD_ULOGIC_VECTOR (10 DOWNTO 0); --11-Bit breites Signal zur Aufnahme aller Daten des PS/2-Datensignals SIGNAL key_pressed : STD_ULOGIC_VECTOR (7 DOWNTO 0); --Temporäres Signal in dem der Hexwert einer Taster gespeichert wird SIGNAL sync_keyclock : STD_ULOGIC; --Synchronisiertes PS/2-Taktsignal SIGNAL sync_keydata : STD_ULOGIC; --Synchronisiertes PS/2-Datensignal BEGIN --------------------------------------------------------------- --Synchronisiert den PS/2-Takt und die PS/2-Daten miteinander-- --------------------------------------------------------------- sync: PROCESS (clk, keydata, keyclock) BEGIN IF clk'EVENT AND clk = '1' THEN sync_keyclock <= keyclock; sync_keydata <= keydata; END IF; END PROCESS;
Abbildung 4.7: Flussdiagramm des Prozesses Key_Recognition |
Abbildung 4.8: Flussdiagramm des Prozesses Key_Data_Saving |
------------------------------------------------------- --Shiftet die PS/2-Datenbits nacheinander in "keyval"-- ------------------------------------------------------- key_recognition: PROCESS (keyclock, keydata, reset, enable) BEGIN IF reset = '0' THEN keyval <= "00000000000"; key_pressed <= x"00"; ELSIF sync_keyclock'EVENT AND sync_keyclock ='0' THEN IF enable = '1' THEN keyval <= sync_keydata & keyval (10 DOWNTO 1); key_pressed <= keyval (9 DOWNTO 2); END IF; END IF; END PROCESS; -------------------------------------------------------------------------------------------------- --Überprüft wann die Tastatureingabe abgeschlossen wurde, indem ein------------------------------- --Zähler nach der Break-Code-Analyse so lange zählt, bis die Übertragung abgeschlossen sein muss-- -------------------------------------------------------------------------------------------------- key_data_saving: PROCESS (reset, enable, keyclock, clk) VARIABLE cnt : NATURAL RANGE 0 TO ((50*10**6 / 2000) + 1):= 0; VARIABLE released : NATURAL RANGE 0 TO 1 := 0; BEGIN IF reset = '0' THEN data_keyboard <= x"00"; done_pressing <= '0'; ELSIF clk'EVENT AND clk = '1' THEN IF enable = '1' THEN --bei jedem LOW des PS/2-Taktsignals wird der Zähler "cnt" zurückgesetzt IF keyclock = '0' THEN cnt := 0; done_pressing <= '0'; ELSE --Überprüfen ob es sich um ein Loslassen der Taste handelt (Break-Code startet mit "F0") IF key_pressed = x"F0" OR released = 1 THEN released := 1; cnt := cnt + 1; --erst wen der Zähler seinen Endwert erreichen kann muss die Übertragung vollständig sein IF cnt = (50*10**6 / 2000) THEN data_keyboard <= key_pressed; done_pressing <= '1'; cnt := cnt + 1; ELSIF cnt >= (50*10**6 / 2000) THEN released := 0; END IF; END IF; END IF; ELSE data_keyboard <= x"00"; done_pressing <= '0'; END IF; END IF; END PROCESS; END structure;
4.3.1 Keyboard_Encoder
In der Entity Keyboard_Encoder wird eine gedrückte Taste der PS/2-Tastatur in ein entsprechendes Morsesignal umgewandelt. Dafür muss ein Tokensignal generiert werden, das den entsprechenden Morsecode repräsentiert. Hierfür wird die gleiche Methode benutzt, wie beim Erstellen des Tokensignals in der Morse-Entity. Ein Dit (Punktsymbol des Morsecodes) wird dabei durch "01" gekennzeichnet und ein Dah (Strichsymbol des Morsecodes) durch "11". Insgesamt ist das Tokensignal 14-Bit breit um alle gängigen Morsezeichen (Buchstaben inkl. Umlaute, Zahlen und einige Sonderzeichen) abbilden zu können. Im Gegensatz zu dem Tokensignal in der Morse-Entity wird das Tokensignal hier jedoch umgekehrt beschrieben. In dem Morse-Token befindet sich an den beiden Bits mit der geringsten Wertigkeit das zuletzt eingegebene Morsesignal. Es befindet sich beispielsweise bei einem "A" an dieser Stelle somit ein Dah, repräsentiert mit "11", wohingegen sich in dem Tokensignal im Keyboard_Encoder ein Dit befinden würde, da ein "A" durch ein Dit-Dah (.-) dargestellt wird. Die Generation des Tokensignals findet in dem Prozess "token_generation" statt.
Ist das Tokensignal gesetzt, so wird im Prozess "audio" das Tokensignal vom LSB zum MSB bitpaarweise überprüft. Handelt es sich bei dem Bitpaar um "01", so wird das Signal "sound" für zwei Taktperioden des entsprechenden Taktes der Wortgeschwindigkeit gesetzt. Handelt es sich bei dem Bitpaar hingegen um "11", so wird das Signal für sechs Taktperioden gesetzt. Durch diese Unterscheidung wird ein Dah dreimal so lang wie ein Dit ausgegeben. Weist das Bitpaar allerdings "00" auf, so ist die entsprechende Übertragung des Morsewortes beendet. Gleiches gilt, wenn der Zähler, der die Bitpaare einzeln abfragt, größer als 14 wird, da das Token-Signal zu diesem Zeitpunkt komplett analysiert wurde.
In der Entity Keyboard_Encoder wird allerdings zusätzlich der "Wortmodus" eingeführt. Anstatt nur ein einzelnes Zeichen als Morsecode ausgeben zu können wird hier ein RAM-Speicher verwendet, der das aktuelle Tokensignal nach einer Tastenbetätigung in dem RAM speichert und die Speicheradresse anschließend inkrementiert. Wird im Anschluss an ein oder mehrere solcher Eingaben die "Enter"-Taste auf der Tastatur gedrückt oder die maximale Speicheradresse erreicht, so werden die gespeicherten Tokensignale nacheinander ausgelesen und wie zuvor beschrieben durch die Überprüfung der jeweiligen Bitpaare des Signals als Morsecode durch Audiosignale ausgegeben. Anschließend wird der Prozess "assignment" aufgerufen, indem ein Softwarereset ausgeführt wird. Dabei wird der RAM komplett mit Nullen aufgefüllt, sodass die Audioausgabe nur einmal stattfinden kann.
Abbildung 4.9: Flussdiagramm des Prozesses Assignment der |
Abbildung 4.10: Flussdiagramm des Prozesses Token_Generation der Entity Keyboard_Encoder |
LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.NUMERIC_STD.ALL; ENTITY Keyboard_Encoder IS PORT ( clock_50 : IN STD_ULOGIC; --50Mhz Systemtakt clockNC : IN STD_ULOGIC; --Wortgeschwindigkeit reset : IN STD_ULOGIC; --Reset-Signal done_pressing : IN STD_ULOGIC; --Signal ob eine Taste der Tastatur zuende übertragen hat data_keyboard : IN STD_ULOGIC_VECTOR (7 DOWNTO 0); --Tastaturdaten Audio_Out : OUT STD_ULOGIC; --Signal zum Anschalten des Tons bei einem codierten Morsesignal word : IN STD_ULOGIC; --Schalter zur Umstellung zwischen Buchstaben- und Wortmodus mem_addr_leds : OUT STD_ULOGIC_VECTOR (7 DOWNTO 0) --LEDs zur Anzeige der aktuellen Speicheradresse des RAMs ); END Keyboard_Encoder; ARCHITECTURE structure OF Keyboard_Encoder IS SIGNAL sound : STD_ULOGIC := '0'; SIGNAL cnt : NATURAL := 0; SIGNAL token : STD_ULOGIC_VECTOR (13 DOWNTO 0) := (OTHERS => '0'); SIGNAL token_word : STD_ULOGIC_VECTOR (15 DOWNTO 0) := (OTHERS => '0'); SIGNAL done_old : STD_ULOGIC := '0'; SIGNAL done_rise : STD_ULOGIC := '0'; SIGNAL done_old2 : STD_ULOGIC := '0'; SIGNAL done_rise2 : STD_ULOGIC := '0'; SIGNAL mem_addr : NATURAL RANGE 0 TO 279 := 0; SIGNAL RAM_ADDR : STD_ULOGIC_VECTOR (7 DOWNTO 0); SIGNAL RAM_OUT : STD_ULOGIC_VECTOR (21 DOWNTO 0); SIGNAL RAM_IN : STD_ULOGIC_VECTOR (21 DOWNTO 0); SIGNAL read_write : STD_ULOGIC := '0'; SIGNAL mem_reset_addr : NATURAL RANGE 0 TO 279 := 0; SIGNAL mem_reset_data : STD_ULOGIC_VECTOR (21 DOWNTO 0) := (OTHERS => '0'); SIGNAL soft_reset : STD_ULOGIC := '1'; SIGNAL mem_max : NATURAL RANGE 0 TO 279 := 0; component Word_Memory is port ( mem_clock_2_clk : in STD_ULOGIC := 'X'; -- clk mem_slave_2_address : in STD_ULOGIC_VECTOR(7 downto 0) := (others => 'X'); -- address mem_slave_2_clken : in STD_ULOGIC := 'X'; -- clken mem_slave_2_chipselect : in STD_ULOGIC := 'X'; -- chipselect mem_slave_2_write : in STD_ULOGIC := 'X'; -- write mem_slave_2_readdata : out STD_ULOGIC_VECTOR(21 downto 0); -- readdata mem_slave_2_writedata : in STD_ULOGIC_VECTOR(21 downto 0) := (others => 'X'); -- writedata mem_reset_2_reset : in STD_ULOGIC := 'X'; -- reset mem_reset_2_reset_req : in STD_ULOGIC := 'X' -- reset_req ); end component Word_Memory; BEGIN u0 : component Word_Memory port map ( mem_clock_2_clk => clock_50, -- mem_clock_2.clk mem_slave_2_address => RAM_ADDR, -- mem_slave_2.address mem_slave_2_clken => '1', -- .clken mem_slave_2_chipselect => '1', -- .chipselect mem_slave_2_write => read_write,-- .write mem_slave_2_readdata => RAM_OUT, -- .readdata mem_slave_2_writedata => RAM_IN, -- .writedata mem_reset_2_reset => '0' -- mem_reset_2.reset ); Audio_Out <= sound; --------------------------------------------------------------------------------------------------------------- --Zuweisung der entsprechenden Speicheradresse in Abhängigkeit davon ob ein reset ausgelöst wurde oder nicht,-- --sowie das Leeren des RAM durch Auffüllen des Speichers mit Nullen-------------------------------------------- --------------------------------------------------------------------------------------------------------------- assignment: PROCESS (clock_50, reset, soft_reset) BEGIN IF clock_50'EVENT AND clock_50 = '1' THEN IF reset = '0' OR soft_reset = '1' THEN --Leeren des Speichers RAM_ADDR <= STD_ULOGIC_VECTOR(TO_UNSIGNED(mem_reset_addr,8)); IF mem_reset_addr <= 279 THEN mem_reset_addr <= mem_reset_addr + 1; ELSE mem_reset_addr <= 0; END IF; ELSE --"normale" Zuweisung der Speicheradresse im "normalen" Betrieb RAM_ADDR <= STD_ULOGIC_VECTOR(TO_UNSIGNED(mem_addr,8)); mem_reset_addr <= 0; END IF; END IF; END PROCESS; -------------------------------------------------------------------- --Erzeugung eines gespiegelten Token-Signals zum späteren Auslesen-- -------------------------------------------------------------------- token_generation: PROCESS (reset, done_pressing, data_keyboard) BEGIN IF reset = '0' THEN token <= (OTHERS => '0'); ELSIF done_pressing'EVENT AND done_pressing = '1' THEN CASE data_keyboard IS --Buchstaben WHEN x"1C" => token <="00000000001101"; -- A WHEN x"32" => token <="00000001010111"; -- B WHEN x"21" => token <="00000001110111"; -- C WHEN x"23" => token <="00000000010111"; -- D WHEN x"24" => token <="00000000000001"; -- E WHEN x"2B" => token <="00000001110101"; -- F WHEN x"34" => token <="00000000011111"; -- G WHEN x"33" => token <="00000001010101"; -- H WHEN x"43" => token <="00000000000101"; -- I WHEN x"3B" => token <="00000011111101"; -- J WHEN x"42" => token <="00000000110111"; -- K WHEN x"4B" => token <="00000001011101"; -- L WHEN x"3A" => token <="00000000001111"; -- M WHEN x"31" => token <="00000000000111"; -- N WHEN x"44" => token <="00000000111111"; -- O WHEN x"4D" => token <="00000001111101"; -- P WHEN x"15" => token <="00000011011111"; -- Q WHEN x"2D" => token <="00000000011101"; -- R WHEN x"1B" => token <="00000000010101"; -- S WHEN x"2C" => token <="00000000000011"; -- T WHEN x"3C" => token <="00000000110101"; -- U WHEN x"2A" => token <="00000011010101"; -- V WHEN x"1D" => token <="00000000111101"; -- W WHEN x"22" => token <="00000011010111"; -- X WHEN x"1A" => token <="00000011110111"; -- Y WHEN x"35" => token <="00000001011111"; -- Z --Zahlen WHEN x"45" => token <="00001111111111"; -- 0 WHEN x"16" => token <="00001111111101"; -- 1 WHEN x"1E" => token <="00001111110101"; -- 2 WHEN x"26" => token <="00001111010101"; -- 3 WHEN x"25" => token <="00001101010101"; -- 4 WHEN x"2E" => token <="00000101010101"; -- 5 WHEN x"36" => token <="00000101010111"; -- 6 WHEN x"3D" => token <="00000101011111"; -- 7 WHEN x"3E" => token <="00000101111111"; -- 8 WHEN x"46" => token <="00000111111111"; -- 9 --Sonderzeichen -- WHEN x"" => token <="00000111110111"; -- Å WHEN x"52" => token <="00000011011101"; -- Ä -- WHEN x"" => token <="00000111010111"; -- È -- WHEN x"" => token <="00000101110101"; -- É WHEN x"4C" => token <="00000001111111"; -- Ö WHEN x"54" => token <="00000011110101"; -- Ü WHEN x"4E" => token <="01011111010101"; -- ß -- WHEN x"" => token <="00110111011101"; -- ; -- WHEN x"" => token <="00010111110101"; -- ? WHEN x"4A" => token <="00110101010111"; -- - -- WHEN x"" => token <="00010111110111"; -- _ -- WHEN x"" => token <="00001111011111"; -- Ñ WHEN x"49" => token <="00110111011101"; -- . WHEN x"41" => token <="00111101011111"; -- , -- WHEN x"" => token <="00111111010101"; -- : -- WHEN x"" => token <="00001101111101"; -- ( -- WHEN x"" => token <="00110111110111"; -- ) -- WHEN x"" => token <="00011111111101"; -- ' -- WHEN x"" => token <="00001101010111"; -- = WHEN x"5B" => token <="00000111011101"; -- + -- WHEN x"" => token <="00001101011101"; -- / -- WHEN x"" => token <="00011111011101"; -- @ WHEN x"29" => token <="10000000000000"; -- " " WHEN x"5A" => token <="11000000000000"; -- "ENTER" WHEN OTHERS => token <= (OTHERS => '0'); END CASE; END IF; END PROCESS;
------------------------------------------------------------------------------------------------------- --Auslesen des Token-Signals und Erzeugung des "sound"-Signals zur Ausgabe des codierten Morsesignals-- --sowie Auslesen des RAMs bei der Aktivierung des "Wortmodus" durch Schalterstellung zur Übertragung--- --ganzer Worte / Sätze ------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------- audio: PROCESS (reset, clockNC, token, word) VARIABLE double: NATURAL RANGE 0 TO 16 := 0; VARIABLE state: NATURAL RANGE 0 TO 3 := 0; VARIABLE word_state: NATURAL RANGE 0 TO 2 := 0; VARIABLE state2: NATURAL RANGE 0 TO 2 := 0; BEGIN IF reset = '0' THEN sound <= '0'; cnt <= 0; double := 0; word_state := 0; state2 := 0; state := 0; mem_max <= 0; mem_addr <= 0; read_write <= '1'; RAM_IN <= (OTHERS => '0'); soft_reset <= '0'; token_word <= (OTHERS => '0'); ELSIF clockNC'EVENT AND clockNC = '1' THEN soft_reset <= '0'; --BUCHSTABENMODUS --Codierung einzelner Buchstaben durch paarweises Überprüfen der Bits des Token-Signals IF word = '0' THEN done_rise <= '0'; done_old <= done_pressing; IF done_old = '0' AND done_pressing = '1' THEN done_rise <= '1'; END IF; IF double >= 0 AND double < 14 THEN --Codierung eines "Dits" als Signal mit doppelter Periodendauer der Wortgeschwindigkeit IF token(double + 1 DOWNTO double) = "01" THEN IF cnt >= 2 THEN cnt <= 0; sound <= '0'; double := double + 2; ELSE cnt <= cnt + 1; sound <= '1'; END IF; --Codierung eines "Dahs" als Signal mit sechsfacher Periodendauer der Wortgeschwindigkeit --(3-fache Dit-Länge) ELSIF token(double +1 DOWNTO double) = "11" THEN IF cnt >= 6 THEN cnt <= 0; sound <= '0'; double := double + 2; ELSE cnt <= cnt + 1; sound <= '1'; END IF; --Auslesen beenden, wenn nur noch Nullen im Token-Signal gelesen werden ELSE sound <= '0'; double := 14; END IF; END IF; --WORTMODUS --Codierung ganzer Worte / Sätze durch auslesen des Speichers und dann paarweises Überprüfen der Bits des gelesenen Datenworts ELSIF word = '1' THEN done_rise2 <= '0'; done_old2 <= done_pressing; IF done_old2 = '0' AND done_pressing = '1' THEN done_rise2 <= '1'; END IF; --Daten in den RAM schreiben, wenn eine Taste betätigt wurde und diese Taste KEIN "Enter" war IF (done_rise2 = '1' OR state > 0) AND token /= "11000000000000" AND word_state = 0 THEN CASE state IS --Datens in den RAM schreiben und Speicheradresse inkrementieren WHEN 0 => read_write <= '1'; RAM_IN <= "00000000" & token; state := 1; WHEN 1 => read_write <= '0'; mem_addr <= mem_addr + 1; mem_max <= mem_max + 1; state := 0; WHEN OTHERS => END CASE; --Token-Signal in Token_Word-Signal übertragen, wenn eine Taste gedrückt wurde und diese Taste ein "Enter" war ELSIF done_rise2 = '1' AND token = "11000000000000" THEN token_word(13 DOWNTO 0) <= token; --Datenworte aus dem RAM zurücklesen und anschließend nacheinander in Morsesignale codieren ELSIF (token_word(13 DOWNTO 0) = "11000000000000" OR mem_addr > 279) OR word_state > 0 THEN CASE word_state IS --Einmalig beim Eintritt in diesen Fall soll die Speicheradresse auf Null gesetzt werden --um von Anfang an die Werte aus dem Speicher auszulesen WHEN 0 => mem_addr <= 0; read_write <= '0'; token_word(13 DOWNTO 0) <= RAM_OUT(13 DOWNTO 0); word_state := 1; --Birpaarweises Auslesen der einzelnen Datenworte mit anschließender Inkrementierung der Speicher- / Leseadresse WHEN 1 => IF double >= 0 AND double < 14 THEN IF token_word(double + 1 DOWNTO double) = "01" THEN --Codierung eines "Dits" als Signal mit doppelter Periodendauer der Wortgeschwindigkeit IF cnt >= 2 THEN cnt <= 0; sound <= '0'; double := double + 2; ELSE cnt <= cnt + 1; sound <= '1'; END IF; ELSIF token_word(double +1 DOWNTO double) = "11" THEN --Codierung eines "Dahs" als Signal mit sechsfacher Periodendauer der Wortgeschwindigkeit --(3-fache Dit-Länge) IF cnt >= 6 THEN cnt <= 0; sound <= '0'; double := double + 2; ELSE cnt <= cnt + 1; sound <= '1'; END IF; ELSIF token_word(13 DOWNTO 0) = "10000000000000" THEN --Prüfen ob das Tokenwort ein Leerzeichen repräsentiert --In kombination mit dem cnt = 6 von "double >= 14" ergibt sich eine Länge von 7 "Dits"! => Pause zwischen Worten IF cnt >= 8 THEN cnt <= 0; sound <= '0'; double := 14; state2 := 0; ELSE cnt <= cnt + 1; END IF; --wird wieder ein "Enter" aus dem Speicher ausgelesen ist man am Ende der Übertragung angekommen ELSIF token_word(13 DOWNTO 0) = "11000000000000" THEN sound <= '0'; word_state := 2; --ist die aktuelle Leseadresse größer-gleich der maximalen Adresse in die beim Schreiben gespeichert wurde, --so ist die Übertragung ebenfalls beendet ELSIF mem_addr >= mem_max THEN sound <= '0'; word_state := 2; --wenn im Datenwort nur noch Nullen stehen muss die Leseadresse inkrementiert und anschließend das neue Datenwort ausgelesen werden ELSIF token_word(double +1 DOWNTO double) = "00" AND token_word(13 DOWNTO 0) /= "11000000000000" AND token_word(13 DOWNTO 0) /= "10000000000000" THEN sound <= '0'; cnt <= 0; double := 14; END IF; END IF; --Wenn das Datenwort zuende analysiert wurde, wird die Leseadresse inkrementiert und das nächste Datenwort ausgelesen IF double >= 14 THEN IF cnt >= 6 THEN cnt <= 0; sound <= '0'; double := 0; mem_addr <= mem_addr + 1; token_word(13 DOWNTO 0) <= RAM_OUT(13 DOWNTO 0); ELSE cnt <= cnt + 1; read_write <= '0'; END IF; END IF; --Wenn der SPeicher komplett ausgelesen wurde, dann wird der Speicher durch einen Software-Reset --wieder geleert (siehe Prozess: Assignment) WHEN OTHERS => soft_reset <= '1'; IF cnt >= 6 THEN cnt <= 0; sound <= '0'; double := 0; word_state := 0; mem_max <= 0; mem_addr <= 0; cnt <= 0; read_write <= '0'; sound <= '0'; double := 0; token_word <= (OTHERS => '0'); ELSE cnt <= cnt + 1; read_write <= '1'; RAM_IN <= (Others => '0'); END IF; END CASE; END IF; END IF; END IF; --Ist das Token-Signal komplett ausgelesen worden und eine neue Taste wurde gedrückt, --dann soll das bitpaarweise Auslesen erneut beginnen IF double >= 14 AND (done_rise = '1' AND word = '0') THEN double := 0; END IF; --Ausgabe der Speicheradresse auf einigen der LEDs mem_addr_leds <= STD_ULOGIC_VECTOR(TO_UNSIGNED(mem_addr,8)); END PROCESS; END structure;
4.4 Audioausgabe
Die Audioausgabe umfasst im Wesentlichen drei verschiedene Entities. Die Entity Audio_Codec bildet für dieses gesamte Teilsystem eine untergeordnete Top-Level-Entity, die alle benötigten Entities miteinander verbindet. Der grundsätzliche Aufbau der Audioausgabe beruht auf dem Quellcode der GitHub-Seite des Nutzers "AntonZero" [Ant17]. Die einzelnen Teilkomponenten wurden entsprechend an die Anforderungen des Projektes angepasst.
4.4.1 Audio_Codec
Die Entity Audio_Codec übernimmt die komplette Steuerung der Tonerzeugung, sowie die Ansteuerung des integrierten Audio-Codecs mittels I²C-Signalen.
Das Audiosignal, das letztlich über den Line-Out-Ausgang des Entwicklerboards ausgegeben werden soll, wird zunächst aus einem ROM gelesen, der mithilfe eines "Memory Initialisation Files" (.mif) beim Übertragen des Programms von Quartus II zum Entwicklerboard, so gefüllt wird, dass die gespeicherten Werte eine Periode eines 500Hz-Audiosignals repräsentieren. Im "read_audio_data"-Prozess wird dann bei gedrückter Morsetaste oder einem HIGH-Signal des erhaltenen "sound"-Signals der Entity Keyboard_Encoder, ein Datenwort aus dem ROM ausgelesen und die Speicheradresse des ROMs aus der das aktuelle Datenwort ausgelesen wird, inkrementiert und an die Entity Audio_Generation übergeben. [Ant17]
Weiterhin wird der Audio-Codec in dieser Entity auf einen bestimmten Betriebszustand eingestellt, was sich am Besten durch eine "Finite State Machine" wie in Abbildung 4.12 dargestellt, beschreiben lässt. Dabei werden die einzelnen Betriebsmodi des Audio-Codecs nacheinander eingeschaltet, indem die entsprechenden Daten über ein I²C-Signal an den Audio-Codec gesendet werden. Wird ein Reset ausgelöst, so werden auch zunächst die Einstellungen des Codecs zurückgesetzt, bis der Reset nicht mehr durchgeführt wird. Anschließend wird der Codec erneut initialisiert. [Ter13] [Wol05]
Memory-Initialisation-File
Um die Datei zu erstellen, die den ROM mit Startwerten füllen kann, muss zunächst eine ".wav"-Datei produziert werden, die die gewünschten Audiodaten enthält. Dafür wurde mit dem Programm Audacity eine Datei erzeugt, die bei einer Samplerate von 48KHz genau eine Periode eines 500Hz Sinussignals darstellt. Anschließend wurde die Datei mit einem Hexeditor so bearbeitet, dass nur noch die Daten der Sinusschwingung vorhanden sind. Das bedeutet, dass alle Informationen, die sich auf die Metadaten der Audiodatei beziehen, wie z.B. die Länge des Liedes, aus der Datei entfernt wurden. Anschließend wurde mit einem MATLAB-Skript von der bereits zuvor erwähnten GitHub-Seite [Ant17] die übrig gebliebene Hexdatei (.hex) in ein Memory-Initialisation-File (.mif) umgewandelt, indem die Darstellung der Werte aus der Hexdatei in eine spezielle Tabellenform transformiert wird. [Ant17]
Im Platform Designer von Quartus II kann dann dem dort erstellten ROM-Baustein eine Speichergröße zugewiesen und anschließend die ".mif"-Datei zugeordnet werden.
Abbildung 4.11: Flussdiagramm des Prozesses Read_Audio_Data der Entity Audio_Codec
--Angepasst von: https://github.com/AntonZero/WM8731-Audio-codec-on-DE10Standard-FPGA-board LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.NUMERIC_STD.ALL; ENTITY Audio_Codec IS PORT ( --WM8731 pins = Pins benötigt für den Audio-Codec-- AUD_BCLK : OUT STD_ULOGIC; --Audio-Codec Bit-Stream Clock AUD_XCK : OUT STD_ULOGIC; --Audio-Codec Chip Clock AUD_ADCLRCK : OUT STD_ULOGIC; --Audio-Codec ADC LR Clock AUD_ADCDAT : IN STD_ULOGIC; --Audio-Codec ADC Data AUD_DACLRCK : OUT STD_ULOGIC; --Audio-Codec DAC LR Clock AUD_DACDAT : OUT STD_ULOGIC; --Audio-Codec DAC Data --FPGA pins-- clock_50 : IN STD_ULOGIC; --50MHz Systemtakt key : IN STD_ULOGIC; --Morsetaster reset : IN STD_ULOGIC; --Reset-Signal FPGA_I2C_SCLK : OUT STD_ULOGIC; --I²C-Taktsignal FPGA_I2C_SDAT : INOUT STD_ULOGIC; --I²C-Datensignal enable : IN STD_ULOGIC; --Switch Signal zum Umschalten zwischen dem Decoder und Encoder sound : IN STD_ULOGIC; --Signal zum Anschalten des Tons bei einem codierten Morsesignal clock_12MHz_en : IN STD_ULOGIC --12MHz Taktsignal ); END Audio_Codec; ARCHITECTURE structure OF Audio_Codec IS TYPE State_type IS (reset_codec, slave, USB, Volume, power, interface, lineout, remove_mute, idle); SIGNAL state : State_type := reset_codec; SIGNAL aud_mono: STD_ULOGIC_VECTOR (31 DOWNTO 0) := (OTHERS => '0'); --Mono-Audiosignal SIGNAL read_addr: NATURAL RANGE 0 TO 94 := 0; --Anzahl der Bytes für einen Wellenzug eines 500Hz-Signals bei einer 48kHz Samplerate SIGNAL ROM_ADDR: STD_ULOGIC_VECTOR (6 DOWNTO 0); SIGNAL ROM_OUT: STD_ULOGIC_VECTOR (15 DOWNTO 0); SIGNAL wM_i2c_busy: STD_ULOGIC; --I²C ist noch beschäftigt SIGNAL wM_i2c_done: STD_ULOGIC; --I²C hat fertig übertragen SIGNAL wM_i2c_send_flag:STD_ULOGIC; --Startkondition zur Übertragung des nächsten Datenwortes SIGNAL wM_i2c_data: STD_ULOGIC_VECTOR (15 DOWNTO 0); --I²C Daten SIGNAL DA_CLR: STD_ULOGIC; --Audio-Codec DAC Data COMPONENT Memory_System is PORT ( mem_clock_clk : in std_ulogic := 'X'; -- clk mem_slave_address : in std_ulogic_vector(6 downto 0) := (others => 'X'); -- address mem_slave_debugaccess : in std_ulogic := 'X'; -- debugaccess mem_slave_clken : in std_ulogic := 'X'; -- clken mem_slave_chipselect : in std_ulogic := 'X'; -- chipselect mem_slave_write : in std_ulogic := 'X'; -- write mem_slave_readdata : out std_ulogic_vector(15 downto 0); -- readdata mem_slave_writedata : in std_ulogic_vector(15 downto 0) := (others => 'X'); -- writedata mem_slave_byteenable : in std_ulogic_vector(1 downto 0) := (others => 'X'); -- byteenable mem_reset_reset : in std_ulogic := 'X'; -- reset mem_reset_reset_req : in std_ulogic := 'X' -- reset_req ); END COMPONENT Memory_System; COMPONENT Audio_generation IS PORT ( aud_clock_12: IN STD_ULOGIC; aud_bk: OUT STD_ULOGIC; aud_dalr: OUT STD_ULOGIC; aud_dadat: OUT STD_ULOGIC; aud_data_in: IN STD_ULOGIC_VECTOR (31 DOWNTO 0) ); END COMPONENT Audio_generation; COMPONENT I2C IS PORT ( i2c_busy: OUT STD_ULOGIC; i2c_scl: OUT STD_ULOGIC; i2c_send_flag: IN STD_ULOGIC; i2C_sda: INOUT STD_ULOGIC; i2c_addr: IN STD_ULOGIC_VECTOR (7 DOWNTO 0); i2c_done: OUT STD_ULOGIC; i2c_data: IN STD_ULOGIC_VECTOR (15 DOWNTO 0); i2c_clock_50: IN STD_ULOGIC ); END COMPONENT I2C; BEGIN audio: COMPONENT Audio_Generation PORT MAP ( aud_clock_12 => clock_12MHz_en, aud_bk => AUD_BCLK, aud_dalr => DA_CLR, aud_dadat => AUD_DACDAT, aud_data_in => aud_mono ); wm8731: COMPONENT i2c PORT MAP( i2c_busy => wm_i2c_busy, i2c_scl => FPGA_I2C_SCLK, i2c_send_flag => wm_i2c_send_flag, i2c_sda => FPGA_I2C_SDAT, i2c_addr => "00110100", i2c_done => wm_i2c_done, i2c_data => wm_i2c_data, i2c_clock_50 => clock_50 ); Memory: COMPONENT Memory_System PORT MAP ( mem_clock_clk => clock_12MHz_en, -- mem_clock.clk mem_slave_address => ROM_ADDR, -- mem_slave.address mem_slave_debugaccess => '0', -- .debugaccess mem_slave_clken => '1', -- .clken mem_slave_chipselect => '1', -- .chipselect mem_slave_write => '0', -- .write mem_slave_readdata => ROM_OUT, -- .readdata mem_slave_writedata => (OTHERS => '0'), -- .writedata mem_slave_byteenable => "11", -- .byteenable mem_reset_reset => '0' -- mem_reset.reset ); AUD_XCK <= clock_12MHz_en; AUD_DACLRCK <= DA_CLR; ROM_ADDR <= STD_ULOGIC_VECTOR(TO_UNSIGNED(read_addr,7)); ------------------------------------------------------------------------------------------------- --Senden eines mono-Ausiosignals an die Entity "Audio_Generation" bei Betätigung der Morsetaste-- --oder beim Erhalt eines codierten Morsesignals aus der Entity "Keyboard_Encoder"---------------- ------------------------------------------------------------------------------------------------- Read_Audio_Data: PROCESS (clock_12MHz_en, enable, reset, key, sound) BEGIN IF reset = '0' THEN read_addr <= 0; aud_mono <= (OTHERS => '0'); ELSIF clock_12MHz_en'EVENT AND clock_12MHz_en = '1' THEN IF (key = '1' AND enable = '0') OR (sound = '0' AND enable = '1') THEN --reset read_addr <= 0; aud_mono <= (OTHERS => '0'); ELSIF (key = '0' AND enable = '0') OR (sound = '1' AND enable = '1')THEN aud_mono(15 DOWNTO 0) <= ROM_OUT; --mono sound aud_mono(31 DOWNTO 16) <= ROM_OUT; IF (DA_CLR = '1') THEN IF (read_addr < 94) THEN read_addr <= read_addr + 1; ELSE read_addr <= 0; END IF; END IF; END IF; END IF; END PROCESS;
Abbildung 4.12: Finite State Machine des Prozesses Codec_Properties der Entity Audio_Codec |
--------------------------------------------------------- --Einstellung des Audio-Codecs durch eine State-Machine-- --------------------------------------------------------- Codec_Properties: PROCESS (clock_50, enable, key, reset, wm_i2c_busy) BEGIN IF enable = '0' THEN IF RISING_EDGE (clock_50) THEN --Zurücksetzen der Startkondition sobald der Morsetaster losgelassen wird IF (key = '1') THEN wm_i2c_send_flag <= '0'; END IF; END IF; IF RISING_EDGE(clock_50) AND wm_i2c_busy = '0' THEN IF reset = '0' THEN state <= reset_codec; END IF; CASE state IS WHEN reset_codec => wm_i2c_data(15 DOWNTO 9) <= "0001111"; wm_i2c_data(8 DOWNTO 0) <= "000000000"; wm_i2c_send_flag <= '1'; state <= interface; WHEN slave => wm_i2c_data(15 DOWNTO 9) <= "0000111"; wm_i2c_data(8 DOWNTO 0) <= "000010011"; wm_i2c_send_flag <= '1'; state <= volume; WHEN volume => wm_i2c_data(15 DOWNTO 9) <= "0000010"; wm_i2c_data(8 DOWNTO 0) <= "101111001"; wm_i2c_send_flag <= '1'; state <= USB; WHEN power => wm_i2c_data(15 DOWNTO 9) <= "0000110"; wm_i2c_data(8 DOWNTO 0) <= "000000111"; wm_i2c_send_flag <= '1'; state <= slave; WHEN USB => wm_i2c_data(15 DOWNTO 9) <= "0001000"; wm_i2c_data(8 DOWNTO 0) <= "000000001"; wm_i2c_send_flag <= '1'; state <= lineout; WHEN interface => wm_i2c_data(15 DOWNTO 9) <= "0001001"; wm_i2c_data(8 DOWNTO 0) <= "111111111"; wm_i2c_send_flag <= '1'; state <= power; WHEN lineout => wm_i2c_data(15 DOWNTO 9) <= "0000100"; wm_i2c_data(8 DOWNTO 0) <= "000010010"; wm_i2c_send_flag <= '1'; state <= remove_mute; WHEN remove_mute => wm_i2c_data(15 DOWNTO 9) <= "0000101"; wm_i2c_data(8 DOWNTO 0) <= "000000000"; wm_i2c_send_flag <= '1'; state <= idle; WHEN OTHERS => NULL; END CASE; END IF; END IF; END PROCESS; END structure;
4.4.2 I²C-Signal
Um Zeit und Ressourcen zu sparen wurde die I²C-Entity nicht eigenständig entworfen. Da ein "I²C"-Signal ein standardmäßiges Peripheriebusprotokoll ist, bestand keine Notwendigkeit eine neue Version einer bereits bekannten Systemkomponente zu verwirklichen und wurde deshalb von der GitHub-Seite des Nutzers AntonZero [Ant17] übernommen.
--Übernommen von: https://github.com/AntonZero/WM8731-Audio-codec-on-DE10Standard-FPGA-board LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.NUMERIC_STD.ALL; ENTITY I2C IS PORT ( i2c_busy: OUT STD_ULOGIC; i2c_scl: OUT STD_ULOGIC; i2c_send_flag: IN STD_ULOGIC; i2C_sda: INOUT STD_ULOGIC; i2c_addr: IN STD_ULOGIC_VECTOR (7 DOWNTO 0); i2c_done: OUT STD_ULOGIC; i2c_data: IN STD_ULOGIC_VECTOR (15 DOWNTO 0); i2c_clock_50: IN STD_ULOGIC ); END i2c; ARCHITECTURE structure of I2C IS SIGNAL i2c_clk_en: STD_ULOGIC := '0'; SIGNAL clk_prs: NATURAL RANGE 0 TO 300 := 0; SIGNAL clk_en: STD_ULOGIC := '0'; SIGNAL ack_en: STD_ULOGIC := '0'; SIGNAL clk_i2c: STD_ULOGIC := '0'; SIGNAL get_ack: STD_ULOGIC := '0'; SIGNAL data_index: NATURAL RANGE 0 TO 15 := 0; TYPE fsm IS (st0, st1, st2, st3, st4, st5, st6, st7, st8); SIGNAL i2c_fsm: fsm := st0; BEGIN PROCESS (i2c_clock_50) BEGIN IF i2c_clock_50'EVENT AND i2c_clock_50 = '1' THEN IF (clk_prs < 250) THEN clk_prs <= clk_prs + 1; ELSE clk_prs <= 0; END IF; -- 50% duty cycle clock for i2c IF (clk_prs < 125) THEN clk_i2c <= '1'; ELSE clk_i2c <= '0'; END IF; -- clock for ack on SCL = HIGH IF (clk_prs = 62) THEN ack_en <= '1'; ELSE ack_en <= '0'; END IF; -- clock for data on SCL = LOW IF (clk_prs = 187) THEN clk_en <= '1'; ELSE clk_en <= '0'; END IF; END IF; IF i2c_clock_50'EVENT AND i2c_clock_50 = '1' THEN IF (i2c_clk_en = '1') THEN i2c_scl <= clk_i2c; ELSE i2c_scl <= '1'; END IF; -- ack on SCL = HIGH IF (ack_en = '1') THEN CASE i2c_fsm IS WHEN st3 => -- get ack IF (i2c_sda = '0') THEN i2c_fsm <= st4; --ack data_index <= 15; ELSE i2c_clk_en <= '0'; i2c_fsm <= st0; --nack END IF; WHEN st5 => -- get ack IF (i2c_sda = '0') THEN i2c_fsm <= st6; --ack data_index <= 7; ELSE i2c_fsm <= st0; --nack i2c_clk_en <= '0'; END IF; WHEN st7 => -- get ack IF (i2c_sda = '0') THEN i2c_fsm <= st0; -- ack ELSE i2c_fsm <= st0; --nack i2c_clk_en <= '0'; END IF; WHEN OTHERS => NULL; END CASE; END IF; IF (clk_en = '1') THEN CASE i2c_fsm IS WHEN st0 => -- stand by i2c_sda <= '1'; i2c_busy <= '0'; i2c_done <= '0'; IF (i2c_send_flag = '1') THEN i2c_fsm <= st1; i2c_busy <= '1'; END IF; WHEN st1 => -- start condition i2c_sda <= '0'; i2c_fsm <= st2; data_index <= 7; WHEN st2 => -- send addr i2c_clk_en <= '1'; --start clocking i2c_scl IF (data_index > 0) THEN data_index <= data_index - 1; i2c_sda <= i2c_addr(data_index); ELSE i2c_sda <= i2c_addr(data_index); get_ack <= '1'; END IF; IF (get_ack = '1') THEN get_ack <= '0'; i2c_fsm <= st3; i2c_sda <= 'Z'; END IF; WHEN st4 => -- send 1st 8 bit IF (data_index > 8) THEN data_index <= data_index - 1; i2c_sda <= i2c_data(data_index); ELSE i2c_sda <= i2c_data(data_index); get_ack <= '1'; END IF; IF (get_ack = '1') THEN get_ack <= '0'; i2c_fsm <= st5; i2c_sda <= 'Z'; END IF; WHEN st6 => -- send 2nd 8 bit IF (data_index >0) THEN data_index <= data_index - 1; i2c_sda <= i2c_data(data_index); ELSE i2c_sda <= i2c_data(data_index); get_ack <= '1'; END IF; IF (get_ack = '1') THEN get_ack <= '0'; i2c_fsm <= st7; i2c_sda <= 'Z'; END IF; WHEN st8 => -- stop condition i2c_clk_en <= '0'; i2c_sda <= '0'; i2c_fsm <= st0; i2c_done <= '1'; WHEN OTHERS => NULL; END CASE; END IF; END IF; END PROCESS; END structure;
4.4.3 Audio_Generation
Die Entity Audio_Generation erhält als Eingangssignal das jeweilige Datenwort, das in der Entity Audio_Codec aus dem ROM ausgelesen wurde. Mit einem Zähler wird in dieser Entity das jeweilige Datenwort bitweise an den Audio-Codec seriell als Mono-Signal übermittelt. [Ant17]
Abbildung 4.13: Flussdiagramm des Prozesses Generation der Entity Audio_Generation |
--Angepasst von: https://github.com/AntonZero/WM8731-Audio-codec-on-DE10Standard-FPGA-board LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.NUMERIC_STD.ALL; ENTITY Audio_generation IS PORT ( aud_clock_12: IN STD_ULOGIC; --12MHz Taktsignal aud_bk: OUT STD_ULOGIC; --ausgehendes 12MHz Taktsignal aud_dalr: OUT STD_ULOGIC; --Audio-Codec ADC LR Clock aud_dadat: OUT STD_ULOGIC; --Audio-Codec DAC Data aud_data_in: IN STD_ULOGIC_VECTOR (31 DOWNTO 0) --32-Bit breites Mono-Signal aus der Entity "Audio-Codec" ); END Audio_generation; ARCHITECTURE structure OF Audio_generation IS SIGNAL sample_flag: STD_ULOGIC := '0'; SIGNAL data_index: NATURAL RANGE 0 TO 31 := 0; SIGNAL da_data: STD_ULOGIC_VECTOR (15 DOWNTO 0) := (OTHERS => '0'); SIGNAL da_data_out: STD_ULOGIC_VECTOR (31 DOWNTO 0) := (OTHERS => '0'); SIGNAL aud_prscl: NATURAL RANGE 0 TO 300 := 0; SIGNAL clk_en: STD_ULOGIC := '0'; BEGIN aud_bk <= aud_clock_12; ------------------------------------------------------ --Sendet das Mono-Signal bitweise an den Audio-Codec-- ------------------------------------------------------ Generation: PROCESS (aud_clock_12) BEGIN IF aud_clock_12'EVENT AND aud_clock_12 = '0' THEN aud_dalr <= clk_en; IF (aud_prscl < 250) THEN -- 48k sample rate aud_prscl <= aud_prscl + 1; clk_en <= '0'; ELSE aud_prscl <= 0; da_data_out <= aud_data_in; -- get sample clk_en <= '1'; END IF; IF (clk_en = '1') THEN --send new sample sample_flag <= '1'; data_index <= 31; END IF; IF (sample_flag = '1') THEN IF (data_index > 0) THEN aud_dadat <= da_data_out(data_index); data_index <= data_index - 1; ELSE aud_dadat <= da_data_out(data_index); sample_flag <= '0'; END IF; END IF; END IF; END PROCESS; END structure;
4.5 LC-Display
Das LC-Display wird mithilfe der LCD-Entity angesteuert. Mit einer "Finite State Machine" wird hier das LCD auf einen entsprechenden Betriebsmodus eingestellt. Sollte ein Reset ausgelöst werden, so wird das Display erneut initialisiert. Im regulären Betrieb wird hier allerdings unterschieden ob das System gerade im "Decodermodus" oder im "Encodermodus" arbeitet.
Befindet sich das System im "Decodermodus", so wird das erhaltene Tokensignal aus der Morse-Entity in ein entsprechendes Zeichen umgewandelt, was auf dem Bildschirm dargestellt werden kann.
Ist das System allerdings in den "Encodermodus" versetzt worden, so wird der erhaltene Hexcode der Tastatur nach dem Loslassen einer Taste ebenfalls in ein Zeichen umgewandelt, welches auf dem Display darstellbar ist. Bei einer Umschaltung zwischen den beiden Modi wird die Anzeige des Displays geleert
um Verwirrungen zu vermeiden. [Hit98] [Lar18] [Ter13]
LIBRARY IEEE; USE IEEE.std_logic_1164.ALL; ENTITY LCD IS PORT ( clock_400hz_en : IN STD_ULOGIC; --400Hz Taktsignal reset : IN STD_ULOGIC; --Reset-Signal data_input : IN STD_ULOGIC_VECTOR (13 DOWNTO 0); --Token-Signal des Decoders / Morse data_keyboard : IN STD_ULOGIC_VECTOR (7 DOWNTO 0); --Keyboard-Daten nach Betätigung einer Taste data_cntr : OUT STD_ULOGIC_VECTOR (3 DOWNTO 0); --Steuersignale des LCD, wie z.B. LCD_EN, LCD_RW etc. data_out : OUT STD_ULOGIC_VECTOR (7 DOWNTO 0); --Datensignale für das LCD (sowohl druckbare-, als auch Steuerzeichen) enable_char : IN STD_ULOGIC; --Pausen-Signal der Morse-Entity zur Überprüfung ob ein Token-Signal "fertig" ist enable : IN STD_ULOGIC; --Switch Signal zum Umschalten zwischen dem Decoder und Encoder done_pressing : IN STD_ULOGIC --gibt an ob die Tastaturtaste losgelassen wurde und die Datenübertragung beendet wurde ); END LCD; ARCHITECTURE structure of LCD IS SIGNAL LCD_ON, LCD_EN, LCD_RS, LCD_RW: STD_ULOGIC; --Stuersignale für das LCD SIGNAL LCD_DATA : STD_ULOGIC_VECTOR (7 DOWNTO 0); --Datensignal für das LCD SIGNAL sig_old : STD_ULOGIC; SIGNAL sig_rise: STD_ULOGIC; SIGNAL sig_old2: STD_ULOGIC; SIGNAL sig_rise2: STD_ULOGIC; SIGNAL sig_fall2: STD_ULOGIC; SIGNAL sig_old3: STD_ULOGIC; SIGNAL sig_rise3: STD_ULOGIC; BEGIN -------------------------------------------------------------- --Ansteuerung des LCD mit Initialisierung und Zeichenausgabe-- -------------------------------------------------------------- LCD_Initialising: PROCESS (clock_400hz_en, reset, data_input, enable_char, enable, data_keyboard, done_pressing) VARIABLE ini : NATURAL RANGE 0 TO 9 := 0; VARIABLE next_command : NATURAL RANGE 0 TO 9 := 0; VARIABLE char_cnt : NATURAL RANGE 0 TO 34 := 0; BEGIN --LCD neu initialisieren IF reset = '0' THEN LCD_ON <= '1'; --LCD_ON LCD_EN <= '1'; --LCD_EN LCD_RW <= '0'; --LCD_RW LCD_RS <= '0'; --LCD_RS LCD_DATA <= x"38"; --x"38" = Display OFF, 2-lines of Display in use ( = reset) ini := 0; next_command := 0; char_cnt := 0; ELSIF Clock_400hz_en'EVENT AND Clock_400hz_en = '1' THEN CASE ini IS --LCD Initialisierung WHEN 0 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"38"; --x"38" = Display OFF, 2-lines of Display in use next_command := next_command+1; ini := 8; WHEN 1 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"38"; --x"38" = Display OFF, 2-lines of Display in use next_command := next_command+1; ini := 8; WHEN 2 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"38"; --x"38" = Display OFF, 2-line mode next_command := next_command+1; ini := 8; WHEN 3 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"38"; --x"38" = Display OFF, 2-lines of Display in use (4-mal zum initialisieren!) next_command := next_command+1; ini := 8; WHEN 4 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"08"; --x"08" = Display OFF, Cursor OFF, Blinking Cursor Position OFF (0F = Display
--ON and Cursor ON, Blinking cursor position ON) next_command := next_command+1; ini := 8; WHEN 5 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"01"; --x"01" = Clear Display next_command := next_command+1; ini := 8; WHEN 6 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"0C"; --x"0C" = Display ON (0E = Display ON, Cursor ON and Blinking cursor OFF) next_command := next_command+1; ini := 8; WHEN 7 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"06"; --x"06" = Auto increment address and move cursor to the right next_command := 9; ini := 8; -- Initialisierung ist ab hier abgeschlossen!!! WHEN 8 => --Dieser Zyklus dient dazu das Enable-Signal in LCD_EN auch auf '0' zu setzen. --Ansonsten können die Daten nicht an den Bildschirm übertragen werden!!! LCD_ON <= '1'; LCD_EN <= '0'; ini := next_command; WHEN 9 => IF char_cnt = 16 THEN LCD_RS <= '0'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_ON <= '1'; LCD_DATA <= x"C0"; --Setze Cursor an den Anfang der zweiten Zeile des Displays char_cnt := char_cnt + 1; ini := 8; ELSIF char_cnt = 34 THEN LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"01"; --x"01" = clear Display char_cnt := 0; ini := 8; ELSE -- defaults sig_rise <= '0'; --setzt das Signal "sig_rise" standardmäßig auf ein LOW, da das
--enable_char-Signal NICHT immer eine steigende Flanke hat sig_rise2 <= '0'; sig_rise3 <= '0'; sig_fall2 <= '0'; -- shift value in sig_old <= enable_char; --als altes Signal wird "sig_old" das aktuelle
--standard_char-Signal zugewiesen sig_old2 <= enable; sig_old3 <= done_pressing; -- do some real action IF sig_old = '0' AND enable_char = '1' AND enable = '0' THEN
--es wird geprüft ob sich das alte Signal zum neuen Signal unterscheidet. Ist dies der Fall, gab es eine positive Flanke, da die Zuweisung erst im NÄCHSTEN
--Taktzyklus erfolgt, prüft man somit IMMER die vorherige Zuweisung!!!
--(Quelle: https://www.uni-ulm.de/fileadmin/website_uni_ulm/iui.inst.050/vorlesungen/sose09/lrob/Crashkurs_VHDL.pdf Kap. 4.2.3) sig_rise <= '1'; END IF; IF sig_old2 = '0' AND enable = '1' THEN sig_rise2 <= '1'; ELSIF sig_old2 = '1' AND enable = '0' THEN sig_fall2 <= '1'; END IF; IF sig_old3 = '0' AND done_pressing = '1' AND enable = '1' THEN sig_rise3 <= '1'; END IF; ---------------------------------------- --Ansteuerung des LCD im Decoder-Modus-- ---------------------------------------- If sig_rise = '1' THEN LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '1'; CASE data_input IS --Buchstaben WHEN "00000000000111" => LCD_DATA <=x"41"; -- A WHEN "00000011010101" => LCD_DATA <=x"42"; -- B WHEN "00000011011101" => LCD_DATA <=x"43"; -- C WHEN "00000000110101" => LCD_DATA <=x"44"; -- D WHEN "00000000000001" => LCD_DATA <=x"45"; -- E WHEN "00000001011101" => LCD_DATA <=x"46"; -- F WHEN "00000000111101" => LCD_DATA <=x"47"; -- G WHEN "00000001010101" => LCD_DATA <=x"48"; -- H WHEN "00000000000101" => LCD_DATA <=x"49"; -- I WHEN "00000001111111" => LCD_DATA <=x"4A"; -- J WHEN "00000000110111" => LCD_DATA <=x"4B"; -- K WHEN "00000001110101" => LCD_DATA <=x"4C"; -- L WHEN "00000000001111" => LCD_DATA <=x"4D"; -- M WHEN "00000000001101" => LCD_DATA <=x"4E"; -- N WHEN "00000000111111" => LCD_DATA <=x"4F"; -- O WHEN "00000001111101" => LCD_DATA <=x"50"; -- P WHEN "00000011110111" => LCD_DATA <=x"51"; -- Q WHEN "00000000011101" => LCD_DATA <=x"52"; -- R WHEN "00000000010101" => LCD_DATA <=x"53"; -- S WHEN "00000000000011" => LCD_DATA <=x"54"; -- T WHEN "00000000010111" => LCD_DATA <=x"55"; -- U WHEN "00000001010111" => LCD_DATA <=x"56"; -- V WHEN "00000000011111" => LCD_DATA <=x"57"; -- W WHEN "00000011010111" => LCD_DATA <=x"58"; -- X WHEN "00000011011111" => LCD_DATA <=x"59"; -- Y WHEN "00000011110101" => LCD_DATA <=x"5A"; -- Z --Zahlen WHEN "00001111111111" => LCD_DATA <=x"30"; -- 0 WHEN "00000111111111" => LCD_DATA <=x"31"; -- 1 WHEN "00000101111111" => LCD_DATA <=x"32"; -- 2 WHEN "00000101011111" => LCD_DATA <=x"33"; -- 3 WHEN "00000101010111" => LCD_DATA <=x"34"; -- 4 WHEN "00000101010101" => LCD_DATA <=x"35"; -- 5 WHEN "00001101010101" => LCD_DATA <=x"36"; -- 6 WHEN "00001111010101" => LCD_DATA <=x"37"; -- 7 WHEN "00001111110101" => LCD_DATA <=x"38"; -- 8 WHEN "00001111111101" => LCD_DATA <=x"39"; -- 9 --Sonderzeichen -- WHEN "00000111110111" => LCD_DATA <=x"8F"; -- Å WHEN "00000001110111" => LCD_DATA <=x"E1"; -- Ä -- WHEN "00000111010111" => LCD_DATA <=x"8A"; -- È -- WHEN "00000101110101" => LCD_DATA <=x"90"; -- É WHEN "00000011111101" => LCD_DATA <=x"EF"; -- Ö WHEN "00000001011111" => LCD_DATA <=x"F5"; -- Ü WHEN "01010111110101" => LCD_DATA <=x"E2"; -- ß WHEN "00110111011101" => LCD_DATA <=x"3B"; -- ; WHEN "00010111110101" => LCD_DATA <=x"3F"; -- ? WHEN "00110101010111" => LCD_DATA <=x"2D"; -- - WHEN "00010111110111" => LCD_DATA <=x"5F"; -- _ -- WHEN "00001111011111" => LCD_DATA <=x"A5"; -- Ñ WHEN "00011101110111" => LCD_DATA <=x"2E"; -- . WHEN "00111101011111" => LCD_DATA <=x"2C"; -- , WHEN "00111111010101" => LCD_DATA <=x"3A"; -- : WHEN "00001101111101" => LCD_DATA <=x"28"; -- ( WHEN "00110111110111" => LCD_DATA <=x"29"; -- ) WHEN "00011111111101" => LCD_DATA <=x"27"; -- ' WHEN "00001101010111" => LCD_DATA <=x"3D"; -- = WHEN "00000111011101" => LCD_DATA <=x"2B"; -- + WHEN "00001101011101" => LCD_DATA <=x"2F"; -- / WHEN "00011111011101" => LCD_DATA <=x"40"; -- @ WHEN OTHERS => LCD_DATA <= x"00"; END CASE; char_cnt := char_cnt + 1; ini := 8; ------------------------------------------- --Umschalten zwischen Decoder und Encoder-- ------------------------------------------- --Prüfen ob gerade erst zwischen Encoder und Decoder umgestellt wurde ELSIF sig_rise2 = '1' OR sig_fall2 = '1' THEN LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"01"; --x"01" = clear Display ini := 8; char_cnt := 0; ---------------------------------------- --Ansteuerung des LCD im Encoder-Modus-- ---------------------------------------- --Prüfen ob eine Tastureingabe gerade beendet wurde ELSIF sig_rise3 = '1' THEN LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '1'; CASE data_keyboard IS --Buchstaben WHEN x"1C" => LCD_DATA <=x"41"; -- A WHEN x"32" => LCD_DATA <=x"42"; -- B WHEN x"21" => LCD_DATA <=x"43"; -- C WHEN x"23" => LCD_DATA <=x"44"; -- D WHEN x"24" => LCD_DATA <=x"45"; -- E WHEN x"2B" => LCD_DATA <=x"46"; -- F WHEN x"34" => LCD_DATA <=x"47"; -- G WHEN x"33" => LCD_DATA <=x"48"; -- H WHEN x"43" => LCD_DATA <=x"49"; -- I WHEN x"3B" => LCD_DATA <=x"4A"; -- J WHEN x"42" => LCD_DATA <=x"4B"; -- K WHEN x"4B" => LCD_DATA <=x"4C"; -- L WHEN x"3A" => LCD_DATA <=x"4D"; -- M WHEN x"31" => LCD_DATA <=x"4E"; -- N WHEN x"44" => LCD_DATA <=x"4F"; -- O WHEN x"4D" => LCD_DATA <=x"50"; -- P WHEN x"15" => LCD_DATA <=x"51"; -- Q WHEN x"2D" => LCD_DATA <=x"52"; -- R WHEN x"1B" => LCD_DATA <=x"53"; -- S WHEN x"2C" => LCD_DATA <=x"54"; -- T WHEN x"3C" => LCD_DATA <=x"55"; -- U WHEN x"2A" => LCD_DATA <=x"56"; -- V WHEN x"1D" => LCD_DATA <=x"57"; -- W WHEN x"22" => LCD_DATA <=x"58"; -- X WHEN x"1A" => LCD_DATA <=x"59"; -- Y WHEN x"35" => LCD_DATA <=x"5A"; -- Z --Zahlen WHEN x"45" => LCD_DATA <=x"30"; -- 0 WHEN x"16" => LCD_DATA <=x"31"; -- 1 WHEN x"1E" => LCD_DATA <=x"32"; -- 2 WHEN x"26" => LCD_DATA <=x"33"; -- 3 WHEN x"25" => LCD_DATA <=x"34"; -- 4 WHEN x"2E" => LCD_DATA <=x"35"; -- 5 WHEN x"36" => LCD_DATA <=x"36"; -- 6 WHEN x"3D" => LCD_DATA <=x"37"; -- 7 WHEN x"3E" => LCD_DATA <=x"38"; -- 8 WHEN x"46" => LCD_DATA <=x"39"; -- 9 --Sonderzeichen -- WHEN x"" => LCD_DATA <=x"8F"; -- Å WHEN x"52" => LCD_DATA <=x"E1"; -- Ä -- WHEN x"" => LCD_DATA <=x"8A"; -- È -- WHEN x"" => LCD_DATA <=x"90"; -- É WHEN x"4C" => LCD_DATA <=x"EF"; -- Ö WHEN x"54" => LCD_DATA <=x"F5"; -- Ü WHEN x"4E" => LCD_DATA <=x"E2"; -- ß -- WHEN x"" => LCD_DATA <=x"3B"; -- ; -- WHEN x"" => LCD_DATA <=x"3F"; -- ? WHEN x"4A" => LCD_DATA <=x"2D"; -- - -- WHEN x"" => LCD_DATA <=x"5F"; -- _ -- WHEN x"" => LCD_DATA <=x"A5"; -- Ñ WHEN x"49" => LCD_DATA <=x"2E"; -- . WHEN x"41" => LCD_DATA <=x"2C"; -- , -- WHEN x"" => LCD_DATA <=x"3A"; -- : -- WHEN x"" => LCD_DATA <=x"28"; -- ( -- WHEN x"" => LCD_DATA <=x"29"; -- ) -- WHEN x"" => LCD_DATA <=x"27"; -- ' -- WHEN x"" => LCD_DATA <=x"3D"; -- = WHEN x"5B" => LCD_DATA <=x"2B"; -- + -- WHEN x"" => LCD_DATA <=x"2F"; -- / -- WHEN x"" => LCD_DATA <=x"40"; -- @ WHEN x"29" => LCD_DATA <=x"20"; -- " " WHEN OTHERS => LCD_DATA <= x"00"; END CASE; char_cnt := char_cnt + 1; ini := 8; END IF; END IF; END CASE; --Übertragung der Steuersignale und Datenbits an das LCD data_cntr(3) <= LCD_ON; data_cntr(0) <= LCD_EN; data_cntr(1) <= LCD_RW; data_cntr(2) <= LCD_RS; data_out <= LCD_DATA; END IF; END PROCESS; END structure;
5 Zusammenfassung und Fazit
Im Verlauf der Projektzeit konnten alle geforderten Ziele umgesetzt werden. Neben der einfachen Anzeige der decodierten Morsezeichen auf einem Siebensegment-"FIFO" (First-In-First-Out), wurde zusätzlich ein LCD zur Darstellung der Zeichen verwendet. Weiterhin konnte nach ausgiebiger Recherche der integrierte Audio-Codec und damit auch der Line-Out-Ausgang des Entwicklerboards verwendet werden. Zusätzlich wurde ein "Wortmodus" integriert, der es dem Benutzer erlaubt, nicht nur einzelne Buchstaben, sondern auch ganze Worte oder Sätze in Morsecode zu übermitteln.
Problematisch war bei der Erstellung allerdings, dass die ursprünglich verwendete Version der Entwicklungsumgebung Quartus II nicht in der Lage war die Komponenten des Platform Designers auf dem Board zu aktivieren, wodurch ein Umstieg auf die aktuelle Softwareversion 18.0 notwendig war.
6 Ausblick
Weitere Möglichkeiten dieses Projekt zu erweitern bestehen in der Erstellung einer geeigneten Testbench zur Verifizierung des korrekten Signalflusses und in der Kommunikation mit einem weiteren Entwicklerboard. Für die Kommunikation könnte in diesem Fall der Line-In oder Mic-In Eingang genutzt werden und mithilfe des Audio Codecs decodiert werden.
Auch die Einbindung eines NIOS II Cores könnte interessante Möglichkeiten eröffnen, wie z.B. die Einbindung einer SD-Card zur Langzeitspeicherung von erhaltenen Nachrichten. Die VGA-Schnittstelle könnte ebenfalls eingebunden werden, um beispielsweise eine grafische Benutzeroberäche zu erzeugen, die es einem ermöglicht zwischen verschiedenen Funktionen umschalten zu können.
Literaturverzeichnis
[Ant17] Anton: AntonZero/WM8731-Audio-codec-on-DE10Standard-FPGA-board.
https://github.com/AntonZero/WM8731-Audio-codec-on-DE10Standard-FPGA-board. Version: 2017
[Hit98] Hitachi, Ltd.: HD44780U (LCD-II), (Dot Matrix Liquid Crystal Display Controller/Driver).
(1998). https://www.digikey.com/eewiki/download/attachments/4096079/HD44780.pdf?version=1&modificationDate=1343224991517&api=v2
[Jor18] Jorczyk, Udo: Vorlesung: Entwurf digitaler Systeme. Gelsenkirchen, 2018
[Lar18] Larson, Scott: Digi-Key Electronics Database. https://www.digikey.com/eewiki/display/LOGIC/Home. Version: 2018
[MR13] Molitor, Paul ; Ritter, Jörg: Kompaktkurs VDHL: Mit vielen anschaulichen Beispielen. [2. Au.]. München : Oldenbourg, 2013
(Informatik 10-2012). http://dx.doi.org/10.1524/9783486719659. ISBN 9783486712926
[Ter13] Terasic Technologies: DE2-115 User Manual: World Leading FPGA Based Products and Design Services.
(2013). ftp://ftp.altera.com/up/pub/Altera_Material/Boards/DE2-115/DE2_115_User_Manual.pdf
[Wol05] Wolfson Microelectronics plc: Portable Internet Audio CODEC with Headphone Driver and Programmable Sample Rates
WM8731 / WM8731L. Edinburgh, 2005
Besonderer Dank
Besonderer Dank geht an dieser Stelle an Herrn Prof. Dr. Udo Jorczyk, der es mir ermöglicht hat dieses Projekt im Rahmen einer Semesterarbeit umsetzen zu können, sowie an Herrn Dipl.-Ing.(FH) Oliver Gießelmann, der mir als Betreuer des Projektes mit Rat und Tat zur Seite stand.
Ebenso geht mein Dank an Herrn Prof. Dr. Paul Molitor und Herrn Dr. Jörg Ritter der Martin-Luther-Universität Halle-Wittenberg, die mir erlaubt haben Teile des hier dargestellten Codes aus ihrem Lehrbuch "Kompaktkurs VHDL" [MR13] für meine Dokumentation verwenden zu dürfen.
Download
Projektdateien:
Morsedecoder und -encoder - Projektdateien download
Autor: Kai-Frederik Nessitt
Gelsenkirchen 2018