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.

GesamtsystemBlockschaltbild

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]

 

Clock Unit LCD und 12MHz

 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]Clock Unit WPM

 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 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]

Press Duration

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]Keyboard Sync

 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;

Key Recognition

Key Data Saving

Abbildung 4.7: Flussdiagramm des Prozesses Key_Recognition
der Entity Keyboard

Abbildung 4.8: Flussdiagramm des Prozesses Key_Data_Saving
der Entity Keyboard

-------------------------------------------------------
--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.

Assignment Keyboard Encoder

Token Generation Keyboard Encoder

Abbildung 4.9: Flussdiagramm des Prozesses Assignment der
Entity Keyboard_Encoder

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.

Read Audio Data Audio Codec

 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;

 

AudioCodecFSM

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]

Generation Audio Generation

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