Erweiterung eines Morsedecoders und -encoders mittels VHDL

und Implementierung auf einem FPGA-Entwicklungsboard

--  ---  .-.  ...  .  -.-.  ---  -..  .  .-.  .---  .-.-.-  -----

 


 

1. Einführung

Im Folgenden wird ein Morse De- und Encoder vorgestellt, der im Rahmen einer Projektarbeit für das Fach Entwicklung Digitaler Systeme bei Prof. Dr. Udo Jorczyk weiterentwickelt wurde. Der von uns entwickelte Morse De- und Encoder basiert auf dem von Herrn Nessitt in einer Abschlussarbeit entwickelten Morse De- und Encoder (Morse De- und Encoder mittels FPGA). Dieser wurde jedoch um einige Funktionen erweitert. Im unteren Bereich der Seite unter Downloads finden sich die Projektdateien, die auch den gesamten Programmcode enthalten.

Um die Funktionen des Morse De- und Encoders anschaulich zu erklären, haben wir ein Video vorbereitet.

Video 1:  Funktionen des Morsecoders 2.0

 

Das folgende Bild stellt eine Übersicht über die Funktionen des Morsecoders dar.

Morse-Decoder2.png

IMG_2316_Kopie2.jpg

 

 

 

 

 

 

 

 

 

 

 

 

  Abbildung 1:  Set-up und Funktionen des Morsecoders 2.0

 


2. I2C-Schnittstelle zum Audio Codec

Zunächst wurde ein Problem bei der Übertragung von Daten an den Audio Codec über die I2C-Schnittstelle gelöst. Wir wollen nun explizit darauf hinweisen, dass dieses Problem im ursprünglichen Projekt von Herrn Nessitt, welches in Quartus 18.0 programmiert wurde, nicht aufgetaucht ist und durch die Konvertierung des Projektes nach Quartus 13.0 entstanden ist. In Quartus 13.0 kam es, bei der Synthetisierung der I2C-Schnittstelle zu Timing Fehlern, sodass die Übertragung unvollständig war und die Steuerregister des Audio-Codecs nicht richtig initialisiert werden konnten. Das Problem ist in folgendem Bild dargestellt, welches mit dem SignalTap II Logic Analyzer aufgenommen wurde:

i2c2.png

Abbildung 2:  Darstellung der I2C-Übertragung mit Timingfehler

Es ist zu erkennen, dass bei der Übertragung jedes zweite Datenpaket abgeschnitten wird. Zum Beispiel. dauert die Übertragung des ersten Datenpakets 13FFh deutlich kürzer an, als die des zweiten Datenpakets 1E00h. Dies sollte bei der Übertragung jedoch nicht passieren, da alle Datenpakete gleich lang sind. Dieser Übertragungsfehler ist vermutlich darauf zurückzuführen, dass das Setzen der neuen Sendedaten über ein LOW-aktives Busy-Signal getriggert wird. Wenn diese jedoch zuvor nicht auf HIGH zurückgesetzt wurde, werden die aktuellen Sendedaten direkt überschrieben. Aus diesem Grund haben wir nach jedem Zustand in der I2C-State Machine, also nach dem senden eines jeden Datenpaketes, einen Watezyklus eingebaut. In diesem wartet die State Machine auf eine HIGH-Signal auf der I2C-Busy Leitung. Das nachfolgende Flussdiagramm und der Programmcode stellen die neuen Wartezyklen dar.

I2C.png

 Abbildung 3:  Flussdiagramm der I2C-Wartezyklen

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 <= st8;          -- ack  --st8 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                        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;

 


3. Audio Codec Mikrofon

Nach dem die I2C-Übertragung korrigiert wurde, konnten die Initialisierungsdaten der Register im Audio Codec geändert werden, sodass ein Mikrofon für das Abhören von Morsecodes genutzt werden konnte.

 

3.1 Mikrofon ADC-Kanal Auswertung

Dafür wurden die ADC-Daten des Mikrofonkanals aus dem Audio Codec in der Microphone-Entity ausgewertet. Zunächst wurde der serielle ADC-Kanal mit einer Abtatsrate von 48k samples/s abgetastet und zur Parallelisierung in einem Schieberegister zwischengespeichert. Die ADC-Daten schwanken immer, jedoch mit unterschiedlicher Amplitude und Frequenz, abhängig vom eingespielten Ton. Bei Stille schwankt der Wert nur mit einer kleinen Amplitude um den Basiswert 7D48h. Wird in das Mikrofon ein Ton eingespielt gibt es je nach Lautstärke Ausschläge bis ca. 0000h und FFFFh. Um die vom Mikrofon aufgenommenen Daten zu verarbeiten, wurde der im Schieberegister abgelegt Wert mit einem Schwellwert verglichen. Dieser entspricht mit 3EA4h etwa der Hälfte der im Mikrofon anliegenden Grundspannung bei Stille. Es wurde eine Audio-Schmitt-Trigger implementiert, der ein Signal (audio_peak) ein- und ausschaltet, wenn im Mikrofon ein Ton eingespielt wird.

 Mikrofon.png

Abbildung 4:  Flussdiagramm der Mikrofon-Entity

 

LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;
 
 
ENTITY Microphone IS
PORT (	aufnahme_switch : in std_ulogic;	--Switch der die Aufnahme über das Microphon ermöglicht
			clk_12MHz : in std_ulogic; 				
			clk_49kHz : in std_ulogic;					--DA_CLR, 49k Samples/sec
			reset :in std_ulogic;
			AUD_ADCDAT :in std_ulogic; 		--ADC signal aus dem Audio Codec
			audio_peak : out std_ulogic
        );
 
END Microphone;
 
 
 
ARCHITECTURE structure OF Microphone IS

SIGNAL sample_flag:     STD_ULOGIC          := '0';
SIGNAL data_index:      NATURAL RANGE 0 TO 15       := 0;
signal aud_adcdata_in:	std_ulogic_vector (15 downto 0);
SIGNAL done_sample:		std_ulogic :='0';


begin

process (clk_12MHz, reset, clk_49kHz, sample_flag, AUD_ADCDAT, done_sample)
begin

if reset = '0' then
	audio_peak <= '0';
	sample_flag <= '0';
	done_sample <= '0';
	data_index <= 15;

elsif clk_12MHz'event and clk_12MHz = '1' then

	IF aufnahme_switch = '1' THEN 
				IF clk_49kHz = '1' THEN
					 sample_flag <= '1';
					 data_index <= 15;
				END IF;
							  
				IF (sample_flag = '1') THEN
				
					IF (data_index > 0) THEN
					
						aud_adcdata_in(data_index)	 <= AUD_ADCDAT;
						data_index <= data_index - 1;
						
					ELSIF (data_index = 0) THEN
					
						aud_adcdata_in(data_index)	 <= AUD_ADCDAT;
					
						sample_flag <= '0';
						done_sample <= '1';
						 
					END IF;
					
				END IF;
				
	--------------------Datenschwellwert---------------------------
	--Am microphon liegt ohne eigabe eines Tones immer ein Wert um die x"7D48" an
	--Ein Schwellwert bei x"3EA4" (ungefähr die Hälfte) detektiret Töne in einem Frequenzbereich von ca. 400 bis 800Hz sicher
				IF done_sample = '1' and clk_49kHz = '1' THEN
				
					if aud_adcdata_in < x"3EA4" then  --Mittelwert von Höchstwert ca. 7D48 (Wert wenn nichts ins Microgesprochen wird)
						audio_peak <= '1';
							
					else 
						audio_peak <='0';		--audio_peak ist ohne Ton auf '0', mit Ton zappelt der Wert mit 49kHz.
					end if;			
               
					done_sample <= '0';		
				END IF;           

			END IF;

end if;
end process;
end architecture;

 

3.2 Audio_Rekonstruktion

Dem Ausgangssignal audio_peak wurde mit Hilfe eines Timmers ein eindeutiger HIGH- oder LOW-Pegel zugewiesen. Dabei wird ein Zähler bei einem HIGH-Pegel auf 0 zurückgesetzt und bei einem LOW-Pegel hochgezählt. Stand ein LOW-Pegel für mindestens 55 Systemtakte stabil an wird das Ausgangssignal gesetzt. Das ausgewertete Mikrofonsignal kann über einen Switch als Eingang auf den Morse-Decoder geführt werden.

audio_regeneration.png

Abbildung 5:  Flussdiagramm der Audio_Rekonstruktion-Entity

LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;

-----------------------------------------------------------------------------------------------------------
--zu rekonstruierendes Audiosignal flackert mit 49kHz wenn ein High Pegel detektiert wurde.
--Bei jedem High pegel wird ein counter zurück gesetzt und das reconstruierte Audiosignal auf '0' gesetzt.
--Bei einem low wird erst 55 Takte abgewartet bis das Audiosignal auf '1' gesetzt wird.
-----------------------------------------------------------------------------------------------------------
 
ENTITY Audio_Reconstruction IS
PORT (	audio_peak :in std_ulogic;		--eingehendes zu rekonstruierendes Audiosignal
			clk :in std_ulogic; 				--49k samples/s
			reset :in std_ulogic;
			rec_audio : out std_ulogic		--reconstruiertes Audiosignal, dass wie das Morsetatser Signal funkioniert
        );
 
END Audio_Reconstruction;

ARCHITECTURE structure OF Audio_Reconstruction IS

signal counter_low : natural range 0 to 26107200 :=0;

begin

process (clk, reset, audio_peak)
begin

	if reset = '0' then
		rec_audio <= '1';

	elsif clk'event and clk = '1' then
			
		if audio_peak ='1' then
			counter_low <= 0;				--ist das signal high wird der Counter zurückgesetzt
			
		elsif audio_peak ='0' then
			counter_low <= counter_low + 1;
			
		end if;

				if counter_low > 55 then
					rec_audio <= '1';		--Invertierte Logic um Logic des Morsetatsers zu folgen
					
				elsif counter_low <= 55 then
					rec_audio <= '0';
				end if;
		
	end if;
end process;
end architecture;

 


4. Erweiterung der Tastatur um Steuerzeichen

Um die Eingabe über die Tastatur im Encoder-Wortmodus intuitiver zu gestalten, wurden einige Steuertasten mit Befehlen belegt: BACKSPACE, ENTF, ESC und LEFT/ RIGHT ARROW. Diese führen auf dem LCD-Display besondere Aktionen aus und manipulieren den SRAM, in dem die Zeichen im Wortmodus abgelegt werden.  So ist es möglich einen bereits eingegebenen Text vor dem Absenden nochmals zu bearbeiten.

Dafür wird bei Betätigung einer solchen Taste ein spezielles Token gesetzt. Wird dieses Token erkannt, wird es nicht im SRAM abgelegt, sondern eine besondere Aktion ausgeführt. So löst z.B. ein ESC einen Soft-Reset aus, der den SRAM und das LCD-Display löscht. Bei einem BACKSPACE hingegen wird, anders als beim Schreiben, die Schreibadresse dekrementiert und die SRAM-Zelle auf die der Zeige zeigt gelöscht. Die folgende Tabelle listet die erweiterten Funktionen auf. Die darauf folgenden Programmcodes zeigen die Einbindung der Funktionen sowohl in der Keyboard_Encoder- als auch LCD_driver-Entity.

BACKSPACE:

Löscht das letzte Zeichen vom LCD-Display und aus dem SRAM.

ENTF:

Löscht das Zeichen auf dem gerade der Cursor steht.

ESC:

Löscht sowohl das LCD-Display als auch den SRAM.

LEFT/ RIGHT ARROW:      

Bewegen den Cursor auf dem LCD-Display und den Pointer, der auf die SRAM-Zellen zeigt, sodass diese manipuliert werden können.

 

 

 

 

 

 

 

4.1 Einbindung in Keyboard_Encoder-Entiy

Tastatursteuerzeichen manipulieren in dieser Entity nur den SRAM im Wortmodus. So wird z.B. bei Betätigung der Pfeiltasten LEFT/ RIGHT ARROW die Schreibadresse des SRAM de- oder inkrementiert. Bei einem BACKSPACE wird zunächst die Schreibadresse dekrementiert und dann das Zeichen in der letzten SRAM-Zelle gelöscht.

-----------Sondertasten um die Eingabe über die Tastatur intuitiver zu gestalten, z.B ESC löscht den SRAM und das LCD display----------------
---------------------------------------------------------------------------------------------------------------------------------------------

-- ESC:
	elsif (token_word (13 downto 0) = "11011010000000") then --Bei "Esc" wird ein soft reset ausgeführt, d.h. der SRAM wird gelöscht 
							
				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;
							
--BACKSPACE:		
	elsif (token_word (13 downto 0) = "11010000000000" or state3 > 0) then --" backspace" löscht den letzten SRAM speicher platz
		
		CASE state3 IS
		
			--Datens in den RAM schreiben und Speicheradresse dekrementieren
			WHEN 0 =>
				read_write <= '1';
				mem_addr <= mem_addr - 1;
				mem_max <= mem_max - 1;
				state3 := 1;
			when 1 =>
				 state3 := 2;
				
			WHEN 2 =>							
				read_write <= '0';
				RAM_IN <= (Others => '0'); 
				token_word <= (others => '0');
				state3 := 0;
			WHEN OTHERS =>
			
		END CASE;
							
--LEFT ARROW:							
	elsif (token_word (13 downto 0) = "11011011000000") then--and counter4 /= state4 then --"left arrow" dekrementiert die SRAM Adresse in die geschrieben wird
		read_write <= '0';
		mem_addr <= mem_addr -1;
		token_word <= (others => '0');
								
--RIGHT ARROW:			
	elsif (token_word (13 downto 0) = "11011011010000") then--and counter4 /= state4 then --"right arrow" dekrementiert die SRAM Adresse in die geschrieben wird
		read_write <= '0';
		mem_addr <= mem_addr + 1;
		token_word <= (others => '0');

--ENTF:				
	elsif (token_word (13 downto 0) = "11011000000000") then--and counter4 /= state4 then --"Entf" löscht die SRAM-Zelle auf der gerade der zeiger steht				
		read_write <= '1';
		RAM_IN <= (others => '0');
		token_word <= (others => '0');
						

 

4.2 Einbindung in LCD_driver-Entiy

Tastatursteuerzeichen manipulieren in dieser Entity nur das LC-Display.  Um einen Befehl an das LC-Display zu senden, die keinen Buchstaben druckt, muss das Signal LCD_RS <= '0' auf Null gesetzt werden. Dies signalisiert dem LC-Display, dass z.B. auf der LCD_DATA Leitung ein Befehl statt eines druckbaren Zeichens anliegt. Einige Steuerzeichen müssen aus mehreren Befehlen zusammengsetzt werden, da ein Standard LCD-Driver den Befehl "lösche ein einzelnes Zeichen" nicht kennt. So wird bei einem BACKSPACE beispielsweise der Cursor nach links bewegt, dann wird eine Leertaste gedruckt, danach wir der Cursor erneut nach links bewegt.

--------------------------------------------------------------
	--Ansteuerung des LCD mit Initialisierung und Zeichenausgabe--
	--------------------------------------------------------------
	LCD_Initialising:	PROCESS (clock_400hz_en, reset, data_input, enable_char, enable, data_keyboard, done_pressing, word)
		
		VARIABLE ini				: NATURAL RANGE 0 TO 12 	:= 0;
		VARIABLE next_command	: NATURAL RANGE 0 TO 12 	:=	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)
					sonderzeichen <= (others => '0');
					vga_valid <= '0';
					
					ini := 0;
					next_command := 0;
					char_cnt := 0;
				
				ELSIF Clock_400hz_en'EVENT AND Clock_400hz_en = '1' THEN
					
CASE ini IS
...
WHEN 9 =>
...
...
					 -- Steuertasten mit besonderen Funktionen
					 -- LCD_RS <= '0' aktiviert, dass LCD_DATA ein Befehl statt ein Zeichen darstellt
					 
					 WHEN x"5A" => LCD_DATA <= x"14"; -- "Enter"
										LCD_RS <= '0';
										sonderzeichen <= "100";
				 
					 when x"66" => 
									 LCD_DATA <= x"10"; -- "Backspace" -> bewege curso nach links, dann drucke eine leertaste, danach bewege den curso nach links
									 LCD_RS <= '0';
									 sonderzeichen <= "110";
									 next_command:=10;
									 char_cnt := char_cnt - 2;
					 
					 when x"71" => LCD_DATA <= x"20"; -- "Entf" löscht das aktuelle Zeichen auf dem der Cursour steht
										sonderzeichen <= "101";
										next_command := 12;
										char_cnt := char_cnt - 1;
					
					when x"76" =>  LCD_DATA <= x"01"; -- "Esc" löscht das LCD display
										LCD_RS <= '0';
										
					when x"6B" =>  LCD_DATA <= x"10"; -- "left Arrow"
										LCD_RS <= '0';
										sonderzeichen <= "001";
										char_cnt := char_cnt - 2;
										
					when x"74" =>  LCD_DATA <= x"14"; -- "Right Arrow"
										LCD_RS <= '0';
										sonderzeichen <= "011";
										--char_cnt := char_cnt - 1;
										
					WHEN OTHERS => LCD_DATA <= x"00";  
					 
				END CASE;
					 
				char_cnt := char_cnt + 1;
				
				if LCD_DATA = x"01" and LCD_RS = '0' then
					char_cnt := 0;
				end if;
				
				ini := 8;
								
			END IF;
			
		END IF;	
		
		
when 10 => 
			LCD_ON <= '1';
			LCD_EN <= '1';
			LCD_RW <= '0';
			LCD_RS <= '1';
			
			LCD_DATA <= x"20"; --" " 
			next_command:= 11;
			ini := 8;
--										
when 11 =>
			LCD_ON <= '1';
			LCD_EN <= '1';
			LCD_RW <= '0';
			LCD_RS <= '0';
			
			LCD_DATA <= x"10"; --scrollt cursor nach links
			next_command := 9;
			ini := 8;
			
when 12 => 
			LCD_ON <= '1';
			LCD_EN <= '1';
			LCD_RW <= '0';
			LCD_RS <= '0';
			
			LCD_DATA <= x"10"; --scrollt cursor nach links
			next_command := 9;
			ini := 8;
			
END CASE

 


5. VGA-Schnittstelle

Die VGA Schnittstelle wurde zu großen Teilen von Johnnyjax aus dem Projekt  "vga-kbd-terminal" von Github übernommen und auf den Morsecoder angepasst (https://github.com/Johnnyjax/vga-kbd-terminal). In dem originalen VGA-Entity wurden die PS2-Signale von einer PS2-Tastatur auf einen VGA-Monitor angezeigt. Diese PS2 Signale wurden während der Datenverarbeitung in ASCII-Zeichen umgewandelt. Diese PS2-zu-ASCII-Schnittstelle wurde entfernt. Stattdessen wurde die VGA-Entity direkt an die LCD-Entity angekoppelt, welche mit dem LC-Display über den ASCII-Zeichensatz kommuniziert. So konnten direkt die ASCII-Daten des LC-Display übernommen und an den VGA ausgegeben werden.

Abbildung 3:  Anzeige auf dem VGA-Monitor

 

Der nachfolgende Programmcode zeigt den Prozess in der LCD-Entity, der die Daten an die VGA-Enitiy weitergibt. Zusätzlich wird jedes Mal wenn zwischen Decoder- und Encodermodus gewechselt wird ein "DEC: " oder "ENC: " auf den VGA-Monitor geschrieben.

VGA_ansteuereung: process (clock_50, reset, LCD_DATA, LCD_EN, LCD_RS, sonderzeichen, vga_valid, enable, en_rise, en_fall, en_old, lcd_en_old, lcd_en_fall, state, state2)

begin

if reset = '0' then
	rx_done_tick_sig <= '0';
	vga_out_sig <= (others => '0');
	lcd_en_fall <= '0';
	en_old <= '0';
	en_fall <= '0';
	en_rise <= '0';
	state <= 0;
	state2 <= 0;
	
	
elsif clock_50'event and clock_50 ='1' then
		lcd_en_old <= LCD_EN;
		en_old <= enable;
		
		if en_old ='0' and enable = '1' then --nun in encoder modus
			en_rise <='1';
			rx_done_tick_sig <= '0';
		else
			en_rise <='0';
		end if;
		
		if en_old ='1' and enable = '0' then --nun im decoder modus!!!
			en_fall <='1';
			rx_done_tick_sig <= '0';
		else
			en_fall <='0';
		end if;
		
		if lcd_en_old ='1' and LCD_EN = '0' then --Für Eigegebene Zeichen, liegen nur an wenn LCD_EN auf '0' steht (siehe ini:=8)
			lcd_en_fall <= '1';
		else 
			lcd_en_fall <= '0';
		end if;
		
		if (en_rise ='1' or state > 0) and vga_valid = '1' then -- schreibe "ENC: "
			case state is
			when 0=>
				vga_out_sig <= "01011010"; --ENTER
				rx_done_tick_sig <= '1';--
				state <= state +1;
			when 1=>
				rx_done_tick_sig <= '0';--
				state <= state +1;
			when 2=>
				vga_out_sig <= x"45"; --"E"
				rx_done_tick_sig <= '1';--
				state <= state +1;
			when 3=>
				rx_done_tick_sig <= '0';--
				state <= state +1;
			when 4=>
				vga_out_sig <=x"4E"; --"N"
			   rx_done_tick_sig <= '1';--
				state <= state +1;
			when 5=>
				rx_done_tick_sig <= '0';--
				state <= state +1;
			when 6=>
				vga_out_sig <= x"43"; --"C"
				rx_done_tick_sig <= '1';--
				state <= state +1;
			when 7=>
				rx_done_tick_sig <= '0';--
				state <= state +1;
			when 8=>
				vga_out_sig <= x"3A"; --":"
			   rx_done_tick_sig <= '1';--
				state <= state +1;
			when 9=>
				rx_done_tick_sig <= '0';--
				state <= state +1;
			when 10=>
				vga_out_sig <= x"20"; --" "
			   rx_done_tick_sig <= '1';--
				state <= state +1;	
			when 11=>
				rx_done_tick_sig <= '0';--
				state <= state +1;
			when 12 =>
				en_rise <= '0';
				state <= 13;
			when others =>
				state <= 0;
			end case;
		
		
		elsif (en_fall ='1' or state2 > 0 ) and vga_valid = '1' then -- schreibe "DEC: "
			case state2 is
			when 0=>
				vga_out_sig <= "01011010"; --ENTER
				rx_done_tick_sig <= '1';--
				state2 <= state2 +1;
			when 1=>
				rx_done_tick_sig <= '0';--
				state2 <= state2 +1;
			when 2=>
				vga_out_sig <= x"44"; --"D"
				rx_done_tick_sig <= '1';--
				state2 <= state2 +1;
			when 3=>
				rx_done_tick_sig <= '0';--
				state2 <= state2 +1;
			when 4=>
				vga_out_sig <=x"45"; --"E"
			   rx_done_tick_sig <= '1';--
				state2 <= state2 +1;
			when 5=>
				rx_done_tick_sig <= '0';--
				state2 <= state2 +1;
			when 6=>
				vga_out_sig <= x"43"; --"C"
				rx_done_tick_sig <= '1';--
				state2 <= state2 +1;
			when 7=>
				rx_done_tick_sig <= '0';--
				state2 <= state2 +1;
			when 8=>
				vga_out_sig <= x"3A"; --":"
			   rx_done_tick_sig <= '1';--
				state2 <= state2 +1;
			when 9=>
				rx_done_tick_sig <= '0';--
				state2 <= state2 +1;
			when 10=>
				vga_out_sig <= x"20"; --" "
			   rx_done_tick_sig <= '1';--
				state2 <= state2 +1;
			when 11=>
				rx_done_tick_sig <= '0';--
				state2 <= state2 +1;
			when 12 =>
				en_fall <= '0';
				state2 <= 13;
			when others =>
				state2 <= 0;
			end case;
		
		
		
		elsif lcd_en_fall = '1' and vga_valid = '1' then 
			if sonderzeichen = "000" then 			--buchstaben
				vga_out_sig <= LCD_DATA;
				rx_done_tick_sig <='1';
				
			elsif sonderzeichen /= "000" then --elsif LCD_RS ='0' then
				case sonderzeichen is
					when "100" => --enter
						vga_out_sig <= "01011010"; --PS2 code für enter
					when "110" => --backspace
						vga_out_sig <="01100110"; --Ps2 code für backspace
					when "001" => --left arrow
						vga_out_sig <= "01101011"; ---etc. etc.
					when "011" => --right arrow
						vga_out_sig <= "01110100";
					when "101" => 	--entf
						vga_out_sig <= x"20";
					when others =>
						vga_out_sig <= x"00"; 
				end case;
				rx_done_tick_sig <= '1';
				lcd_en_fall <= '0';			
			end if;
		else
			rx_done_tick_sig <= '0';
		end if;
			
end if;
		rx_done_tick <= rx_done_tick_sig;
		vga_out <= vga_out_sig;
end process;

 


Downloads

Projektdateien:

Morse De- und Encoder 2.0 Projektdateien


Autoren: Tim Michalek und Nina Häselhoff
Gelsenkirchen 2021

_______________________________________________________________________________________________________________________________________________________________


2. Erweiterung eines Morsedecoders und -encoders mittels VHDL

und Implementierung auf einem FPGA-Entwicklungsboard


1. Einführung

Der folgend vorgestellte Code wurde im Rahmen einer Projektarbeit des Moduls Entwurf digitaler Systeme bei Herrn Prof. Dr. Udo Jorczyk erarbeitet und basiert ebenfalls auf dem Projekt Morse De- und Encoder mittels FPGA von Herrn Nessitt. Hierbei wurde eine VGA Schnittstelle, die zu Teilen von Johnnyjax aus dem Projekt  "vga-kbd-terminal" von Github übernommen und stark angepasst wurde, implementiert. (https://github.com/Johnnyjax/vga-kbd-terminal). 

 
Video 2:  Funktionen des Morsecoders mit VGA

2. Encoder-/Decodermodus auf dem VGA

Um vom Encodermodus in den Decodermodus und umgekehrt zu schalten, wird ein switch „enable“ verwendet. Dabei wird bei jedem Umschalten zwischen Encoder- und Decodermodus ein vorgegebener Text auf dem VGA-Bildschirm ausgegeben. Schaltet man also in den Encodermodus so wird folgendes dargestellt: „E: Verschluesselter Morsecode:“. Im Falle des Decodermodus wird „D: Entschluesselter Morsecode:“ geschrieben. Dies wird mithilfe einer Case-Anweisung in der LCD-Component implementiert. Dabei wird ein Signal „switch_enc“ bzw. „switch_dec“ mit einer Range von 0 to 63 definiert. Zuvor wird des Weiteren festgelegt, dass eine steigende Flanke „en_rise = 1“ dem Encodermodus und eine fallende Flanke „en_fall = 1“ dem Decodermodus zugeordnet wird. Somit wird unter Verwendung einer If-Anweisung abgefragt welcher Modus aktuell ansteht. Im Falle, dass en_rise = 1 ist gelangt das Programm in die Case-Anweisung für den Encodermodus. Dabei wird bei jedem „when“ ein Symbol, beispielsweise ein Enter oder Buchstabe, auf den VGA geladen und switch_enc inkrementiert, um in das nächste „when“ zu gelangen. Zum Schluss, wenn der vollständige Text dargestellt ist, wird en_rise wieder auf Null gesetzt, um so sicherzustellen, dass die Case-Anweisung des Decodermodus bei einem nächsten Umschalten aufgerufen werden kann.

If vga_save = '1' and (en_rise = '1' or switch_enc > 0) then -- E: Verschluesselter Morsecode
 Case switch_enc is 
 
 When 0 =>
 vga_out_sig <= "00010000"; -- ENTER
 rx_done_sig <= '1';
 switch_enc <= switch_enc + 1;
 
 When 1 => 
 rx_done_sig <= '0';
 switch_enc <= switch_enc + 1;
 
 When 2 => 
 vga_out_sig <= x"45"; -- E
 rx_done_sig <= '1';
 switch_enc <= switch_enc + 1;
 --en_rise <= '0';
 
.....
 
 When 60 =>
 vga_out_sig <= "00010000"; -- ENTER
 rx_done_sig <= '1';
 switch_enc <= switch_enc + 1;
 
 When 61 => 
 rx_done_sig <= '0';
 switch_enc <= switch_enc + 1;
 
 When 62 =>
 en_rise <= '0';
 switch_enc <= switch_enc + 1;
 
 When others => 
 switch_enc <= 0;
 
 END CASE;
---------------------------------------------------------------------------------------------------------------------
elsif vga_save = '1' and (en_fall = '1' or switch_dec > 0) then --D: Entschluesselter Morsecode

Case switch_dec is

When 0 =>
vga_out_sig <= "00010000"; -- ENTER
rx_done_sig <= '1';
switch_dec <= switch_dec + 1;

When 1 =>
rx_done_sig <= '0';
switch_dec <= switch_dec + 1;

When 2 =>
vga_out_sig <= x"44"; -- D
rx_done_sig <= '1';
switch_dec <= switch_dec + 1;
--en_fall <= '0';

.....

When 60=>
vga_out_sig <= "00010000"; -- ENTER
rx_done_sig <= '1';
switch_dec <= switch_dec + 1;

When 61 =>
rx_done_sig <= '0';
switch_dec <= switch_dec + 1;

When 62 =>
en_fall <= '0';
switch_dec <= switch_dec + 1;

When others =>
switch_dec <= 0;

END CASE;

3. Debouncer für den Enable-Switch

Da es beim Umschalten zwischen Encoder- und Decodermodus zu Problemen bei der Signalübertragung kam (unvollständige Textausgabe bzw. Wiederholung von bestimmten Textteilen), wurde zum Entprellen des Schalters ein Debouncer eingebaut. Hierbei wird der Input des Enable-Switches als i_switch definiert. Innerhalb eines Prozesses wird bei jeder steigenden Taktflanke des Clocks mit einer If-Anweisung abgefragt, ob i_switch ungleich dem Signal r_state ist (indem Fall, ob i_switch = 1 ist) und ob r_count kleiner des festgelegten Debounce-Limits ist. Ist dies der Fall wird der Zähler r_count inkrementiert, im anderen Fall wird der Wert von i_switch auf r_state geladen und der Zähler wieder Null gesetzt. Nach Beendigung des Prozesses wird r_state dann auf den Ausgang o_switch geladen.

	Process_SW:PROCESS(i_Clk)
	BEGIN
	
	if i_CLK'EVENT AND i_Clk ='1' then
		if(i_Switch /= r_State and r_Count < c_DEBOUNCE_LIMIT) then		--i_switch = 1
			r_Count <= r_Count + 1;													--r_Count inkrementieren
		
		elsif r_Count = c_DEBOUNCE_LIMIT then
			r_State <= i_Switch;									--wenn r_Count=500.000 dann r_state=1
			r_Count <= 0;																
		else	
		r_Count <=0;
		
		end if;
	end if;	
	END PROCESS;
	
	o_Switch <= r_State;					--o_switch=1

4. Implementierung von Sonderzeichen für VGA und LCD

Des Weiteren werden bestimmte Sonderzeichen wie Enter und Backspace in das Programm eingebaut. Hierzu wird bei Tastendruck auf dem Keyboard ein bestimmtes Hexdatensignal erfasst, beispielsweise x“5A“ bei einem Enter, wodurch folgend eine bestimmte Adresse des LCDs geladen wird. Bei einem Enter wird x“14“ bzw. 0001 0100, also ein leeres Feld, auf den LCD geladen (siehe LCD Correspondence Tabelle [1]). Gleichzeitig werden bestimmte Daten auf den VGA geladen. Diese Daten sind in der kbd_code Component festgelegt. Dort wird für jedes implementierte Sonderzeichen eine Konstante definiert, welcher ein fester Wert zugewiesen wird. Wenn diese Konstante mit dem überlieferten Wert aus der LCD Entity übereinstimmt, werden diese in "tick" gewandelt und weiter an vga_kbd_txt Entity gegeben, wo diese dann in der Cursorpositionsberechnung benötigt werden.LCD_data-sheet.png

In der LCD Enity:
 ------------------------------------------------------------------------------------
 --Sonderzeichen Auswahl--
 ------------------------------------------------------------------------------------
 WHEN x"5A" => LCD_DATA <= x"14"; 				-- "Enter"  
					LCD_RS <= '0';
					vga_out_spec <= "00010000";
 WHEN x"66" => LCD_DATA <= x"10";				-- "Backspace" 
					LCD_RS <= '0';						
					vga_out_spec <= "01100110";
					--next_command:=10;
					char_cnt := char_cnt - 2;
 ------------------------------------------------------------------------------------																 
 WHEN x"76" =>  LCD_DATA <= x"01"; 				-- "Esc" 
					LCD_RS <= '0';						
					vga_out_spec <= "00010001";	
-------------------------------------------------------------------------------------					
 WHEN x"6B" =>  LCD_DATA <= x"10"; 				-- "left Arrow"
					LCD_RS <= '0';						
					vga_out_spec <= "01101011";
					char_cnt := char_cnt - 2;
-------------------------------------------------------------------------------------					
 WHEN x"74" =>  LCD_DATA <= x"14"; 				-- "Right Arrow"
					LCD_RS <= '0';						
					vga_out_spec <= "01110100";
					--char_cnt := char_cnt - 1;
-------------------------------------------------------------------------------------					
 WHEN x"75" =>  LCD_DATA <= x"17"; 				-- "upper Arrow"
					--LCD_RS <= '0';						
					vga_out_spec <= "01110101";
					char_cnt := char_cnt - 2;
-------------------------------------------------------------------------------------					
 WHEN x"72" =>  LCD_DATA <= x"17"; 				-- "down Arrow"
					--LCD_RS <= '0';						
					vga_out_spec <= "01110010";
					--char_cnt := char_cnt - 1;
-------------------------------------------------------------------------------------				
 WHEN OTHERS => LCD_DATA <= x"00"; 
 

In der kb_code Entity:
architecture arch of kb_code is
	constant BRK : std_ulogic_vector(7 downto 0) := "11110000";
	constant ENTER : std_ulogic_vector(7 downto 0) := "00010000";     
	constant BCKSPC : std_ulogic_vector(7 downto 0) := "01100110";
	constant UP_KEY : std_ulogic_vector(7 downto 0) := "01110101";		--x75
	constant DOWN_KEY: std_ulogic_vector(7 downto 0) := "01110010";	        --x72
	constant LEFT_KEY : std_ulogic_vector(7 downto 0) := "01101011";	--x6B
	constant RIGHT_KEY: std_ulogic_vector(7 downto 0) := "01110100"; 	--x74
	constant ESC_KEY	:std_ulogic_vector(7 downto 0) := "00010001"; 	--x11
	
	type statetype is(init, typematic, wait_brk, get_code, waste_cycle);
	signal state_reg, state_next : statetype;
	signal scan_out, w_data : std_ulogic_vector(7 downto 0);						
	signal compare_reg, compare_next : std_ulogic_vector(7 downto 0);
	signal scan_done_tick, got_code_tick : std_ulogic;	

.....

process(clk, reset)
	begin
		if reset = '1' then
			state_reg <= wait_brk;
			compare_reg <= (others => '0');
		elsif(clk'event and clk = '1') then
			state_reg <= state_next;
			compare_reg <= compare_next;
		end if;
	end process;
	
	process(state_reg, scan_done_tick, compare_reg, scan_out)
	begin
		got_code_tick <= '0';
		state_next <= state_reg;
		compare_next <= compare_reg;
		enter_tick <= '0';
		up_tick <= '0';
		down_tick <= '0';
		left_tick <= '0';
		right_tick <= '0';
		bck_spc_tick <= '0';
		esc_tick <= '0';
		
		if scan_done_tick ='1' then 	
			case scan_out is
				when BRK =>
				when ENTER =>
					enter_tick <='1';
				when BCKSPC =>
					bck_spc_tick <= '1';
				when LEFT_KEY =>
					left_tick <= '1';	
				when RIGHT_KEY =>
					right_tick <= '1';
				when DOWN_KEY =>
					down_tick <= '1';
				when UP_KEY =>
					up_tick <= '1';
				when ESC_KEY =>
					esc_tick <= '1';
				when others => 
					got_code_tick <= '1';
			end case;
		else 
		got_code_tick <= '0';
		enter_tick <= '0';
		up_tick <= '0';
		down_tick <= '0';
		left_tick <= '0';
		right_tick <= '0';
		bck_spc_tick <= '0';
		esc_tick <= '0';
		end if;		
		
	end process;
end arch;

In der vga-kbd_txt Entity:
	cur_x_next <= (others => '0') when ((we = '1' or right_tick = '1') and cur_x_reg = MAX_X-1) or enter_tick = '1' or (cur_x_reg = 0 and cur_y_reg = 0 and bck_spc_tick = '1') or esc_tick='1' or reset = '1'
	else
	cur_x_reg + 1 when we = '1' or right_tick = '1'
	else
	to_unsigned(MAX_X-1,cur_x_next'length) when (left_tick = '1' or bck_spc_tick = '1') and cur_x_reg = 0 --"001001111"
	else
	cur_x_reg - 1 when left_tick = '1' or bck_spc_tick = '1'
	else
	cur_x_reg;
	----------------------------------------------------------------------------
	cur_y_next <= (others => '0') when (down_tick = '1' and cur_y_reg = MAX_Y-1) or (cur_x_reg = 0 and cur_y_reg = 0 and bck_spc_tick = '1') or (cur_y_reg = MAX_Y-1 and enter_tick ='1')  or esc_tick = '1' or reset = '1'--or (cur_y_reg = MAX_Y-1)
	else
	to_unsigned(MAX_Y-1,cur_y_next'length) when up_tick = '1' and cur_y_reg = 0
	else
	cur_y_reg + 1 when enter_tick = '1' or down_tick = '1' or (we = '1' and cur_x_reg = MAX_X -1)
	else
	cur_y_reg - 1 when up_tick = '1' or (bck_spc_tick = '1' and cur_x_reg = 0)
	else
	cur_y_reg;

5. Farbkombinationen für den VGA

Mithilfe von Switches ist es nun möglich die Schrift-, Cursor- und Hintergrundfarbe anzupassen. Hierbei wurden insgesamt sechs Farbkombinationen ausgewählt. Es werden drei Signale "Colour_font", "Colour_cur" und "Colour_bck" definiert, welchen bei bestimmten Schalterstellungen festgelegte Werte zugewiesen werden. 

Colour_font <= "000" when farbcombi = "000" 
	else "111" when farbcombi = "001"
	else "010" when farbcombi = "010" 
	else "101" when farbcombi = "011" 
	else "110" when farbcombi = "100" 
	else "100" when farbcombi = "101" 
	else "000";  
	
	Colour_Cur <= "000" when farbcombi = "000" 
	else "111" when farbcombi = "001"
	else "010" when farbcombi = "010" 
	else "111" when farbcombi = "011" 
	else "110" when farbcombi = "100" 
	else "100" when farbcombi = "101" 
	else "000";  
	
	Colour_bck <= "111" when farbcombi = "000" 
	else "000" when farbcombi = "001"
	else "000" when farbcombi = "010" 
	else "000" when farbcombi = "011" 
	else "001" when farbcombi = "100" 
	else "110" when farbcombi = "101" 
	else "111" ;
	
	font_rgb			<= Colour_font 	when font_bit = '1' 	else Colour_bck;
	font_rev_rgb 	<= Colour_bck	 	when font_bit ='1' 	else Colour_Cur;

	process(video_on, cursor_on, font_rgb, font_rev_rgb)
	begin
		if video_on = '0' then
			text_rgb <= "000";  --000
		else
			if cursor_on = '1' then
				text_rgb <= font_rev_rgb;
			else
				text_rgb <= font_rgb;
			end if;
		end if;
	end process;


6. Implementierung der Auflösung

Zur Implementierung der Auflösung wird die Pixel Anzahl und der dazugehörige Pixel-Clock benötigt. In diesem Fall haben wir uns für 640x480, 1280x720 und 1920x1080, also SD, HD und Full HD entschieden. Diese drei Auflösungen haben feste Pixel-Clocks, die berechnet werden müssen. Mit Hilfe vom Video Timings Calculator konnten dann die Pixel-Clocks von 25MHz, 74,25 MHz und 148,5MHz und die dazugehörigen Porches und Syncs berechnet werden. Die Clocks werden dann einfachheitshalber mit dem MegaWizard Plug-In Manager erzeugt. Mit 2 Switches und If-Anweisungen können nun die Pixel und Pixel-Clocks beliebig ausgewählt werden. Hinzukommt noch, dass eine maximale Buchstabenfelderanzahl für jede Auflösung berechnet werden muss, da bei einer höheren Auflösung auch mehr Buchstaben dargestellt werden können. Ein Buchstabenfeld ist jeweils 8x16 Pixel breit und hoch (8by16 ASCII Font). Heißt bei einer Auflösung von 1920x1080 passen 1920 / 8 = 240 Buchstaben in x-Richtung und 1080 / 16 = 68 Buchstaben in y-Richtung hinein, insgesamt 16320 Buchstaben. Diese werden alle einfach von Hand berechnet und in eine If-Anweisung eingebaut.

In der vga-kbd_txt Entity:

	Process (rsl_sw)
	begin
	if rsl_sw = "00" then		--1280x720	
		MAX_X := 160;
		MAX_Y := 45;
	elsif rsl_sw = "01" then	--1920x1080
		MAX_X := 240;
		MAX_Y := 68;
	elsif rsl_sw = "11" then	--640x480				
		MAX_X := 80;				--Spalten
		MAX_Y := 30;				--Zeilen	
	end if;
	end Process;

In der vga-sync Entity:

begin if rsl_sw = "00" then --Für SD HD:= 1280; --Horizontal Active Video --1650 HF:= 110; --Horizontal Front Porch HB:= 220; --Horizontal Back Porch --220 HR:= 40; --Horizontal Sync Pulse --40 VD:= 720; --Vertical Active Video --750 VF:= 5; --Vertical Front Porch VB:= 20; --Vertical Back Porch --20 VR:= 5; --Vertical Sync Pulse elsif rsl_sw = "01" then --für HD HD:= 1920; --Horizontal Active Video --2200 HF:= 88; --Horizontal Front Porch HB:= 148; --Horizontal Back Porch --148 HR:= 44; --Horizontal Sync Pulse --44 VD:= 1080; --Vertical Active Video --1125 VF:= 4; --Vertical Front Porch VB:= 36; --Vertical Back Porch VR:= 5; --Vertical Sync Pulse elsif rsl_sw = "11" then --"10" und "11" für FULL HD HD:= 640; --Horizontal Active Video // durch 8 = 80 Buchstaben HF:= 4; --Horizontal Front Porch HB:= 60; --Horizontal Back Porch HR:= 96; --Horizontal Sync Pulse VD:= 480; --Vertical Active Video // durch 16 = 30 Zeilen bzw. Buchstabenreihen VF:= 10; --Vertical Front Porch VB:= 33; --Vertical Back Porch VR:= 2; --Vertical Sync Pulse end if; end process;

In der vga_kbd_txt_top Entity:
	P_dbl_clock <= CLOCK_50 when rsl_sw = "11"	--SD
	else clk_74 when rsl_sw = "00"			--HD
	else clk_148_5;			 		--Full HD
	
	P_clock <= clk_25 when rsl_sw = "11"		--SD
	else clk_74 when rsl_sw = "00"			--HD
	else clk_148_5;					--Full HD
	

7. RAM löschen

 Da ein kompletter RAM nicht mit einer Clock Flanke gelöscht werden kann, muss nach einem Reset oder ESC Event jede einzelne Speicherzelle mit jeweils einer Clock Flanke gelöscht werden. Hierfür wurde ein Counter implementiert, der bis zur maximalen RAM-Größe hochzählt und die Zelle mit "0000000" füllt.

	process(clk)
	begin
		if(clk'event and clk = '1') then
			if(reset ='1' or esc_tick = '1' or delete = '1' ) then
				if(count <= 60000) then
					 count <= count + 1;
					 ram(count) <= (others => '0');--"0000000";
					 delete <= '1';
				else
					 count <= 0;
					 delete <= '0';
				end if;
			else		
				if(we = '1') then	
					ram(to_integer(unsigned(addr_a))) <= din_a;
				end if;
				addr_a_reg <= addr_a;
				addr_b_reg <= addr_b;
			end if;	
		end if;
	end process;
	dout_a <= ram(to_integer(unsigned(addr_a_reg)));
	dout_b <= ram(to_integer(unsigned(addr_b_reg)));

Downloads:

Morse De- und Encoder mit VGA Projektdatein

Autoren: Pauline Bußmann und Gerold Hackenfort

Gelsenkirchen 2021

_______________________________________________________________________________________________________________________________________________________________



3. Erweiterung eines Morsedecoders und -encoders um eine SD-Karten Schnittstelle mittels SPI in VHDL


 1. Einführung

Der folgend vorgestellte Code wurde im Rahmen einer Projektarbeit des Moduls Entwurf digitaler Systeme bei Herrn Prof. Dr. Udo Jorczyk erarbeitet und basiert ebenfalls auf dem Projekt Morse De- und Encoder mittels FPGA von Herrn Nessitt. Hierbei wurde eine SD-Karten-Schnittstelle mittels Spi realisiert. Als Vorlage diente das Projekt "SimpleSDHC" von ibm2030 aus Github (https://github.com/ibm2030/SimpleSDHC.git). Das Ziel des Projekts war es die über eine Tastatur eingegeben Zeichen in ASCII-Zeichenformat auf die SD-Karte zu schreiben und diese auszulesen und als morsedecodiertes Signal über die Soundausgabe auszugeben. 

Die Realisierung erfolgt hauptsächlich in drei Dateien. Die sd_spi Datei aus dem Projekt SimpleSDHC enthält das Protokoll zur Kommunikation mit der SD-Karte. Mithilfe der sd_card Datei erfolgt die Synchronisation und Kommunikation zwischen der Keyboard_Encoder Datei und der sd_spi Datei. Über die Keyboard_Encoder Datei erfolgt, für das Schreiben in die SD-Karte, das Übersetzen der Eingabe des Anwenders in das ASCII-Zeichenformat und für das Auslesen der SD-Karte, die Umwandlung der ASCII-Zeichen in das morsecodierte Signal. 

2. sd_spi Datei

Für unser Projekt haben wir einige Veränderung an der Datei vorgenommen die wir hier kurz erläutern.

Ursprünglich wurde in der Datei mit einem 25 MHz Takt gearbeitet. Da wir jedoch mit einem 50 MHz Takt arbeiten, muss der Parameter slowClockDivider dementsprechend angepasst werden.

entity sd_controller is
	generic (
	clockRate : integer := 50000000;		-- Incoming clock is 50MHz (can change this to 2000 to test Write Timeout)
	slowClockDivider : integer := 128;	-- Basic clock is 50MHz, slow clock for startup is 50MHz/128KHz = 390kHz
	R1_TIMEOUT : integer := 10;			-- Number of bytes to wait before giving up on receiving R1 response
	WRITE_TIMEOUT : integer range 0 to 999 := 500		-- Number of ms to wait before giving up on write completing
	);

In dem Projekt wurde ausschließlich mit einem asynchronen Reset gearbeitet, da dieser Taktunabhängig gesetzt werden kann. Dementsprechend haben wir den Prozess nach diesem Kriterium angepasst.

updateStateVariables: process(clk, reset)
BEGIN

	IF reset = '0' THEN                    
		state <= RST;                
		return_state <= RST;				
		sr_return_state <= RST;           
		cmd_out <= (others=>'1');
		data_in <= (others=>'0');
		dout <= (others=>'0');
		address <= (others=>'0');
		data_out <= (others=>'1');
		card_type <= ct_None;
		byte_counter <= 0;						
		bit_counter <= 0;							
		crc7 <= (others => '0');
		in_crc16 <= (others => '0');
		out_crc16 <= (others => '0');
		crcLow <= (others => '0');
		error <= '1';
		error_code <= ec_NoSDError;
		sdAvail <= '0';
		error <= '0';
		slow_clock <= true;              
		clock_divider <= 0;                
		transfer_data_out <= false;
		sCs <= '1';
		sDin_taken <= '0';
		wr_erase_count <= "00000001";
		
		-- SD outputs
		sclk <= '0';
		cs <= '1';
		mosi <= '1';
		
		-- Interface outputs
		sd_type <= "00";
		sd_busy <= '1';
		sd_error <= '1';
		sd_error_code <= ec_NoSDError;
		dout <= "00000000";
		dout_avail <= '0';
		din_taken <= '0';
		multiple <= false;
		skipFirstR1Byte <= false;	
		
	ELSIF clk'event AND clk = '1' THEN
		
		-- State variables
		state <= new_state;
		if (set_return_state) then return_state <= new_return_state; end if;
		if (set_sr_return_state) then sr_return_state <= new_sr_return_state; end if;
		if (set_cmd_out) then cmd_out <= new_cmd_out; end if;
		data_in <= new_data_in;
		if (set_address) then address <= new_address; end if;
		data_out <= new_data_out;
		if (set_byte_counter) then byte_counter <= new_byte_counter; end if;
		bit_counter <= new_bit_counter;
		error <= new_error;
		error_code <= new_error_code;
		card_type <= new_card_type;
		slow_clock <= new_slow_clock;
		clock_divider <= new_clock_divider;
		crc7 <= new_crc7;
		in_crc16 <= new_in_crc16;
		out_crc16 <= new_out_crc16;
		crcLow <= new_crcLow;
		transfer_data_out <= new_transfer_data_out;
		sCs <= new_cs;
		
		-- SD outputs
		sclk <= new_sclk;
		cs <= new_cs;
		mosi <= new_data_out(7);
		wr_erase_count <= new_wr_erase_count;
		
		-- Interface outputs
		sd_type <= new_card_type;
		sd_busy <= new_busy;
		sd_error <= new_error;
		sd_error_code <= new_error_code;
		
		if set_davail then -- NB can't do this at the same cycle as we set data_in
			sDavail <= '1';
			dout <= data_in;
			dout_avail <= '1';
		elsif sDavail='1' and dout_taken='1' then
			sDavail <= '0';
			dout_avail <= '0';
		end if;
		multiple <= new_multiple;
		skipFirstR1Byte <= new_skipFirstR1Byte;

		-- This latches the din_valid and generates din_latch and din_taken
		if din_valid='0' or (wr='0' and wr_multiple='0') then
			-- Reset din_latch when din_valid is false, or no write in progress
			sDin_taken <= '0';
			din_taken <= '0';
			din_latch <= false;
		elsif din_valid='1' and last_din_valid='0' then
			-- Set din_latch on rising edge of din_valid
			sDin_taken <= '0';
			din_taken <= '0';
			din_latch <= true;
		elsif din_latch and new_din_taken='1' then
			-- Reset din_latch when din_taken rises
			sDin_taken <= '1';
			din_taken <= '1';
			din_latch <= false;
		end if;
		last_din_valid <= din_valid;
	end if;
 end process;

3. sd_card Datei

Wie bereits erwähnt, dient diese Datei zur Synchronisation zwischen der sd_spi und der keyboard_encoder Datei. Mithilfe einer Finite-state maschine (FSM) werden je nach Zustand die entsprechenden Flags gesetzt. Von hier aus kann ein Initialisierungs-, Lese- oder Schreibprozess gestartet werden. Zudem wird der keyboard_encoder-Datei unter anderem mitgeteilt, wann ein Byte zu Ende gelesen wurde.
library IEEE;
USE IEEE.std_logic_1164.all;
USE ieee.std_logic_unsigned.all;
USE STD.textio.all;

USE ieee.numeric_std.ALL;
ENTITY sd_card IS
port (

			reset 			: in std_logic;			-- System reset
			clk 				: in std_logic;			-- twice the SPI clk (max 50MHz)
			cs 				: out std_logic;			-- To SD card
			mosi 				: out std_logic;			-- To SD card
			miso 				: in std_logic;			-- From SD card
			sclk 				: out std_logic;			-- To SD card  
 
			sd_en_led				: buffer std_ulogic;
			sd_finish_led			: buffer std_ulogic;
			sd_init					: buffer std_logic;
			sd_in						: in std_logic_vector (8 downto 0);
			sdstartwrite			: in std_ULOGIC;
			sdstartread				: in std_uLOGIC;
			nextByte					: in std_ULOGIC;
			rdata						: buffer std_logic_vector(7 downto 0);													
			readFinish				: buffer std_ulogic;
			readFinish2				: buffer std_ulogic;
			readNextByte			: in std_ULOGIC;	
			writeFinish				: buffer std_ULOGIC;
			
			sensitivityFlag		: buffer std_uLOGIC
			);
END sd_card;

architecture start_communication of sd_card is

		type t_State is (initState1,initState2, initState3, initState4, initState5, readState1, 
								readState2, readState3, readState4, readState5, readState6,writeState1, 
								writeState2, writeState3, writeState4, writeState5, writeState6, writeState7
		);
		signal state, New_state : t_State;
		
		signal rd 				: std_logic := '0';
		signal rd_multiple 	: std_logic := '0';
		signal wr				: std_logic := '0';
		signal wr_multiple 	: std_logic := '0';
		signal addr 			: std_logic_vector(31 downto 0) := (others => '0');
		signal din 				: std_logic_vector(7 downto 0) := (others => '0');
		signal din_valid 		: std_logic := '0';
		signal dout_taken 	: std_logic := '0';
		signal sd_fsm 			: std_logic_vector(7 downto 0);
		signal sd_error 		: std_logic;
		signal sd_error_code : std_logic_vector(2 downto 0);
		signal sd_busy 		: std_logic;
		signal din_taken 		: std_logic;
		signal dout_avail 	: std_logic;
		signal reset_2 		: std_logic;
		signal dout 			: std_logic_vector(7 downto 0);
		
		signal new_sd_en_led		: std_ulogic;
		signal new_sd_finish_led: std_ulogic;
		signal new_sd_init		: std_ulogic;
		signal new_rdata			: std_LOGIC_VECTOR (7 downto 0);
		signal new_readFinish	: std_ulogic;
		signal new_readFinish2 	: std_ulogic;
		signal new_writeFinish 	: std_ulogic;
		signal new_sensitivityFlag: std_ULOGIC;
		signal new_dout_taken 	: std_logic;
		signal new_din_valid		: std_logic;	
		signal new_rd 				: std_logic;
		signal new_wr 				: std_logic;
		signal new_reset_2  		: std_logic;
		signal new_byte_counter1, byte_counter1 : integer range 0 to 512 := 0;
		signal Tick, new_Tick 	: std_logic;
	
	component sd_controller IS
		GENERIC(
			WRITE_TIMEOUT : integer := 1
			);
		port(
		  
			cs 					: out std_logic;			-- To SD card
			mosi 					: out std_logic;			-- To SD card
			miso 					: in std_logic;			-- From SD card
			sclk 					: out std_logic;			-- To SD card
			card_present 		: in std_logic;			-- From socket - can be fixed to '1' if no switch is present
			card_write_prot 	: in std_logic;			-- From socket - can be fixed to '0' if no switch is present, or '1' to make a Read-Only interface

			rd 					: in std_logic;			-- Trigger single block read
			rd_multiple 		: in std_logic;			-- Trigger multiple block read
			dout 					: out std_logic_vector(7 downto 0);	-- Data from SD card
			dout_avail 			: out std_logic;			-- Set when dout is valid
			dout_taken 			: in std_logic;			-- Acknowledgement for dout
			
			wr 					: in std_logic;			-- Trigger single block write
			wr_multiple 		: in std_logic;			-- Trigger multiple block write
			din 					: in std_logic_vector(7 downto 0);	-- Data to SD card
			din_valid 			: in std_logic;			-- Set when din is valid
			din_taken 			: out std_logic;			-- Ackowledgement for din
			
			addr 					: in std_logic_vector(31 downto 0);	-- Block address
			erase_count 		: in std_logic_vector(7 downto 0); -- For wr_multiple only

			sd_error 			: out std_logic;			-- '1' if an error occurs, reset on next RD or WR
			sd_busy 				: out std_logic;			-- '0' if a RD or WR can be accepted
			sd_error_code 		: out std_logic_vector(2 downto 0); -- See above, 000=No error
			
			
			reset 				: in std_logic;				-- System reset
			clk 					: in std_logic				-- twice the SPI clk (max 50MHz)
			
			-- Optional debug outputs
			--sd_type 				: out std_logic_vector(1 downto 0);	-- Card status (see above)
			--sd_fsm 				: out std_logic_vector(7 downto 0) := "11111111" -- FSM state (see block at end of file)
			
		);
	End Component;
	
	begin
	SDCardUnit: Component	sd_controller
		Port Map (	--clock_25Mhz	=> clock_25Mhz,
						cs 					=> cs,		
						mosi 					=> mosi,
						miso 					=> miso,
						sclk 					=> sclk,
						card_present 		=> '1',
						card_write_prot 	=> '0',
						
						rd 					=> rd,
						rd_multiple 		=> rd_multiple,
						dout 					=> dout,
						dout_avail 			=> dout_avail,
						dout_taken 			=> dout_taken,
						
						wr 					=> wr,
						wr_multiple 		=> wr_multiple,
						din 					=> din,
						din_valid 			=> din_valid,
						din_taken 			=> din_taken,
						
						addr 					=> addr,
						erase_count 		=> "00000010",
						sd_error 			=> sd_error,
						sd_busy 				=> sd_busy,
						sd_error_code		=> sd_error_code,
						
						reset 				=> reset_2,
						clk 					=> clk
						
						-- Optional debug outputs
						--sd_type 				=> sd_state,
						--sd_fsm 				=> sd_fsm
					);
					
			process (clk, reset)
				begin
					if reset = '0' then
						dout_taken 		<= '0';
						din_valid 		<= '0';
						rd 				<= '0';
						wr 				<= '0';
						reset_2 			<= '0';
						sd_en_led 		<= '0';
						sd_finish_led 	<= '0';
						sd_init 			<= '0';
						readFinish 		<= '0';
						readFinish2 	<= '0';
						writeFinish 	<= '0';
						sensitivityFlag<= '0';
						rdata				<= (OTHERS => '0');
						state 			<= initState1;
						byte_counter1	<= 0;
						Tick  			<= '0';
						
					ELSIF clk'event AND clk = '1' THEN 
						state 			<= new_state;
						dout_taken 		<= new_dout_taken;
						din_valid 		<= new_din_valid;
						rd 				<= new_rd;
						wr 				<= new_wr;
						reset_2 			<= new_reset_2;
						sd_en_led 		<= new_sd_en_led;
						sd_finish_led 	<= new_sd_finish_led;
						sd_init 			<= new_sd_init;
						readFinish 		<= new_readFinish;
						readFinish2 	<= new_readFinish2;
						writeFinish 	<= new_writeFinish;
						sensitivityFlag<= new_sensitivityFlag;
						rdata				<= new_rdata;
						byte_counter1	<= new_byte_counter1;
						Tick  			<= new_Tick;
					end if;
			end process;
			
			calculateState: process (state,Tick)
			variable vec9 				: std_logic_vector(8 downto 0);
			
			begin	
				new_state 			<= state; 
				new_dout_taken		<=	dout_taken;
				new_din_valid		<=	din_valid; 
				new_rd				<=	rd;
				new_wr				<=	wr; 
				new_reset_2			<=	reset_2; 
				new_sd_en_led		<=	sd_en_led; 
				new_sd_finish_led	<=	sd_finish_led;
				new_sd_init			<=	sd_init; 
				new_readFinish		<=	readFinish; 
				new_readFinish2	<=	readFinish2;
				new_writeFinish	<=	writeFinish;
				new_sensitivityFlag	<=	sensitivityFlag;
				new_rdata			<= rdata;
				new_byte_counter1	<= byte_counter1;
				new_Tick 			<= Tick;
				
				case state is
					when initState1 =>
						new_dout_taken <= '0';
						new_state <= initState2;
						
					when initState2 =>											
							new_reset_2 <= '1';       --Entfernt das Reset im sd controller task
							new_state <= initState3;
						
					when initState3 =>
							new_state <= initState4;
						
					when initState4 =>
						if sd_busy='0' then    			--warten bis sd nicht mehr busy
							new_state <= initState5;
						else 
							new_Tick <= not(Tick);
						end if;
						
					when initState5 =>
							new_sd_en_led <= '1';
							new_sd_init <= '1';		
							if sdstartread = '1' then						 
								new_state <= readState1;
							elsif sdstartwrite = '1' then
								new_state <= writeState1;
							else 
								new_writeFinish <= '0';
								new_readFinish2 <= '0';
								new_Tick <= not(Tick);
							end if;
							
					when readState1 => 
						new_sd_finish_led <='0';
						-- Read from address 0
						addr <= (9 => '1' ,others=>'0');  --start lese adresse
						new_rd <= '1';  
						new_state <= readState2;				
						new_byte_counter1 <= 3;
						
						
					when readState2 =>
						new_sensitivityFlag <= '0';
						if readNextByte = '1' then
							new_readFinish <= '0';
							if byte_counter1 = 0 then -- springen hier rein falls alle Bytes gelesen wurden
								new_state <= readstate6;
							else
								if dout_avail = '1' then
									new_rdata <= dout;
									new_state <= readstate3;
								else 
									new_Tick <= not(Tick);
								end if;
							end if;
						else 
							new_Tick <= not(Tick);
						end if;
					
					when readState3 =>															
							new_dout_taken <= '1';
							new_state <= readState4;
					
					when readState4 =>
						if dout_avail='0' then
							new_state <= readState5;
						else
							new_Tick <= not(Tick);
						end if;
						
					when readState5 =>
						new_dout_taken <= '0';
						new_byte_counter1 <= byte_counter1 - 1;
						new_sensitivityFlag <= '1';
						new_readFinish <= '1';
						new_state  <= readState2;
					
					when readState6 =>
						new_rd <= '0';
						if sd_busy = '0' then
							new_sd_finish_led <='1';
							new_readFinish2 <= '1';
							new_state <= initState5;  							 
						else
							new_Tick <= not(Tick);
						end if;
						
					when writeState1 =>
						new_sd_finish_led <='0';
						
						addr <= (9 => '1' ,others=>'0');  --start schreib adresse
						new_wr <= '1';
						new_byte_counter1 <= 512;
						new_state <= writeState2;
						
					when writeState2 =>
						if nextByte = '1' then
							vec9 := sd_in; 	        
							din <= vec9(7 downto 0);
							new_din_valid <= '1';
							new_sensitivityFlag <= '1';
							new_state <= writeState3;
						else
							new_Tick <= not(Tick);
						end if;
						
					when writeState3 =>
						new_sensitivityFlag <= '0';
						if new_byte_counter1 = 0 then 	-- springen hier rein falls alle Bytes geschrieben wurden
							new_din_valid <= '0';
							new_state  <= writeState7;
						else
							if din_taken='1' then
								new_state  <= writeState4;
							else
								new_Tick <= not(Tick);
							end if;
						end if;
					
					when writeState4 =>
							new_din_valid <= '0';
							new_state <= writeState5;
						
					when writeState5 =>
						if din_taken='0' then
							new_state <= writeState6;
						else
							new_Tick <= not(Tick);
						end if;
						
					when writeState6 =>
						new_byte_counter1 <= byte_counter1 - 1;  							
						new_state <= writeState2;
				
					when writeState7 =>
						new_wr <= '0';
						if sd_busy = '0' then
							new_sd_finish_led <='1';
							new_writeFinish <= '1';
							new_state <= initState5;
						else
							new_Tick <= not(Tick);
						end if;
				end case;
				
			end process;		
end start_communication;

4. Keyboard_Encoder Datei

Die eingegeben Zeichen sollen in jeweils einer Speicherzelle der SD-Karte abgelegt werden. Dafür müssen die Keyboard-Eingaben in ASCII-Zeichen umgewandelt werden. Dies erfolgt im Prozess "token_generation".

token_generation:	PROCESS (reset, done_pressing, data_keyboard)
	
	BEGIN
	
		IF reset = '0' THEN
			token <= (OTHERS => '0');
			--token_1 <= (OTHERS => '0');
		
		ELSIF done_pressing'EVENT AND done_pressing = '1' THEN
			
			
			CASE data_keyboard IS
					
				--Buchstaben
				 WHEN x"1C" => token <="00000000001101"; token_1 <="001000001"; -- A
				 WHEN x"32" => token <="00000001010111"; token_1 <="001000010"; -- B
				 WHEN x"21" => token <="00000001110111"; token_1 <="001000011"; -- C
				 WHEN x"23" => token <="00000000010111"; token_1 <="001000100"; -- D
				 WHEN x"24" => token <="00000000000001"; token_1 <="001000101"; -- E
				 WHEN x"2B" => token <="00000001110101"; token_1 <="001000110"; -- F
				 WHEN x"34" => token <="00000000011111"; token_1 <="001000111"; -- G
				 WHEN x"33" => token <="00000001010101"; token_1 <="001001000"; -- H
				 WHEN x"43" => token <="00000000000101"; token_1 <="001001001"; -- I
				 WHEN x"3B" => token <="00000011111101"; token_1 <="001001010"; -- J
				 WHEN x"42" => token <="00000000110111"; token_1 <="001001011"; -- K
				 WHEN x"4B" => token <="00000001011101"; token_1 <="001001100"; -- L
				 WHEN x"3A" => token <="00000000001111"; token_1 <="001001101"; -- M
				 WHEN x"31" => token <="00000000000111"; token_1 <="001001110"; -- N
				 WHEN x"44" => token <="00000000111111"; token_1 <="001001111"; -- O
				 WHEN x"4D" => token <="00000001111101"; token_1 <="001010000"; -- P
				 WHEN x"15" => token <="00000011011111"; token_1 <="001010001"; -- Q
				 WHEN x"2D" => token <="00000000011101"; token_1 <="001010010"; -- R
				 WHEN x"1B" => token <="00000000010101"; token_1 <="001010011"; -- S
				 WHEN x"2C" => token <="00000000000011"; token_1 <="001010100"; -- T
				 WHEN x"3C" => token <="00000000110101"; token_1 <="001010101"; -- U
				 WHEN x"2A" => token <="00000011010101"; token_1 <="001010110"; -- V
				 WHEN x"1D" => token <="00000000111101"; token_1 <="001010111"; -- W
				 WHEN x"22" => token <="00000011010111"; token_1 <="001011000"; -- X
				 WHEN x"1A" => token <="00000011110111"; token_1 <="001011001"; -- Y
				 WHEN x"35" => token <="00000001011111"; token_1 <="001011010"; -- Z
				 
				 --Zahlen
				 WHEN x"45" => token <="00001111111111"; token_1 <="000110000"; -- 0
				 WHEN x"16" => token <="00001111111101"; token_1 <="000110001"; -- 1
				 WHEN x"1E" => token <="00001111110101"; token_1 <="000110010"; -- 2
				 WHEN x"26" => token <="00001111010101"; token_1 <="000110011"; -- 3
				 WHEN x"25" => token <="00001101010101"; token_1 <="000110100"; -- 4
				 WHEN x"2E" => token <="00000101010101"; token_1 <="000110101"; -- 5
				 WHEN x"36" => token <="00000101010111"; token_1 <="000110110"; -- 6
				 WHEN x"3D" => token <="00000101011111"; token_1 <="000110111"; -- 7
				 WHEN x"3E" => token <="00000101111111"; token_1 <="000111000"; -- 8
				 WHEN x"46" => token <="00000111111111"; token_1 <="000111001"; -- 9
		 
				 --Sonderzeichen
				-- WHEN x"" => token <="00000111110111"; -- Å
				 WHEN x"52" => token <="00000011011101"; token_1 <="011100001"; -- Ä
				-- WHEN x"" => token <="00000111010111"; -- È
				-- WHEN x"" => token <="00000101110101"; -- É
				 WHEN x"4C" => token <="00000001111111"; token_1 <="011101111"; -- Ö
				 WHEN x"54" => token <="00000011110101"; token_1 <="011110101"; -- Ü
				 WHEN x"4E" => token <="01011111010101"; token_1 <="011100010"; -- ß
				-- WHEN x"" => token <="00110111011101"; -- ;
				-- WHEN x"" => token <="00010111110101"; -- ?
				 WHEN x"4A" => token <="00110101010111"; token_1 <="000101101"; -- -
				-- WHEN x"" => token <="00010111110111"; -- _
				-- WHEN x"" => token <="00001111011111"; -- Ñ
				 WHEN x"49" => token <="00110111011101"; token_1 <="000101110"; -- .
				 WHEN x"41" => token <="00111101011111"; token_1 <="000101100"; -- ,
				-- 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"; token_1 <="000101011"; -- +
				-- WHEN x"" => token <="00001101011101"; -- /
				-- WHEN x"" => token <="00011111011101"; -- @
				
				 WHEN x"29"	=>	token	<="10000000000000"; -- " "	
				 WHEN x"5A" => token <="11000000000000"; -- "ENTER"
				 
				 WHEN x"66" => token <="11010000000000"; -- "Backspace" 
				 WHEN x"71" => token <="11011000000000"; -- "Entf"
				 WHEN x"76" => token <="11011010000000"; -- "Esc"
				 
				 when x"6B" => token <= "11011011000000"; -- "left Arrow"											
				 when x"74" => token <= "11011011010000"; -- "Right Arrow"
									
				 WHEN OTHERS => token <= (OTHERS => '0'); 
			 
			END CASE;
				
		END IF;				
	
	END PROCESS;
In dem Prozess Audio wurde zusätzlich zum Buchstaben- und Wortmodus ein SD-Karten Modus eingebaut. In den SD-Karten Modus gelangt man, indem man Switch 15 betätigt (Switch oben). Daraufhin erfolgt die Initialisierung, die erfolgreich ist, sobald die LED über den Switch aufleuchtet. Im Anschluss kann zwischen dem Modus Lesen und Schreiben mithilfe von Switch 16 gewechselt werden. Ist der Switch oben, kann gelesen werden und die Tonausgabe erfolgt. Diese kann durch das Betätigen der Enter-Taste wiederholt werden. Ist der Switch unten, kann das Schreiben über das Keyboard erfolgen und mit Enter bestätigt werden. Ist der Lese bzw. der Schreibprozess erfolgreich gewesen, so leuchtet die LED über den Switch auf.
audio:	PROCESS (reset, clockNC, sensitivityFlag, writeFinish, readFinish2) 
	
	VARIABLE double:		NATURAL RANGE 0 TO 16 	:= 0;
	VARIABLE	state:		NATURAL RANGE 0 TO 2 	:= 0;
	VARIABLE word_state:	NATURAL RANGE 0 TO 2 	:= 0;
	VARIABLE state3:		NATURAL RANGE 0 TO 2		:= 0;
	VARIABLE word_state_sd:	NATURAL RANGE 0 TO 3	:= 0;
	VARIABLE double_sd:		NATURAL RANGE 0 TO 16 	:= 0;
	
	---------------------------------------------------------------------------------------------------------
	-----------------------SD-CARD---------------------------------------------------------------------------
	---------------------------------------------------------------------------------------------------------
	
	BEGIN			
				
		IF reset = '0' THEN
		
			sound <= '0';
			cnt <= 0;
			double := 0;
			word_state := 0;
			state := 0;
			state3 :=0;
			mem_max <= 0;
			mem_addr <= 0;
			mem_addr_old <= 0;
			read_write <= '1';
			RAM_IN <= (OTHERS => '0');
			soft_reset <= '0';
			token_word <= (OTHERS => '0');			
			
			reset_sd <= '0';	
			sdstartread <= '0';	
			sdstartwrite <= '0';
			newNextByte <= '0';
			nextByte <= '0';
			sd_in <= "000000000";
			word_state_sd := 0;
			readNextByte <= '0';
			double_sd := 0;
			
		
		ELSIF clockNC'EVENT AND clockNC = '1' THEN

			soft_reset <= '0';	
		
			IF sd_enable = '1' then
				reset_sd <= '1';			-- Reset aus SD-Karte entfernen, Initialisierung kann beginnen
				
				if sd_init = '1' then	-- Abfrage ob SD-Card Initialisert ist
				
----------LESEN AUS DER SD-Card-------------------------------------------------------------------------------------------------				
					
					if sd_mode = '1' then --Switch oben, es wird gelesen
						
						done_rise2 <= '0';
						done_old2 <= done_pressing;								--wenn done_pressing = 1 -> Taster wurde losgelassen
				
						IF done_old2 = '0' AND done_pressing = '1' THEN		-- Taste wurde gedrückt 
							done_rise2 <= '1';										-- wenn done_rise2 = 1 -> Taste gedrückt, wenn = 0 dann nicht gedrückt
						END IF;
						
						--Token-Signal in Token_Word-Signal übertragen, wenn eine Taste gedrückt wurde und diese Taste ein "Enter" war
						IF done_rise2 = '1' AND (token = "11000000000000" or token = "11011010000000" 
						or token = "11010000000000" or token = "11011011000000" or token = "11011011010000" or token= "11011000000000") THEN
							word_state_sd := 0;
						end if;
									
						CASE word_state_sd IS
							
							WHEN 0 =>
							
								sdstartread <= '1';
								readNextByte <= '1';
								word_state_sd := 1;
								
							WHEN 1 =>
									
								if readFinish = '1' then
												
									Case dout is
							
										when "01000001"	=>	token_word_sd	<="00000000001101";	--	A
										when "01000010"	=>	token_word_sd	<="00000001010111";	--	B
										when "01000011"	=>	token_word_sd	<="00000001110111";	--	C
										when "01000100"	=>	token_word_sd	<="00000000010111";	--	D
										when "01000101"	=>	token_word_sd	<="00000000000001";	--	E
										when "01000110"	=>	token_word_sd	<="00000001110101";	--	F
										when "01000111"	=>	token_word_sd	<="00000000011111";	--	G
										when "01001000"	=>	token_word_sd	<="00000001010101";	--	H
										when "01001001"	=>	token_word_sd	<="00000000000101";	--	I
										when "01001010"	=>	token_word_sd	<="00000011111101";	--	J
										when "01001011"	=>	token_word_sd	<="00000000110111";	--	K
										when "01001100"	=>	token_word_sd	<="00000001011101";	--	L
										when "01001101"	=>	token_word_sd	<="00000000001111";	--	M
										when "01001110"	=>	token_word_sd	<="00000000000111";	--	N
										when "01001111"	=>	token_word_sd	<="00000000111111";	--	O
										when "01010000"	=>	token_word_sd	<="00000001111101";	--	P
										when "01010001"	=>	token_word_sd	<="00000011011111";	--	Q
										when "01010010"	=>	token_word_sd	<="00000000011101";	--	R
										when "01010011"	=>	token_word_sd	<="00000000010101";	--	S
										when "01010100"	=>	token_word_sd	<="00000000000011";	--	T
										when "01010101"	=>	token_word_sd	<="00000000110101";	--	U
										when "01010110"	=>	token_word_sd	<="00000011010101";	--	V
										when "01010111"	=>	token_word_sd	<="00000000111101";	--	W
										when "01011000"	=>	token_word_sd	<="00000011010111";	--	X
										when "01011001"	=>	token_word_sd	<="00000011110111";	--	Y
										when "01011010"	=>	token_word_sd	<="00000001011111";	--	Z
															
															
										when "00110000"	=>	token_word_sd	<="00001111111111";	--	0
										when "00110001"	=>	token_word_sd	<="00001111111101";	--	1
										when "00110010"	=>	token_word_sd	<="00001111110101";	--	2
										when "00110011"	=>	token_word_sd	<="00001111010101";	--	3
										when "00110100"	=>	token_word_sd	<="00001101010101";	--	4
										when "00110101"	=>	token_word_sd	<="00000101010101";	--	5
										when "00110110"	=>	token_word_sd	<="00000101010111";	--	6
										when "00110111"	=>	token_word_sd	<="00000101011111";	--	7
										when "00111000"	=>	token_word_sd	<="00000101111111";	--	8
										when "00111001"	=>	token_word_sd	<="00000111111111";	--	9
										WHEN OTHERS 		=> token_word_sd 	<=(OTHERS => '0');  
										
									End Case;
									word_state_sd := 2;
									double_sd  := 0;
									
								end if;
								
							When 2 =>	
							
								IF double_sd >= 0 AND double_sd < 14 THEN
										
										
									IF token_word_sd(double_sd + 1 DOWNTO double_sd) = "01" THEN											
											
									--Codierung eines "Dits" als Signal mit doppelter Periodendauer der Wortgeschwindigkeit
										IF cnt >= 2 THEN
											cnt <= 0;
											sound <= '0';
											double_sd := double_sd + 2;
										ELSE
											cnt <= cnt + 1;
											sound <= '1';
										END IF;
								
									ELSIF token_word_sd(double_sd +1 DOWNTO double_sd) = "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_sd := double_sd + 2;
										ELSE
											cnt <= cnt + 1;
											sound <= '1';
										END IF;
									
									ELSIF token_word_sd(13 DOWNTO 0) = "10000000000000" THEN --Prüfen ob das Tokenwort ein Leerzeichen repräsentiert			
								
									--In kombination mit dem cnt = 6 von "double_sd >= 14" ergibt sich eine Länge von 7 "Dits"! => Pause zwischen Worten
										IF cnt >= 8 THEN 
											cnt <= 0;
											sound <= '0';
											double_sd := 14;	
										ELSE
											cnt <= cnt + 1;								
										END IF;
								
										--wenn im Datenwort nur noch Nullen stehen muss die Leseadresse inkrementiert und anschließend das neue Datenwort ausgelesen werden		
									ELSIF token_word_sd(double_sd +1 DOWNTO double_sd) = "00" AND token_word_sd(13 DOWNTO 0) /= "11000000000000" AND 
										token_word_sd(13 DOWNTO 0) /= "10000000000000" AND token_word_sd(13 DOWNTO 0) /= "11010000000000" AND token_word_sd(13 DOWNTO 0) /= "11011000000000" 
										AND token_word_sd(13 DOWNTO 0) /= "11011010000000" AND token_word_sd(13 DOWNTO 0) /= "11011011000000" AND token_word_sd(13 DOWNTO 0) /= "11011011010000" THEN
																	
												
--				"11000000000000"; -- "ENTER"
--				"11010000000000"; -- "Backspace" 
--				"11011000000000"; -- "Entf"
--				"11011010000000"; -- "Esc"
--				"11011011000000"; -- "left Arrow"											
--				"11011011010000"; -- "Right Arrow"
								
										sound <= '0';
										cnt <= 0;
										double_sd := 14;
										
									END IF;
								END IF;
								
							--Wenn das Datenwort zuende analysiert wurde, wird die Leseadresse inkrementiert und das nächste Datenwort ausgelesen
								IF double_sd >= 14 THEN
									IF cnt >= 6 THEN
										cnt <= 0;
										sound <= '0';
										double_sd := 0;
											
										word_state_sd := 1;
										readNextByte <= '1';
									ELSE
										cnt <= cnt + 1;
											
									END IF;	
								END IF;
							
							WHEN Others =>		
						
						END CASE;
----------SCHREIBEN in die  SD-Card-------------------------------------------------------------------------------------------------				

					elsif sd_mode = '0' then		--Switch unten, es wird geschrieben 					
						word_state_sd := 0;
						
						done_rise2 <= '0';
						done_old2 <= done_pressing;								--wenn done_pressing = 1 -> Taster wurde losgelassen
				
						IF done_old2 = '0' AND done_pressing = '1' THEN		-- Taste wurde gedrückt 
							done_rise2 <= '1';										-- wenn done_rise2 = 1 -> Taste gedrückt, wenn = 0 dann nicht gedrückt
						END IF;
						
						--wurden alle 512 Bytes uebertragen wird auf neue Eingabe gewartet
						if writeFinish = '1' then
							sdstartwrite <= '0';
							newNextByte <= '0';
						end if;
						
						--Daten in den RAM schreiben, wenn eine Taste betätigt wurde und diese Taste KEIN "Enter", "ESC", "Backspace", "left arrow" oder "right arrow" war
						IF (done_rise2 = '1') AND token /= "11000000000000" and token /= "11011010000000" 
						and token /= "11010000000000" and token /= "11011011000000" and token /= "11011011010000"	and token/= "11011000000000" AND word_state = 0 THEN
								
							sdstartwrite <= '1';
							nextByte <= '1';
							sd_in <= token_1;
							
							
						
						--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" or token = "11011010000000" 
						or token = "11010000000000" or token = "11011011000000" or token = "11011011010000" or token= "11011000000000") THEN
							
							nextByte <= '1';
							sd_in <= "000000000";
							
							newNextByte <= '1';
							
						elsif  newNextByte = '0' then
							nextByte <= '0';
						end if;
				
					end if;
				end if;
			
			ELSIF 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;
Da der Prozess "setState" in sd_card mit einem anderen Takt als der Prozess "audio" in keyboard_encoder läuft, wurde die Sensitivitylist um die Signale sensitivityFlag, writeFinish und readFinish2 erweitert. Durch diese wird das kontinuierliche Lesen/ Schreiben aus/in die SD-Karte vermieden. 
if sensitivityFlag = '1' and newNextByte = '0' then
			nextByte <= '0';
			readNextByte <= '0';
		end if;
		if writeFinish = '1' or readFinish2 = '1' then
			sdstartread <= '0';
			readNextByte <= '0';
			
			sdstartwrite <= '0';
			nextByte <= '0';
			newNextByte <= '0';	
		END IF;

 5. Fazit und Ausblick

Im Rahmen der Projektarbeit ist es uns gelungen, über die Tastatur in die SD-Karte schreiben zu können, ohne den internen RAM des Boards als Zwischenspeicher zu verwenden. Das Auslesen der Zeichen sowie die morsecodierte Soundausgabe funktioniert für drei Bytes, also drei Zeichen. Das Problem, dass beim Auslesen der darauf folgenden Bytes auftritt, konnte aufgrund mangelnder Zeit nicht behoben werden. Jedoch stellen wir einen möglichen Lösungsansatz zur Verfügung.

Zwischen dem Auslesen von zwei Bytes darf maximal die Zeit, die für das Auslesen von einem Byte benötigt wird, liegen. In dieser Zeit erfolgt parallel die Soundausgabe, die abgeschlossen werden muss, bevor das nächste Byte gelesen wird. Diese überschreitet jedoch die Dauer für das Auslesen von einem Byte. Um das Problem zu beheben, muss der Parameter slowClockDivider für den Leseprozess angepasst werden, sodass die SD-Karte mit einem langsameren Takt ausgelesen wird. 


Downloads:

Link => Morse_Coder.zip

Autoren: Vesa Halili und Alex Apt

Gelsenkirchen 2021

_______________________________________________________________________________________________________________________________________________________________


 

 4. Erweiterung eines Morsedecoders und -encoders, Implementierung eines RAMs, ROMs, und UART  

 

1. Einführung

Das Projekt wurde im Rahmen eines Abschlussprojektes des Moduls Entwurf digitaler Systeme durchgeführt. Hier wurde als Basis das Morse De- und Encoder mittels FPGA vom Herrn Kai-Frederik Nessitt Projektes aufgenommen. Die wesentlichen Änderungen, Erweiterungen und Testbenches haben wir hier diskutiert.

Das zu entwickelnde System soll dazu fähig sein, die Morse Sprache zu kodieren und decodieren.

Für den Decoder hat man die Möglichkeit über Tastendruck, den Morse Code einzugeben. Dieser wird als Audio-Output ausgegeben und parallel wird der entsprechende ASCII-Code auf dem 16×2 Display angezeigt.

Des Weiteren kann man über UART und anhand eines mit Python programmierten Userinterface die Daten zum FPGA senden, welche dann auf Morse umgewandelt werden. Die Komponente UART-TX wird die bereits verschickten Daten wieder zum Computer senden.

 

System

                                                                             Abbildung 1 Ein- und Ausgabe

 

 Nummer   Funktionalität                                                       
1 Netzteil
2 Audio-Output
RS-232 Kabel für die UART Kommunikation
4 PS2-Anschluss
5 16 x 2 LCD-Display
6 Zwei 7 Segmentdisplay zu der Anzeige des entsprechenden vom UART angefangene hexadezimal-Code
7 Tastatur mit PS2-Anschluss
8 Zwei Schalter zu der Auswahl der Wortgeschwindigkeit
9 Reset-Schalter
10 Schalter zu der Auswahl zwischen einem Zeichen Encoder Modus oder einem ganzen Satz
11 8 Rote LEDs zu der Anzeige der aktuellen genutzten Zeile vom RAM
12 Encoding mit der Nutzung vom UART
13

Auswahl zwischen Decoder oder Encoder Modus. 0: Decoder; 1: Encoder

14 Taste zu der Eingabe des Morse Code
15 3 Grüne LEDs zur Anzeige der Länge des Drucken Signal   

 

3. Top Level Morse

Das Entity verbindet die unterschiedlichen Komponenten miteinander.
Zudem wird das von der Taste angefangene Signal auf Morse decodiert. Hier muss allerdings der Modus anhand des Schalters 13 (Abbildung 1) ausgewählt werden.
Zunächst wird beim Tastendruck geprüft, ob es sich um einen kurzen Druck, Dot, langne Druck, Dash, oder langen Pause handelt.
Abbildung 2 stellt das Ergebnis des Testbench dar.
 
topmorse
                                                                             Abbildung 3 Testbench TopLevelMorse
 

4. Clock Divider

Das LCD, AudioCodec und die Wortgeschwindigkeit benötigen unterschiedliche Taktsignale. Diese werden durch Das Entity Clockdevider bereitgestellt. Um dies zu schaffen, wurden Zähler verwendet.
Der Clock divider basiert auf dem folgenden Prinzip:
 
clk
                                                                             Abbildung 4 ClockDivider
 
clkdividerflussdiagramm
                                                                             Abbildung 5 ClockDivider Flußdiagramm
 

Abbildung 6 stellt das Ergebnis vom Testbench dar.

Clk bezieht sich auf das Systemtaktzyklus, das 50MHz entspricht. Clock_12MHz wird für den AudioCodec benötigt.

clockdivider1

                                                                             Abbildung 6 ClockDivider 50 MHz auf 12MHz

 
Die zwei Schalter auf der Abbildung 1 (8) sind notwendig, um die Wortgeschwindigkeit für den Decoder und Encoder zu variiren. Hier sind drei Geschwindigkeiten auszuwählen. Diese werden für die andere Komponenten zu bestimmen. Die Komponente clock_divider stellt diese zur Verfügung.

Die Abbildung 6 zeigt das Ergebnis des Testbenches:

clockdivider2

                                                                             Abbildung 7 ClockDivider - Wortgeschwindigkeiten
 
 

5. Tastendruck

Beim Decodieren von Morsecode wird der Status der Taste (Abbildung 1 (14)) ständig überprüft. Diese zeigt an, ob die Taste gedrückt wurde und ob es sich um ein langes oder kurzes Drücken handelt. Dies hängt auch von der Wortgeschwindigkeit ab.

tastendruck

                                                                             Abbildung 8 Tastendruck - Flußdiagramm
 

Zum Testen der Funktionalität des Tastendrucks wurde ein Testbench erstellt.
Abbildung 9 zeigt das erhaltene Ergebnis.

tb tastendruck

                                                                             Abbildung 9 Tastendruck - Testbench

6. PS2-Anschluss Tastatur

Tastatur Entity enthält die Logik der Tastatur Encoder. Die Architektur besteht aus drei Prozessen. Es werden der Taktzyklus von der Clock und die Data der Tastatur mit der Clock vom System synchronisiert. Sobald eine Taste gedrückt wurde, wird der Wert der gedrückten Taste in Hexadezimalzahl ermittelt und geschaut, ob diese gedrückte Taste losgelassen und die Hexadezimalzahl zu einem Token-Signal konvertiert wurde. Das Signal gibt den Morse Code für die entsprechende Taste an. Beim Token Signal wird jeder zweite Bits als ein Dot oder Dash beschrieben.

„11“ repräsentiert einen Dash. „01“ repräsentiert einen Dot.

Es gibt die Möglichkeit zwischen zwei Modi zum Encoding mit der Nutzung einer PS2-Tastatur auszuwählen. Entweder die Encodierung von einzelnen Werten oder einer Reihe von Werten.

Beim Encoding von einzelnen Werten wird aus dem Token Signal ein Sound Signal erstellt. Dieses Sound-Signal wird dann zum AudioCodec weitergeschickt. Das Lesen vom Token Signal fängt von rechts nach links an.

Beim Encoding einer Reihe von Werten, wird das Token Signal in einem Single Port RAM gespeichert. RAM-Matrix besteht aus 257 Zeilen und jede Zeile ist ein Array von 16 Bits.

 
asdd
                                                                             Abbildung 9 RAM Block
 

Die Wortgeschwindigkeit weist einen ram_clk auf. Der ausgabe_modus erlaubt die Nutzung vom RAM und bei jedem Speichern wird die Adresse des RAMs um 1 inkrementiert. Enter-Taste zeigt auf, ob man in dem RAM schreiben oder lesen kann. Das Token-Signal ist ein 16 Bit Vektor und dieser wird in der angegebenen Adresse gespeichert.

Beim Drücken auf die Enter-Taste, wird dann dieses RAM von der ersten Zeile gelesen und die Token-word-Signale dann nacheinander auf Sound encodiert, sodass sie zum Audiocodec weitergeschickt werden können.

Das Auslesen vom RAM wird wie folgt geschehen:


 

-- Dateiname: Tastatur Top
-- Design Software : Quartus II 13.0sp1 (64-bit)
--
-- Erstellt:
-- von - Kai-Frederik Nessitt
-- 2008
-- www.mikroelektronik.w-hs.de
--
-- Verändert:
-- Von Moujahid Bouhouch
--
-- Am 10.06.2021
--
--------------------------------------------------------------------------------
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE ieee.numeric_std.ALL;
ENTITY audio_ausg_ram IS
PORT (
ram_clk : IN std_logic; -- clock des Systems
clockNC_en : IN std_logic; -- Clock des Wortgeschwindigkeit
reset : IN STD_ULOGIC; -- Reset
token : IN std_logic_vector(15 DOWNTO 0); -- Daten zum Codieren
ausgabe_modus : IN std_logic; -- Modus des Codieren/ einzelne Buchtabe oder Sequenz von Buchstaben
breakCode : IN std_logic; -- gibt an ob die Taste losgelassen wurde
soft_reset : IN std_logic;
sound : OUT std_logic; -- Sound Signal zum weiter schicken an die Audio Codec
adresse : OUT std_logic_vector(11 DOWNTO 0); -- Adresse des RAMs
ram_data_out : OUT std_logic_vector(15 DOWNTO 0) -- Daten die schon im RAM gespeichert wurde

);
END audio_ausg_ram;
ARCHITECTURE structure OF audio_ausg_ram IS
SIGNAL mem_max : NATURAL RANGE 0 TO 279 := 0;
signal breakCodevar : std_logic;
signal read_write : std_logic;
signal ram_addr_old : std_logic_vector(17 downto 0) := (others => '0');
signal ram_addr_new : std_logic_vector(17 downto 0) := (others => '0');
type ram_array is array (0 to 256 ) of std_logic_vector (15 downto 0);
-- initial values in the ram
signal ram: ram_array := (others=>(others=>'0'));
signal breakCode_delayed : std_logic := '0';
signal breakCode_rising_edge : std_logic;
signal Enter_taste : std_logic := '0';
SIGNAL count : NATURAL := 0;
signal count_addr : NATURAL range 0 to 256 := 0;
signal soft_reset_var : std_logic := soft_reset;
signal token_word : std_logic_vector(15 DOWNTO 0) := (others => '0');
--signal ram_data_out_var : std_logic_vector(15 downto 0);

BEGIN
-- konviertierung des ram adresse
adresse <= STD_LOGIC_VECTOR(TO_UNSIGNED(count_addr, 12));

-- Das Speichern von Daten im RAM sollte einmal erfolgen, wenn die Taste losgelassen wird
-- es ist erforderlich, dass das Signal einmal während eines Taktzyklus auftritt
-- es wird mit jedem Taktzyklus den Wert von den Wert von BreakCode vergleichen
-- wenn sie sich unterscheiden, breakCode_rising_edge wird mit '1' zugewiesen
get_delayed_breakCode : PROCESS (clockNC_en)
BEGIN
IF reset = '0' THEN

breakCode_delayed <= '0';
ELSE
IF rising_edge(clockNC_en) THEN

breakCode_delayed <= breakCode;
breakCode_rising_edge <= NOT breakCode_delayed AND breakCode;

END IF;

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 (clockNC_en, reset, token, ausgabe_modus)
VARIABLE two_bits_count : NATURAL RANGE 0 TO 16 := 0;
VARIABLE ausgabe_modus_state : NATURAL RANGE 0 TO 2 := 0;
BEGIN
IF reset = '0' THEN

sound <= '0';
count <= 0;
two_bits_count := 0;
ausgabe_modus_state := 0;
count_addr <= 0;
soft_reset_var <= '0';
token_word <= (OTHERS => '0');
ELSIF clockNC_en'EVENT AND clockNC_en = '1' THENsoft_reset_var <= '0';
-- Codierung von einzelnen Buchstaben
IF ausgabe_modus = '0' THEN
IF two_bits_count >= 0 AND two_bits_count < 16 THEN
-- Codierung eines "Dits" als Signal mit doppelter Periodendauer der Wortgeschwindigkeit
-- man prüft jede zwei bits und enscheidet ob dies ein dit oder dash ist

IF token(two_bits_count + 1 DOWNTO two_bits_count) = "01" THEN

IF count >= 2 THEN
count <= 0;
sound <= '0';
two_bits_count := two_bits_count + 2;
ELSE
count <= count + 1;
sound <= '1';
END IF;

--Codierung eines "Dahs" als Signal mit sechsfacher Periodendauer der Wortgeschwindigkeit
ELSIF token(two_bits_count + 1 DOWNTO two_bits_count) = "11" THEN

IF count >= 6 THEN
count <= 0;
sound <= '0';
two_bits_count := two_bits_count + 2;
ELSE
count <= count + 1;
sound <= '1';
END IF;

--Auslesen beenden, wenn nur noch Nullen im Token-Signal gelesen werden
ELSE

sound <= '0';
two_bits_count := 16;

END IF;

END IF;
-- zweite Modus
-- Codierung von Sequenz von Buchstaben
-- wenn eine Taste der Tastatur losgelassen ist, wird das entsprechende Tokensignal
-- im RAM gespeichert
-- Das Lesen vom Ram erfolg, wenn die Enter Taste gedruckt und losgelassen wurde
-- Enter Taste entspricht eine hexadecimalzahl von C0
ELSIF ausgabe_modus = '1' THEN

-- Daten in den ram schreiben, wenn eine Taste betätigt wurde und diese Taste kein "Enter" war
IF breakCode_rising_edge = '1' AND token /= x"C000" THEN

RAM(count_addr) <= token;

count_addr <= count_addr + 1;

-- Token-Signal in token_word-Signal übertragen, wenn eine Taste gedrückt wurde und diese Taste ein "Enter" war
ELSIF breakCode_rising_edge = '1' AND token = x"C000" THEN

token_word(15 DOWNTO 0) <= token;


-- Daten aus dem RAM zurücklesen
ELSIF (token_word(15 DOWNTO 0) = x"C000" OR count_addr > 256) OR ausgabe_modus_state > 0 THEN

CASE ausgabe_modus_state IS

-- Das Lesen wird angefangen mit RAM Adresse 0. Zeile 0
WHEN 0 =>
count_addr <= 0;
token_word(15 DOWNTO 0) <= RAM(count_addr);
ram_data_out(15 DOWNTO 0) <= RAM(count_addr);

ausgabe_modus_state := 1;

-- Die Codieren wird genauso wie beim ersten Modus implementiert
WHEN 1 =>

IF two_bits_count >= 0 AND two_bits_count < 16 THEN

IF token_word(two_bits_count + 1 DOWNTO two_bits_count) = "01" THEN

IF count >= 2 THEN
count <= 0;
sound <= '0';
two_bits_count := two_bits_count + 2;
ELSE
count <= count + 1;
sound <= '1';
END IF;

ELSIF token_word(two_bits_count + 1 DOWNTO two_bits_count) = "11" THEN

IF count >= 6 THEN
count <= 0;
sound <= '0';
two_bits_count := two_bits_count + 2;
ELSE
count <= count + 1;
sound <= '1';
END IF;

ELSIF token_word(15 DOWNTO 0) = "1000000000000000" THEN --Prüfen ob das Tokenwort ein Leerzeichen repräsentiert

--In kombination mit dem count = 6 von "two_bits_count >= 16" ergibt sich eine Länge von 7 "Dits"! => Pause zwischen Worten
IF count >= 8 THEN
count <= 0;
sound <= '0';
two_bits_count := 16;

ELSE
count <= count + 1;


END IF;

ELSIF token_word(15 DOWNTO 0) = x"C000" THEN
sound <= '0';
ausgabe_modus_state := 2;

-- Beendung der Übertragung, wenn man die Zeile des RAMs nur aus nullen besteht
ELSIF RAM(count_addr) = x"0000" THEN
sound <= '0';
ausgabe_modus_state := 2;

-- Beendung der Übertragung, wenn man die letzte RAM zeile erreicht
ELSIF count_addr >= 256 THEN
sound <= '0';
ausgabe_modus_state := 2;



-- wenn im Datenwort nur noch Nullen stehen muss die Leseadresse inkrementiert und anschließend das neue Datenwort ausgelesen werden
ELSIF token_word(two_bits_count + 1 DOWNTO two_bits_count) = "00" AND token_word(15 DOWNTO 0) /= "1100000000000000" AND token_word(15 DOWNTO 0) /= "1000000000000000" THEN

sound <= '0';
count <= 0;
two_bits_count := 16;
END IF;

END IF;


-- Wenn das Datenwort zuende analysiert wurde, wird die Leseadresse inkrementiert und das nächste Datenwort ausgelesen
IF two_bits_count >= 16 THEN

IF count >= 6 THEN
count <= 0;
sound <= '0';
two_bits_count := 0;
count_addr <= count_addr + 1;
token_word(15 DOWNTO 0) <= RAM(count_addr);
ram_data_out(15 DOWNTO 0) <= RAM(count_addr);
ELSE
count <= count + 1;
END IF;
END IF;


-- Wenn der SPeicher komplett ausgelesen wurde, dann wird der Speicher durch einen Software-Reset
WHEN OTHERS =>soft_reset_var <= '1';

IF count >= 6 THEN
count <= 0;
sound <= '0';
two_bits_count := 0;
ausgabe_modus_state := 0;
count_addr <= 0;
count <= 0;
sound <= '0';
two_bits_count := 0;
token_word <= (OTHERS => '0');
ELSE
count <= count + 1;
END IF;

END CASE;

END IF;
END IF;

END IF;


-- Ist das Token-Signal komplett ausgelesen ausgabe_modusen und eine neue Taste wurde gedrückt,
-- dann soll das bitpaarweise Auslesen erneut beginnen

IF two_bits_count >= 16 AND (breakCode_rising_edge = '1' AND ausgabe_modus = '0') THEN
two_bits_count := 0;
END IF;
END PROCESS;


END structure;
 
 
Das Bild stellt das Ergebnis vom ersten Modus dar. Die Werte werden codiert, ohne dies in den RAM zu speichern.
keyboard1
                                                                             Abbildung 11 Tastatur encoder ohne die Nutzung vom RAM
 
keyboard2
                                                                             Abbildung 12 Tastatur encoder ohne die Nutzung vom RAM - Erstellung des Sound-Signales
 

Die Abbildung 13 stellt das Ergebnis vom zweiten Modus dar. Die Werte werden codiert und im RAM gespeichert. Beim Drücken auf die Enter-Taste wird das Sound-Signal erstellt.

keyboard3
                                                                             Abbildung 13 Tastatur encoder - Das Schreiben im RAM
 
keyboard4
                                                                             Abbildung 14 Tastatur encoder - Das Lesen vom RAM und Erstellung des Sound-Signales
 

7. AudioCodec

Das Untergeordnete Top Level verbindet 4 benötigte Komponente zur Steuerung des Audio-Codecs miteinander.  Darüber hinaus werden in dieser Entity durch eine Finite State Machine die Konfiguration Daten des Audio-Codecs „WM8731“ bereitgestellt und mit Hilfe der Komponente I2C zum Audio Codec übertragen.

Die Komponenten sind:

  • ROM-Speicher: Beinhaltet die Werte, die eine Periode eines 500 Hz Audiosignal repräsentieren.
  • Read Audio Data: die unter einige Voraussetzungen die Werte des ROM-Speichers ausliest und zur Komponente Audiogeneration weitergibt.
  • Audiogeneration: die die vom Rom-Speicher ausgelesene Daten bitweise an den Audiocodec DAC sendet.
  • I2C : sendet die Konfigurationsdaten zum Audio Codec.

Im Vergleich zum angepassten Programmcode vom Herrn Nessit, wurde zunächst anstelle des mit QSYS Tool erstellte Memory System, eine neue Romspeicher entworfen. Dabei wurde ein Package verwendet, indem neue Datentype und den Inhalt des Romspeichers deklariert sind. Den Inhalt beide Speicher (Audio digitale Daten) ist gleich gehalten.

Außerdem wurde für eine bessere strukturierung und lesbarkeit die Componente Audio Generation Prozess in einer Komponente gekapselt.

Die Komponente Read Audio Data, wurde verändert und angepasst damit die empfangenen daten durch Uart_TOP, in einem Ton umgewandelt werden können.

 

-- Dateiname: soundmap
-- Design Software : Quartus II 13.0sp1 (64-bit)
--
-- Erstellt:
-- von - Bouchnak wassim
--
-- hier wird mehrere durch einen Wertebereich eingegrenzte datentype deklariert, die den Typ und die Wertengrenze des Rom festlegen
-- Anschlißend wird eine Konstante deklariert, die den Inhalt des ROM-Speichers enthält.

--------------------------------------------------------------------------------

 

library ieee;
use ieee.std_logic_1164.all;

package soundmap is
-- Deklaration eines durch einen Wertebereich eingegrenzte Untertyps, der die Zeilengrenze darstellt

subtype row_range is natural range 0 to 94;

-- Deklaration eines Untertyps, der die Spaltengrenze darstellt
subtype col_range is natural range 15 downto 0;

-- Deklaration eines Typs, der die Zeilen und spalten Wertebereich des ROM-Speichers darstellt
type soundmap_type is array (row_range) of std_logic_vector(col_range);

-- Deklaration einer Konstante , die den Inhalt des ROM-Speichers enthält.
constant soundmap : soundmap_type := ( x"B300",

x"5D06",
x"FA0D",
x"8213",
x"E81A",
x"3320",
x"4727",
x"362D",
x"E233",
x"5738",
x"843E",
x"6943",
x"FC48",
x"3F4C",
x"2351",
x"AE55",
x"D958",
x"985B",
x"FA5E",
x"E860",
x"6E62",
x"8864",
x"2B65",
x"6A66",
x"2B66",
x"8866",
x"6F65",
x"E764",
x"FA62",
x"9960",
x"D75E",
x"B05B",
x"2358",
x"3E55",
x"FE51",
x"654C",
x"8948",
x"5343",
x"E63E",
x"3238",
x"4B33",
x"2F2D",
x"EC27",
x"7F20",
x"FB1A",
x"5F13",
x"B00D",
x"0306",
x"4B00",
x"A4F9",
x"05F2",
x"80EC",
x"14E5",
x"D3DF",
x"B3D8",
x"CED2",
x"1CCC",
x"A9C7",
x"7DC1",
x"97BC",
x"02B7",
x"C3B3",
x"DDAE",
x"4FAA",
x"2DA7",
x"61A4",
x"0CA1",
x"149F",
x"939D",
x"7A9B",
x"D19A",
x"9A99",
x"D299",
x"7899",
x"959A",
x"139B",
x"0C9D",
x"629F",
x"2BA1",
x"51A4",
x"DCA7",
x"C2AA",
x"04AE",
x"95B3",
x"80B7",
x"A5BC",
x"20C1",
x"C9C7",
x"B9CC",
x"CED2",
x"17D8",
x"7EDF",
x"07E5",
x"A1EC",
x"4FF2" );
end package;

-- Dateiname: ROM
-- Design Software : Quartus II 13.0sp1 (64-bit)
--
-- Erstellt:
-- von - Bouchnak wassim
--
--
-- Es findet die Initialisierung und den Datenfluss eines Rom-speicher mit dem Einsatz von Package
-- Soundmap, die im Library morse zu finden ist
--------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

--Aufruf der Bibliothek morse
library morse;
-- Aufruf der gesamte Package soundmap
use morse.soundmap.all;

entity rom is
port (
clk : in std_logic;
addr : in row_range; --type natural DEPTH = 95
dout : out std_logic_vector(col_range) -- WIDTH = 16 bits;
);
end rom;

architecture rtl of rom is

-- diese konstante enthält die sounddateien
constant rom : soundmap_type := soundmap;

begin

PROC_ROM : process(clk)
begin
if rising_edge(clk) then
dout <= rom(addr); --der Inhalt des rom-Speichers entsprechend der Adresse
--wird im nächsten Taktzyklus dem Ausgangssignal zugewiesen
end if;
end process; -- PROC_ROM

end architecture;

Um die Funktionalität dieser Komponenten zu testen, haben wir ein Testbench erstellt.

Dabei wird nach 10 Taktzyklus des 50 Mhz Taktsignal das nrst (reset) Eingangssignal desaktiviert. Danach wird zunächst das decoder Modus aktiviert und Buchstabe A Durch Tastendruck eingegeben. Nachfolgend wird encoder Modus eingeschaltet und Buchstabe Z durch das Sound Signal eingegeben. Anschließend wird Uart Modus aktiviert und Buchstabe Z eingegeben.

Die folgenden Bilder stellen das Ergebnis der Simulation dar.

audio1
                                                                             Abbildung 15Testbench AudioCodec Teil 1

audio2                                                                             Abbildung 16 Testbench AudioCodec Teil 2

 

 

8. LCD Display

 

Nun findet die Initialisierung und der Datenfluss zu 8-Bit-Schnittstellen-LCD-Modulen HD44780 statt. Zudem wird sowohl der Token im Decoder-Modus als auch die Tastaturdaten im Encoder-Modus ausgewertet und auf dem LCD-Display dargestellt.

Mit einer FSM wird hier das LCD auf einen entsprechenden Betrieb Modus eingestellt.

Im Vergleich zur Quellcode vom Herrn Nessit wird dieser Komponente mit einem GENERIC versehen, wobei die Einstellungsparameter geändert werden können. Weiterhin werden die FSM benannt und die Anfangsbedingung, die im Datenblatt zu finden sind, durch einen Zähler eingehalten und weitere Anpassungen.


-- Dateiname: LCD_Controller
-- Design Software : Quartus II 13.0sp1 (64-bit)
--
-- Erstellt:
-- von Kai-Frederik Nessitt
-- Am 2018
--
-- Verändert:
-- Von Bouchnak Wassim
-- Am 07.08.2021
--
-- Es findet die Initialisierung und den Datenfluss zu 8-Bit-Schnittstellen-LCD-Modulen HD44780 statt
-- Zudem wird sowohl der Token im Decoder-Modus als auch die Tastaturdaten
-- im Encoder-Modus ausgewertet und auf dem LCD-Display dargestellt.
--------------------------------------------------------------------------------
LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;
ENTITY LCD IS
GENERIC(
display_lines : STD_LOGIC ; --number of display lines (0 = 1-line mode, 1 = 2-line mode)
character_font : STD_LOGIC ; --font (0 = 5x8 dots, 1 = 5x10 dots)
display_on_off : STD_LOGIC ; --display on/off (0 = off, 1 = on)
cursor : STD_LOGIC ; --cursor on/off (0 = off, 1 = on)
blink : STD_LOGIC ; --blink on/off (0 = off, 1 = on
inc_dec : STD_LOGIC ; --increment/decrement (0 = decrement, 1 = increment)
shift : STD_LOGIC --shift on/off (0 = off, 1 = on)
);
PORT(
clk : IN STD_LOGIC; --clock 800 HZ
reset_n : IN STD_LOGIC; --active low reinitializes lcd
data_input : IN STD_LOGIC_VECTOR (13 DOWNTO 0); --Token-Signal des Decoders / Morse
data_keyboard : IN STD_LOGIC_VECTOR (7 DOWNTO 0); --Keyboard-Daten nach Betätigung einer Taste
enable_Modus : IN STD_LOGIC; --Switch Signal zum Umschalten zwischen dem Decoder 0 und Encoder 1
enable_Pause : IN STD_LOGIC; --Pausen-Signal der Morse-Entity zur Überprüfung ob ein Token-Signal "fertig" ist
done_pressing : IN STD_LOGIC; --gibt an ob die Tastaturtaste losgelassen wurde und die Datenübertragung beendet wurde

rw, rs, e : OUT STD_LOGIC; --read/write, setup/data, and enable for lcd
lcd_data : OUT STD_LOGIC_VECTOR(7 DOWNTO 0); --data signals for lcd
lcd_on : OUT STD_LOGIC; -- LCD Power On/Off
lcd_blon : OUT STD_LOGIC; -- LCD Back Light On/Off
busy : OUT STD_LOGIC --lcd feedback
);

END LCD;
ARCHITECTURE RTL OF LCD IS
TYPE CONTROL IS(power_up, reset1, reset2, reset3, function_set, display_control, display_clear, mode_set, drop_lcd_en, ready, send, Line2, Return_Home);
SIGNAL state, next_command : CONTROL;

SIGNAL enable_Pause_p1 : STD_LOGIC; -- altes Signal enable_Pause ALTsig_Pause
SIGNAL valid_Pause : STD_LOGIC; -- Bestätigung einer neuen Pause im decoder Modus

SIGNAL enable_Modus_p1: STD_LOGIC; -- altes Signal enable_Modus ALTsig_Modus
SIGNAL Modus_Wechsel: STD_LOGIC;


SIGNAL done_pressing_p1: STD_LOGIC; -- altes Signal done_pressing ALTsig_pressing
SIGNAL valid_done_pressing : STD_LOGIC; --

signal char_count : unsigned( 5 downto 0) := "000000"; -- auf dem lcd angezeigt Charakter Zähler
BEGIN
PROCESS(clk, reset_n)
VARIABLE clk_count : INTEGER := 0; --event counter for timing

BEGIN

IF reset_n = '0' THEN
state <= power_up;
next_command <= reset1; -- reset1
lcd_on <= '1';
e <= '0';
rs <= '0';
rw <= '0';
busy <= '0';
char_count <= (others => '0');
--char_count := 0;
clk_count := 0;

ELSIF(clk'EVENT and clk = '1') THEN

-- defaults : somit konnen inpuls signale erzeugt werden wenn die Bedingungen erfüllt sind
valid_Pause <= '0';
Modus_Wechsel <= '0';
valid_done_pressing <= '0';


-- Shift value in : im nächsten Taktzyklus erfolgt die Zuweisung und somit kann jegliche änderung eines Signals fesgestellt werden
enable_Pause_p1 <= enable_Pause;
enable_Modus_p1 <= enable_Modus;
done_pressing_p1 <= done_pressing;

--Es wird geprüft ob sich das alte Signal zum neuen Signal unterscheidet. Ist dies der Fall, gab es eine positive Flanke
IF enable_Pause_p1 = '0' AND enable_Pause = '1' AND enable_Modus = '0' THEN
valid_Pause <= '1'; -- pause bestätigt und token bereit und vollständig
END IF;

IF (enable_Modus_p1 = '0' AND enable_Modus = '1') or ( enable_Modus_p1 = '1' AND enable_Modus = '0') THEN
Modus_Wechsel <= '1'; -- switch decoder zum encoder
END IF;

IF done_pressing_p1 = '0' AND done_pressing = '1' AND enable_Modus = '1' THEN
valid_done_pressing <= '1'; -- done_pressing bestätigt und data_keyboard bereit und vollständig
END IF;


--Beginn des fsm logic
CASE state IS


WHEN power_up => --wait 50 ms to ensure Vdd has risen and required LCD wait is met

busy <= '1';

IF(clk_count < 20) THEN --wait 50 ms
state <= power_up;
clk_count := clk_count + 1;
ELSE --power-up complete
clk_count := 0;
state <= next_command;
next_command <= drop_lcd_en;
END IF;


WHEN reset1 =>

e <= '1';
lcd_data <= x"38";
IF(clk_count < 2) THEN --wait 5 ms
state <= reset1;
clk_count := clk_count + 1;
ELSE
clk_count := 0;
state <= next_command;
next_command <= reset2;
END IF;


WHEN reset2 => -- Externer RESET
e <= '1';
lcd_data <= x"38";
state <= drop_lcd_en;
next_command <= reset3;


WHEN reset3 => -- Externer RESET
e <= '1';
lcd_data <= x"38";
state <= drop_lcd_en;
next_command <= function_set;


WHEN function_set => --function set
e <= '1';
lcd_data <= "0011" & display_lines & character_font & "00";
state <= drop_lcd_en;
next_command <= display_control;


WHEN display_control => --display on/off control
e <= '1';
lcd_data <= "00001" & display_on_off & cursor & blink;
state <= drop_lcd_en;
next_command <= display_clear;


WHEN display_clear => --display clear
e <= '1';
lcd_data <= "00000001";
state <= drop_lcd_en;
next_command <= mode_set;


WHEN mode_set => --entry mode set
e <= '1';
lcd_data <= "00001" & display_on_off & cursor & blink;
state <= drop_lcd_en;
next_command <= ready;

--initialization complete

WHEN drop_lcd_en =>
busy <= '1';
e <= '0';
state <= next_command;

WHEN Line2 => --Setze Cursor an den Anfang der zweiten Zeile des Displays
busy <= '1';
rs <= '0';
rw <= '0';
e <= '1';
lcd_data <= "11000000";
char_count <= char_count + 1;
state <= drop_lcd_en;
next_command <= ready;

WHEN Return_Home =>
busy <= '1';
rs <= '0';
rw <= '0';
e <= '1';
lcd_data <= "00000001"; --clear Display
--char_count := 0;
char_count <= (others => '0');
state <= drop_lcd_en;
next_command <= ready;

WHEN ready =>
busy <= '0';
--IF ( char_count = 16 ) THEN
IF ( char_count = "010000" ) THEN
state <= Line2;
--ELSIF ( char_count = 33 ) THEN
ELSIF ( char_count = "100001" ) THEN
state <= Return_Home;
ELSE
state <= send;
END IF;

WHEN send =>
busy <= '1';


IF ( Modus_Wechsel = '1') THEN
state <= Return_Home;

ELSIF( valid_Pause = '1') THEN
rs <= '1';
rw <= '0';
e <= '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 -1
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 -2
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_count <= char_count + 1;
state <= drop_lcd_en;

elsif (valid_done_pressing = '1') THEN
rs <= '1';
rw <= '0';
e <= '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_count <= char_count + 1;
state <= drop_lcd_en;
END IF;  
END CASE;


END IF;
END PROCESS;
END RTL;

 


Die Ergebnisse vom Testbench werden in den folgenden Abbildungen dargestellt:
 
lcd1
                                                                             Abbildung 17 Testbench LCD-Display Initializierung
 
lcd2
                                                                             Abbildung 18 Testbench LCD-Display 
 
 

9. UART

9.1 Implementierung vom UART in VHDL

Bei der UART Datenübertragung handelt es sich um eine asynchrone Datenübertragung. Es existiert also kein Taktsignal mit dem sich Sender und Empfänger synchronisieren können. Die Baudrate (Übertragungsgeschwindigkeit) müssen bei beiden Kommunikationspartnern identisch eingestellt werden.

Die Datenübertragung per UART erfolgt mit einem festen Datenrahmen. Dieser muss beiden Kommunikationspartnern bekannt sein. [1]

Der UART-Frame besteht aus einem Start-Bit, 8 Datenbits und einem Stop-Bits (Abbildung 19)

UART timing Diagramm
                                                                              Abbildung 19 Datenrahmen eines UART
 

Im Idle-Zustand ist der UART-Bus auf logisch 1. Das Startbit ist logisch 0 und signalisiert den Start eines UART Frames. Die Kommunikation fängt an, wenn der Computer ein 0 logic sendet. Dieses wird im Receiver detektiert. Dann folgen die acht Bits und der Stop-Bit. Die Abtastung der Bits wird mit Hilfe eines Zählers, der die Dauer eines Bit rechnet, realisiert.

FSMUARTRX
                                                                              Abbildung 20 Finit State Machine UART-Rx
 

Nach dem Erhalt eines Datenrahmens werden die extrahierte 8-Daten-Bits und ein Valid-Bit (w_rx_dv) zum UART Transmitter gesendet. Der Transmitter sendet zunächst den Start-Bit (0 Logic), danach folgen die Datenbits und anschließend das Stop Bit. (Abbildung 21)

fsmuarttx
                                                                               Abbildung 21 Finit State Machine UART-Tx
 

Der Datenaustausch erfolgt durch einen RS 232-Anschluss, um eine Verbindung zwischen dem Altera FPGA-board und dem PC herzustellen. (Abbildung 21)

Die Entity UART-Top besteht aus drei Komponenten. (Abbildung 2)  

  • Entity uart_rx dient dazu, die Daten die vom PC zugesendet aufzufangen und entsprechend die 8 Daten Bits in einem 8 Bit Vektor zu speichern. Dieser wird in Top_UART innerhalb der Prozessen „ token_generation “ und „ audio_sound “ zu morse enkodiert und zum AudioCodec übertragen.
  • Entity uart_tx sendet die Daten zum PC
  • Entity Binary_To_7Segment, die sich die angefangenen hexadezimalzahlen Daten auf dem Board anzeigen lassen.

-- Dateiname: uart_rx
-- Design Software : Quartus II 13.0sp1 (64-bit)
--
-- Erstellt:
-- Bouchnak Wassim
-- Moujahid Bouhouch
-- Hier werden 4 Komponenten (uart_rx , uart_tx, 2*Binary_To_7Segment) instanziiert und miteinander verbunden
-- Die empfangene ASCII Zahl wird innerhalb der Prozessen „ token_generation “ und „ audio_sound “
-- zu morse enkodiert und zum AudioCodec übertragen.
--
--
library ieee;
use ieee.std_logic_1164.all;

entity UART_Top is
port (
-- Main Clock (50 MHz)
i_Clk : in std_logic;
nrst : in std_logic;
clockNC_en : in std_logic;
i_UART_RX : in std_logic; -- UART RX Data

i_UART_TX : out std_logic;
tx_busy : out std_logic;
uart_sound : out std_logic;



-- Segment1 is upper digit, Segment2 is lower digit of 7 Segment Display
o_Segment1_A : out std_logic;
o_Segment1_B : out std_logic;
o_Segment1_C : out std_logic;
o_Segment1_D : out std_logic;
o_Segment1_E : out std_logic;
o_Segment1_F : out std_logic;
o_Segment1_G : out std_logic;

o_Segment2_A : out std_logic;
o_Segment2_B : out std_logic;
o_Segment2_C : out std_logic;
o_Segment2_D : out std_logic;
o_Segment2_E : out std_logic;
o_Segment2_F : out std_logic;
o_Segment2_G : out std_logic
);
end entity UART_Top;

architecture RTL of UART_Top is

signal w_RX_DV : std_logic;
signal w_RX_Byte : std_logic_vector(7 downto 0);
signal w_RX_DV_delayed : std_LOGIC;
SIGNAL count : NATURAL := 0;
SIGNAL token : STD_LOGIC_VECTOR (15 downto 0) := (others => '0');
SIGNAL soft_reset_var : std_logic;

signal w_Segment1_A, w_Segment2_A : std_logic;
signal w_Segment1_B, w_Segment2_B : std_logic;
signal w_Segment1_C, w_Segment2_C : std_logic;
signal w_Segment1_D, w_Segment2_D : std_logic;
signal w_Segment1_E, w_Segment2_E : std_logic;
signal w_Segment1_F, w_Segment2_F : std_logic;
signal w_Segment1_G, w_Segment2_G : std_logic;

-- signal two_bits_count : NATURAL RANGE 0 TO 16 := 0;




COMPONENT uart_rx is
port (
clk : in std_logic;
nrst : in std_logic;
rx : in std_logic;
data : out std_logic_vector(7 downto 0);
rx_dv : out std_logic
);

END COMPONENT uart_rx;


COMPONENT uart_tx is
port (
clk : in std_logic;
nrst : in std_logic;
rx_dv : in std_logic;
data : in std_logic_vector(7 downto 0);
busy : out std_logic;
tx : out std_logic
);

END COMPONENT uart_tx;
COMPONENT Binary_To_7Segment is
port (
i_Clk : in std_logic;
i_Binary_Num : in std_logic_vector(3 downto 0);
o_Segment_A : out std_logic;
o_Segment_B : out std_logic;
o_Segment_C : out std_logic;
o_Segment_D : out std_logic;
o_Segment_E : out std_logic;
o_Segment_F : out std_logic;
o_Segment_G : out std_logic
);

END COMPONENT Binary_To_7Segment;




begin


UART_RX_Inst : COMPONENT uart_rx
port map (
clk => i_Clk,
nrst => nrst ,
rx => i_UART_RX,
data => w_RX_Byte,
rx_dv => w_RX_DV
);


UART_TX_Inst : COMPONENT uart_tx
port map (
clk => i_Clk,
nrst => nrst ,
rx_dv => w_RX_DV,
data => w_RX_Byte,
busy => tx_busy,
tx => i_UART_TX
);

SevenSeg1_Inst : COMPONENT Binary_To_7Segment
port map (
i_Clk => i_Clk,
i_Binary_Num => w_RX_Byte(7 downto 4),
o_Segment_A => w_Segment1_A,
o_Segment_B => w_Segment1_B,
o_Segment_C => w_Segment1_C,
o_Segment_D => w_Segment1_D,
o_Segment_E => w_Segment1_E,
o_Segment_F => w_Segment1_F,
o_Segment_G => w_Segment1_G
);

o_Segment1_A <= not w_Segment1_A;
o_Segment1_B <= not w_Segment1_B;
o_Segment1_C <= not w_Segment1_C;
o_Segment1_D <= not w_Segment1_D;
o_Segment1_E <= not w_Segment1_E;
o_Segment1_F <= not w_Segment1_F;
o_Segment1_G <= not w_Segment1_G;


-- Binary to 7-Segment Converter for Lower Digit
SevenSeg2_Inst : COMPONENT Binary_To_7Segment
port map (
i_Clk => i_Clk,
i_Binary_Num => w_RX_Byte(3 downto 0),
o_Segment_A => w_Segment2_A,
o_Segment_B => w_Segment2_B,
o_Segment_C => w_Segment2_C,
o_Segment_D => w_Segment2_D,
o_Segment_E => w_Segment2_E,
o_Segment_F => w_Segment2_F,
o_Segment_G => w_Segment2_G
);

o_Segment2_A <= not w_Segment2_A;
o_Segment2_B <= not w_Segment2_B;
o_Segment2_C <= not w_Segment2_C;
o_Segment2_D <= not w_Segment2_D;
o_Segment2_E <= not w_Segment2_E;
o_Segment2_F <= not w_Segment2_F;
o_Segment2_G <= not w_Segment2_G;


token_generation: PROCESS (nrst, w_RX_DV)
BEGINIF nrst = '0' THEN
-- Initialisierung des Signals
token <= (OTHERS => '0');

ELSIF w_RX_DV'EVENT AND w_RX_DV = '1' THEN


CASE w_RX_Byte IS

--Buchstaben groß
WHEN x"41" => token <="0000000000001101"; -- A
WHEN x"42" => token <="0000000001010111"; -- B
WHEN x"43" => token <="0000000001110111"; -- C
WHEN x"44" => token <="0000000000010111"; -- D
WHEN x"45" => token <="0000000000000001"; -- E
WHEN x"46" => token <="0000000001110101"; -- F
WHEN x"47" => token <="0000000000011111"; -- G
WHEN x"48" => token <="0000000001010101"; -- H
WHEN x"49" => token <="0000000000000101"; -- I
WHEN x"4A" => token <="0000000011111101"; -- J
WHEN x"4B" => token <="0000000000110111"; -- K
WHEN x"4C" => token <="0000000001011101"; -- L
WHEN x"4D" => token <="0000000000001111"; -- M
WHEN x"4E" => token <="0000000000000111"; -- N
WHEN x"4F" => token <="0000000000111111"; -- O
WHEN x"50" => token <="0000000001111101"; -- P
WHEN x"51" => token <="0000000011011111"; -- Q
WHEN x"52" => token <="0000000000011101"; -- R
WHEN x"53" => token <="0000000000010101"; -- S
WHEN x"54" => token <="0000000000000011"; -- T
WHEN x"55" => token <="0000000000110101"; -- U
WHEN x"56" => token <="0000000011010101"; -- V
WHEN x"57" => token <="0000000000111101"; -- W
WHEN x"58" => token <="0000000011010111"; -- X
WHEN x"59" => token <="0000000011110111"; -- Y
WHEN x"5A" => token <="0000000001011111"; -- Z
 --Buchstaben klein
WHEN x"61" => token <="0000000000001101"; -- a
WHEN x"62" => token <="0000000001010111"; -- b
WHEN x"63" => token <="0000000001110111"; -- c
WHEN x"64" => token <="0000000000010111"; -- d
WHEN x"65" => token <="0000000000000001"; -- e
WHEN x"66" => token <="0000000001110101"; -- f
WHEN x"67" => token <="0000000000011111"; -- g
WHEN x"68" => token <="0000000001010101"; -- h
WHEN x"69" => token <="0000000000000101"; -- i
WHEN x"6A" => token <="0000000011111101"; -- j
WHEN x"6B" => token <="0000000000110111"; -- k
WHEN x"6C" => token <="0000000001011101"; -- l
WHEN x"6D" => token <="0000000000001111"; -- m
WHEN x"6E" => token <="0000000000000111"; -- n
WHEN x"6F" => token <="0000000000111111"; -- o
WHEN x"70" => token <="0000000001111101"; -- p
WHEN x"71" => token <="0000000011011111"; -- q
WHEN x"72" => token <="0000000000011101"; -- r
WHEN x"73" => token <="0000000000010101"; -- s
WHEN x"74" => token <="0000000000000011"; -- t
WHEN x"75" => token <="0000000000110101"; -- u
WHEN x"76" => token <="0000000011010101"; -- v
WHEN x"77" => token <="0000000000111101"; -- W
WHEN x"78" => token <="0000000011010111"; -- x
WHEN x"79" => token <="0000000011110111"; -- y
WHEN x"7A" => token <="0000000001011111"; -- z

--Zahlen
WHEN x"30" => token <="0000001111111111"; -- 0
WHEN x"31" => token <="0000001111111101"; -- 1
WHEN x"32" => token <="0000001111110101"; -- 2
WHEN x"33" => token <="0000001111010101"; -- 3
WHEN x"34" => token <="0000001101010101"; -- 4
WHEN x"35" => token <="0000000101010101"; -- 5
WHEN x"36" => token <="0000000101010111"; -- 6
WHEN x"37" => token <="0000000101011111"; -- 7
WHEN x"38" => token <="0000000101111111"; -- 8
WHEN x"39" => token <="0000000111111111"; -- 9

--Sonderzeichen
WHEN x"8E" => token <="0000000011011101"; -- Ä
WHEN x"84" => token <="0000000011011101"; -- ä
WHEN x"99" => token <="0000000001111111"; -- Ö
WHEN x"9A" => token <="0000000011110101"; -- Ü
WHEN x"81" => token <="0000000011110101"; -- ü
WHEN x"E1" => token <="0101011111010101"; -- ß
WHEN x"2D" => token <="0000110101010111"; -- -
WHEN x"2E" => token <="0000110111011101"; -- .
WHEN x"2C" => token <="0000111101011111"; -- ,
WHEN x"2B" => token <="0000000111011101"; -- +
WHEN x"22" => token <="1000000000000000"; -- " "
WHEN x"0A" => token <="1100000000000000"; -- "ENTER"

WHEN OTHERS => token <= (OTHERS => '0');

END CASE;

END IF;
END PROCESS; audio_sound: PROCESS(nrst,clockNC_en,w_RX_DV)VARIABLE two_bits_count : NATURAL RANGE 0 TO 16 := 0; BEGIN



IF nrst = '0' OR w_RX_DV = '1' THEN

uart_sound <= '0';
count <= 0;
two_bits_count := 0;
  ELSIF clockNC_en'EVENT AND clockNC_en = '1' THEN

IF two_bits_count >= 0 AND two_bits_count < 16 THEN

--Codierung eines "Dits" als Signal mit doppelter Periodendauer der Wortgeschwindigkeit
IF token(two_bits_count + 1 DOWNTO two_bits_count) = "01" THEN

IF count >= 2 THEN
count <= 0;
uart_sound <= '0';
two_bits_count := two_bits_count + 2;
ELSE
count <= count + 1;
uart_sound <= '1';
END IF;

--Codierung eines "Dahs" als Signal mit sechsfacher Periodendauer der Wortgeschwindigkeit
--(3-fache Dit-Länge)
ELSIF token(two_bits_count +1 DOWNTO two_bits_count) = "11" THEN

IF count >= 6 THEN
count <= 0;
uart_sound <= '0';
two_bits_count := two_bits_count + 2;
ELSE
count <= count + 1;
uart_sound <= '1';
END IF;

--Auslesen beenden, wenn nur noch Nullen im Token-Signal gelesen werden
ELSE

uart_sound <= '0';
two_bits_count := 16;

END IF;

END IF;



--Ist das Token-Signal komplett ausgelesen ausgabe_modusen und eine neue Taste wurde gedrückt,
--dann soll das bitpaarweise Auslesen erneut beginnen
--IF two_bits_count >= 16 THEN

end if;
 END PROCESS;
end RTL;

 -- Dateiname: uart_rx
-- Design Software : Quartus II 13.0sp1 (64-bit)
--
-- Erstellt:
-- Bouchnak Wassim
--
--
-- UART-rx-Schnittstelle dient zum Empfangen von Daten über die Datenleitung rx.
-- der Empfänger berechnet den Takt des Senders und synchronisiert
-- sich mit Hilfe der Start- und Stopbits darauf
--sobald ein datenpacket vollständig empfangen wird, wird das valid signal für nur
--ein Taktzyklus um 1 gesetzt
--------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;


entity uart_rx is
port (
clk : in std_logic; -- main clock 50Mhz
nrst : in std_logic; --Reset
rx : in std_logic; -- rx input
data : out std_logic_vector(7 downto 0); --
rx_dv : out std_logic -- impuls Signal wird um '1' gesetzt, sobald ein Datenpacket empfangen wird
);
end uart_rx;

architecture rtl of uart_rx is

type statetyp is (START_erkennen, START, HALBES_BIT, DATA_simple, STOP);
signal state : statetyp;

-- CLK / baud rate --damit die dauer eines bit ermittelt wird
constant clk_T_bit : natural := (50e6 / 115200);

-- Zum Zählen von Taktperioden
signal clk_counter : integer range 0 to clk_T_bit - 1;

-- Zum Zählen die Anzahl der übertragenen Bits
signal bit_counter : integer range 0 to 7;

-- Das um einen Taktzyklus verzögerte rx-Signal
signal rx_old : std_logic;

signal data_reg : std_logic_vector(7 downto 0);

begin

process(clk)
begin
if rising_edge(clk) then

-- Pulsed
rx_dv <= '0';

rx_old <= rx;

if nrst = '0' then
data_reg <= (others => '0');
data <= (others => '0');
rx_dv <= '0';
state <= START_erkennen;
clk_counter <= 0;
rx_old <= '0';
bit_counter <= 0;


else


case state is

-- Warten auf die fallende Flanke an rx
when START_erkennen =>
if rx_old = '1' and rx = '0' then
state <= START;
end if;

-- warten auf die dauer eines bit
when START =>
if clk_counter = (clk_T_bit -2) then
state <= HALBES_BIT;
clk_counter <= 0;
else
clk_counter <= clk_counter + 1;
end if;

-- Warten auf die Dauer eines halben bits, um in der mitte des bits zu tasten.
when HALBES_BIT =>
if clk_counter = (clk_T_bit -1) / 2 then
state <= DATA_simple;
clk_counter <= (clk_T_bit -1); --clk_counter wird um den maximalen Wert gesetzt,
else --damit das erste Bit im nächsten STATE direkt eingelesen wird
clk_counter <= clk_counter + 1;
end if;

-- Sample all data bits
when DATA_simple =>
if clk_counter = (clk_T_bit -1) then
clk_counter <= 0;

-- die Daten werden von hohem zu niedrigem Index geschoben
data_reg(7) <= rx;
for i in 7 downto 1 loop
data_reg(i - 1) <= data_reg(i);
end loop;

if bit_counter = 7 then
state <= STOP;
bit_counter <= 0;
else
bit_counter <= bit_counter + 1;
end if;

else
clk_counter <= clk_counter + 1;
end if;

-- Warten auf die Dauer des stop-bits und zuweisung der Ausgangssignalen
when STOP =>
if clk_counter = (clk_T_bit -1) then
state <= START_erkennen;
data_reg <= (others => '0');
data <= data_reg;
clk_counter <= 0;

-- gibt an, ob ein Datenpacket vollständig empfangen Wurde
if rx = '0' then
rx_dv <= '0'; -- error
elsif rx = '1' then
rx_dv <= '1'; -- ok
else
rx_dv <= 'X'; --error
end if;


else
clk_counter <= clk_counter + 1;
end if;

end case;

end if;
end if;
end process;

end rtl;

 

-- Dateiname: LCD_Controller
-- Design Software : Quartus II 13.0sp1 (64-bit)
--
-- Erstellt:
-- Bouchnak Wassim
--
--
-- UART-tx-Schnittstelle dient zum Senden von Daten über die Datenleitung tx
-- Die Daten werden als serieller digitaler Datenstrom mit einem fixen Rahmen übertragen,
-- der aus einem Start-Bit, acht Datenbits und einem Stopp-Bits besteht
-- die empfangenen Datenstrom in UART-RX werden zum Computer zurückgeschickt
--------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;


entity uart_tx is
port (
clk : in std_logic;
nrst : in std_logic;
rx_dv : in std_logic;
data : in std_logic_vector(7 downto 0);
busy : out std_logic;
tx : out std_logic
);
end uart_tx;

architecture rtl of uart_tx is

-- CLK / baud rate --damit die dauer eines bit ermittelt wird
constant clk_T_bit : natural := (50e6 / 115200); -- CLK / baud rate --damit die dauer eines bit ermittelt wird

signal clk_counter : integer range 0 to clk_T_bit - 1;

-- Zum Zählen der Anzahl der übertragenen Bits
signal bit_counter : integer range 0 to 7;

type statetyp is (IDLE, START, DATA_SENDEN, STOP);
signal state : statetyp;

-- Zur Abtastung der Dateneingabe
signal data_sig : std_logic_vector(7 downto 0);

begin

process(clk)

begin
if rising_edge(clk) then

-- default value
tx <= '1';
busy <= '1';

if nrst = '0' then
state <= IDLE;
busy <= '1';
tx <= '1';
bit_counter <= 0;
data_sig <= (others => '0');

else

case state is

-- warten auf das start signal
when IDLE =>
busy <= '0';

if rx_dv = '1' then
state <= START;
data_sig <= data;
busy <= '1';
end if;

-- das start bit senden
when START =>
tx <= '0';

if clk_counter = clk_T_bit - 1 then
clk_counter <= 0;
state <= DATA_SENDEN;
else
clk_counter <= clk_counter + 1;

end if;

-- data bits senden
when DATA_SENDEN =>
tx <= data_sig(bit_counter);

if clk_counter = clk_T_bit - 1 then
clk_counter <= 0;

if bit_counter = 7 then
state <= STOP;
bit_counter <= 0;
else
bit_counter <= bit_counter + 1;
end if;

else
clk_counter <= clk_counter + 1;

end if;

-- stop bit senden
when STOP =>
if clk_counter = clk_T_bit - 1 then
clk_counter <= 0;
state <= IDLE;
busy <= '0';
else
clk_counter <= clk_counter + 1;

end if;

end case;

end if;
end if;
end process;

end rtl;
 

 

Um die Funktionalität dieser Komponenten zu testen, wurde ein Testbench erstellt, indem das untergeordnete Top Level Uart_Top instanziiert ist.

Zunächst wird nach 10 Taktzyklus das nrst (reset) Eingangssignal desaktiviert, danach werden 2 Datenpaket (ASCII A (x 41) und ASCII Z (x 5A)) bitweise zum rx Eingangssignal geschickt. Dabei wird nach dem Empfang der Daten die durch uart_tx gesendeten Bits geprüft, ob sie richtig oder falsch sind.

Die folgenden Bilder stellen das Ergebnis der Simulation dar.

 
uart1
                                                                                Abbildung 19 Testbench UART-Teil1
 
uart2
                                                                                 Abbildung 20 19 Testbench UART-Teil2

9.2 Python 

Da Python bei der Datenverarbeitung sehr stark ist, wurde eine Schnittstelle mit dem PC und dem FPGA Board mit Hilfe der Bibliothek Pyserial erstellt. Darüber hinaus wurde eine Userinterface zum Senden vom Werten zum Board erstellt, welche dann auf Morse Codiert und einen Ton erzeugen.

Damit hat man die Möglichkeit zunächst den Port und Baud Rate in den Entrees zu schreiben. Diese werden für eine erfolgreiche Kommunikation benötigt.

py3
                                                                                  Abbildung 21 Python Userinterface
Bei den falschen Eingaben wird die folgende Fehlermeldung angezeigt:
 
py1
                                                                                  Abbildung 22 Python Bespiel Fehlermeldung
Bei einer erfolgreichen Verbindung kann man mit Hilfe der Daten einen String senden z.B. auf Morse Codieren.
py2
                                                                                  Abbildung 23 Python Userinterface 

Quellcode 
 """
-- GUI als Schnittstelle zwiscxhen dem Laptop und FPGA
-- zum Eingabe vom Sätze die dann auf morse Codieren werden
-- Erstellt:
-- Von Moujahid Bouhouch
--
-- Am 10.10.2021
--
--------------------------------------------------------------------------------
"""
import serial
import time
from tkinter import *
from tkinter import messagebox
global BoardNotConnected # keepTrack of btn State
global EingabeTxt
BoardNotConnected = True
class FPGABoard:

def __init__(self):
pass
def Connection(self):
if self.isOpen():
return True
else:
return False
def CloseThePort(self):
if self.isOpen():
self.__del__()
else:
return False
 def btnConnection():
global BoardNotConnected
global FPGA_Port
PortVar = COMEntryVar.get()
BaudVar = BaudEntryVar.get()
BaudVar = int(BaudVar)
if BoardNotConnected:
try:
print(PortVar, BaudVar)
FPGA_Port = serial.Serial(PortVar, BaudVar, timeout=1)
time.sleep(1)
Offbtn.config(image=On)
BoardNotConnected = False
except:
messagebox.showerror(title= "Fehler",
message="Eine Kommunikation zwischen dem PC und Board konnte nicht erstellt werden"
"\n Bitte prüfen Sie Ihre Einstellungen")
else:
FPGABoard.CloseThePort(FPGA_Port)
Offbtn.config(image=Off)
BoardNotConnected = True
print("no")
def SendWord():

StringToSend = EingabeTxt.get()
StringToSend = StringToSend.upper()
print(StringToSend)
time.sleep(1)
if BoardNotConnected == False:

for i in range(0, len(StringToSend)):
#String auf character stellen und diese zum Board zuschicken
buchstabe = str(StringToSend[i])
print(StringToSend[i])
FPGA_Port.write(buchstabe.encode())
time.sleep(2)
else:
messagebox.showerror(title= "Fehler",
message="Das Board ist nicht verbungen")
root = Tk()
root.title('Morse Coder')
root.iconbitmap('images/morseico.ico')
# Connection bnt images
On = PhotoImage(file="images/on.png")
Off = PhotoImage(file="images/off.png")
root.configure(background='#000000')
root.geometry("400x145")
root.maxsize(400, 145)
root.minsize(400, 145)
#Einstellungung
Settings = LabelFrame(root, text="Einstellungen")
Settings.place(x=1,y=1, width=300, height=65)
#Fill The Frames
COMEntryVar = StringVar()
BaudEntryVar = StringVar()
COMLbl = Label(Settings, text="Port")
COMEntry = Entry(Settings, textvariable = COMEntryVar)
COMEntry.insert(END, 'COM7')
BaudLbl = Label(Settings, text="Baud Rate")
BaudEntry = Entry(Settings, textvariable = BaudEntryVar)
BaudEntry.insert(END,115200)
#Place the frames
COMLbl.grid(row=0, column=0)
COMEntry.grid(row=1, column=0)
BaudLbl.grid(row=0, column=1)
BaudEntry.grid(row=1, column=1)
#Frame für die Verbindung
ConnectionFrame = LabelFrame(root,bg='white', text="Verbindung")
ConnectionFrame.place(x=300,y=1, width=100, height=65)
Offbtn = Button(ConnectionFrame, image=Off, bd=0, command=btnConnection)
Offbtn.place(relx=0.5, rely=0.5, anchor=CENTER)
 #Frame für Codieren
CodierungFame = LabelFrame(root,bg='white')
CodierungFame.place(x=1,y=70, width=400, height=66)
EingabeFrame = LabelFrame(CodierungFame,bg='white', text="Eingabe")
EingabeFrame.place(x=1,y=1, width=300, height=65)
BtnFrame = LabelFrame(CodierungFame,bg='white', text="Senden")
BtnFrame.place(x=300,y=1, width=100, height=65)
EingabeTxt = StringVar()
EingabeEntry = Entry(EingabeFrame, textvariable = EingabeTxt)
EingabeEntry.insert(END, 'VHDL')
EingabeEntry.pack(side=LEFT, expand=True, fill='both')
SendBtn = Button(BtnFrame, text="Daten Senden",command=SendWord)
SendBtn.pack(side=LEFT, expand=True, fill='both')
 
root.mainloop()

 

Quellen:

Kai-Frederik Nessitt

https://www.mikroelektronik.w-hs.de/index.php/projects/morse-de-und-encoder-mittels-fpga

Scott Larson

forum.digikey.com

AntonZero

https://github.com/AntonZero/WM8731-Audio-codec-on-DE10Standard-FPGA-board

https://www.nandland.com/vhdl/modules/binary-to-7-segment.html

https://academy.vhdlwhiz.com/

http://www.mathe-mit-methode.com/schlaufuchs_web/elektrotechnik/mikrocontroller_lernmaterial/microcontroller_allgemein/mikrocontroller_ext_hardware/mikrocontroller_uart.html

 

Download: 
Link => eds_Projekt.zip

Autoren:
 Wassim Bouchnak und Moujahid Bouhouch

Gelsenkirchen 2021

_______________________________________________________________________________________________________________________________________________________________


 

 5. Erweiterung eines Morsedecoders und -encoders um einen Klopfmodus sowie eine Laserschnittstelle

 


1. Einleitung

Im Rahmen unserer Projektarbeit im Fach „Entwurf digitaler Systeme“ sollte auf einem FPGA Entwicklungsboard ein Morse De- sowie Encoder umgesetzt werden. Hierzu wurde eine Ausarbeitung als Vorlage aus einer Abschlussarbeit zur Verfügung gestellt (Morse De- und Encoder mittels FPGA Nessit).  Auf Grundlage dieser Vorlage sowie mit Integration von bereits bestehender Teilprojekten von Kommilitonen (insbesondere Tim Michalek und Nina Häselhoff) wurden weitere Morsefunktionen umgesetzt. Von Kommilitonen wurde zum Beispiel bereits VGA, SD-Karte, UART sowie ein Mikrofoneingabe integriert und somit große Teile der Schnittstellenanbindungen des FPGAs abgedeckt.

Das folgende Bild stellt eine Übersicht über die Funktionen des Morsecoders dar:

Abbildung 1: Set-up und Funktionen des Morsecoders


2. Laserintegration

Der herkömmliche Morsecode wird über viele verschiedene Medien übertragen. Hierbei spielte auch das Lichtmorsen eine größere Rolle in der Schifffahrt. Zu der Übertragung auf See wurden hierbei Scheinwerfer mit Blenden verwendet, um kurze und lange Signale zu übertragen. In unserem ersten Projektteil wird das Morsesignal neben der Audioausgabe auch über ein Laser ausgegeben, der mit einem Photoresistor am Empfänger-Arduino interagiert. Die Ausgabe des Signals erfolgt über den GPIO AB22. An diesem Pin liegen 3.3V an. Diese Spannung ist für unser Lasermodul KY-008 ausreichend um Lichtsignale durch den gesamten Raum zu senden.

Abbildung 2: KY-008 Lasermodul

Zum Senden des Morsesignals wird das im morse_codec definierte Signal sound an die light-Entity übergeben. Hier geht das Signal am Pin encoded_signal ein und wird, bei aktiviertem Lasermodus an den GPIO-Output übergeben.

LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
 
ENTITY light IS
 
PORT    (
            clk      :   IN  STD_ULOGIC; --50Mhz Systemtakt
            clockWPM :   IN  STD_ULOGIC; -- WPM Takt
 
            reset          :   IN  STD_ULOGIC; --Reset-Signal
            encoded_signal  :   IN  STD_ULOGIC; --Morse Signal from Audio output of Keyboard
            laser_mode_en   :   IN  STD_ULOGIC; --Laser_mode enabled
            enable :   IN  STD_ULOGIC; --DE oder Encoder
 
            laser_input :   IN  STD_ULOGIC; -- Eingabe vom Fotoresistor
            laser_val :  OUT STD_ULOGIC; --Laser Morse Signal
 
            laser_out       :  OUT  STD_ULOGIC
 
    ); 
END light;
 
 
 
 
ARCHITECTURE structure OF light IS
 
 
 
 
BEGIN
 
led:   PROCESS (clockWPM, reset, encoded_signal,enable)
BEGIN
      IF reset = '0' THEN
           laser_out <= '0';
           laser_val <= '0';
 
 
      elsif clockWPM'EVENT AND clockWPM = '1' THEN
 
         IF laser_mode_en = '1' THEN
             IF  enable = '1' THEN
                 laser_out <= encoded_signal;
                 laser_val <= '0';
             ELSE
                 laser_val <= laser_input;
                 laser_out <= '0';
             END IF;
         END IF;
      END IF;
 
END PROCESS;
 
END structure;

 

Das ausgehende Lasersignal kann anschließend von einem am Arduino geschalteten Photoresistor empfangen und durch eine pyramidenförmig aufgebaute Tabelle ausgewertet werden. Der Aufbau des Morsecodes lässt sich wie folgt darstellen und verarbeiten:

Abbildung 3: Morsepyramide

Im Arduino-Code lässt sich dies wie folgt beschreiben:

 
const char MorseTree[] =
                         {'\0', 'E', 'T', 'I', 'A', 'N', 'M', 'S',
                          'U', 'R', 'W', 'D', 'K', 'G', 'O', 'H',
                          'V', 'F', 'F', 'L', 'A', 'P', 'J', 'B',
                          'X', 'C', 'Y', 'Z', 'Q', '\0', '\0', '5',
                          '4', '\0', '3', '\0', '\0', '\0', '2', '\0',
                          '\0', '+', '\0', '\0', '\0', '\0', '1', '6',
                          '=', '/', '\0', '\0', '\0', '(', '\0', '7',
                          '\0', '\0', '\0', '8', '\0', '9', '0', '\0',
                          '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
                          '\0', '\0', '\0', '?', '_', '\0', '\0', '\0',
                          '\0', '"', '\0', '\0', '.', '\0', '\0', '\0',
                          '\0', '@', '\0', '\0', '\0', '\0', '\0', '\0',
                          '-', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
                          '\0', ';', '!', '\0', ')', '\0', '\0', '\0',
                          '\0', '\0', ',', '\0', '\0', '\0', '\0', ':',
                          '\0', '\0', '\0', '\0', '\0', '\0', '\0'
                         };

 

Hierbei verändert ein einkommendes Dit den codePointer um codePointer = (2*CodePointer) + 1 und jedes eingehende Dah resultiert in einer Änderung von codePointer = (2*codePointer*) + 2.  Durch eine lange Pause ergibt sich ein Wortende und es wird der Buchstabe innerhalb des MorseTrees mit dem codePointer definiert und ausgegeben. Neben dem Empfangen des Laser-Morsesignals wurde auch das Senden von Morse-Code implementiert. Hierbei wurde auf die Library cww_MorseTx.h zurückgegriffen. Hierfür werden die Eingaben im Seriellen Monitor zunächst im Char-Typen verarbeitet und in einer Case-Struktur ausgegeben.

 
while (Serial.available() > 0) {
       char buffer = Serial.read();
       int ibuffer = buffer;
       if (ibuffer == 10) {
           Serial.println("");
           morseWithTone.send(" ");
       }
       else {
           Serial.print(buffer);
           morseWithTone.send(buffer);
       }

 

 
Diese einfache Möglichkeit Morsecode zu Senden ermöglicht es auch das FPGA in schnelleren WPM Modis zu testen, ohne auf seine eigenen Morsefähigkeiten vertrauen zu müssen. Die Detektion des vom Arduino ausgesendeten Lasersignals erfolgt ebenfalls in der light.vhd. Hierfür wird das aktuell am GPIO AB21 anliegende Signal des Photoresistors an das Signal laser_val übergeben. Dieses wird in der Entitiy morse_signal_decoder im clockWPM Takt abgetastet und auf das Shiftregister übertragen.

 
if clockWPM = '1' then 
 
      if aufnahme_switch ='0' AND laser_mode_en = '0' then
         shift_reg <= shift_reg(shift_reg'high -1 downto shift_reg'low) & NOT morse_signal;
 
      elsif aufnahme_switch ='1' AND laser_mode_en = '0' then
         shift_reg <= shift_reg(shift_reg'high -1 downto shift_reg'low) & NOT rec_audio;
 
      elsif laser_mode_en = '1' AND aufnahme_switch ='0' THEN
         shift_reg <= shift_reg(shift_reg'high -1 downto shift_reg'low) & laser_val;
      end if;
 
end if;

 



Dieses Shiftregister wird in einem gleichzeitig zum Prozess umgesetzten Abfrage in entsprechende DAH, DIT oder Pausen umgewandelt.

dah_low <= '1' WHEN shift_reg(9 DOWNTO 0)="0000000000" ELSE '0';                                           --lange Pause wenn das Shift-Signal 9 Nullen in folge enthält (Taster nicht gedrückt) 
dit_high <= NOT shift_reg(0) AND shift_reg(1) AND NOT (shift_reg(2) AND shift_reg(3) AND shift_reg(4));    --kurzer Tastendruck wenn für einen Takt bis max. drei Takte der Taster gedrückt wurde 
dah_high <= '1' WHEN shift_reg(5 DOWNTO 0)="111110" and shift_reg(10 DOWNTO 6)/="11111" ELSE '0';          --langer Tastendruck, wenn für 5 bis max. 9 Takte der Taster gedrückt wurde 
word_end <= '1' when (shift_reg (13 downto 0)= "00000000000000" and word_enable ='1') else '0';            --wortende wenn shift_reg nur aus nullen besteht

 

Diese Tokens können anschließend von dem Entity morse_codec.vhd weiterverarbeitet werden. Auf diese Umwandlung des eigentlichen Symbols wird im zweiten Projektteil eingegangen.

Zur Simulation der light.vhd wurde eine Testbench tb_light.vhd erstellt und die resultierenden Waveforms in Modelsim analysiert. Es ergibt sich für den Testprozess („ABC“):

Abbildung 4: tb_light in Modelsim


3. Integration des Klopfcodes

Neben der Codierung über den Morsecode gibt es auch noch weitere Kodierungsmöglichkeiten mit anderen Alphabet-Verschlüsselungen. Der Klopfcode ist hierbei eine Möglichkeit Textnachrichten Buchstabe für Buchstabe zu codieren. Anders als im Morse Code wird ein Buchstabe aus zwei Abfolgen von Klopftönen zusammengesetzt. Diese Klopftöne müssen erfasst und anschließend in einer Tabelle ausgewertet werden. Im römischen Alphabet des Klopfcodes sind 25 Zeichen definiert. Das C und K teilen sich hierbei das Signal mit den Werten 1 , 3.

Abbildung 5: Klopfcode-Tabelle

Zur Detektion dieser Klopfmuster wurde ein Klopfmodus integriert, der über einen Switch am FPGA eingeschaltet werden kann. Dieser Switch ändert die Programmabläufe massiv, um die Detektion von zweier Klopfabfolgen zu kombinieren und diese anschließend als ein einzelner Buchstabe auszugeben. Zur Ausgabe dieser Zeichen wurde ein Klopftoken-System (nachfolgender Code) entwickelt, dass es ermöglicht, möglichst nahe am Morsecode zu bleiben, um die Länge des Programms und auch den Hardwareaufwand gering zu halten. In diesem Tokensystem setzt sich jeder Buchstabe aus zwei Token zusammen, die jeweils Zeilen oder Spaltenwert repräsentieren. Diese werden anschließend nacheinander abgefragt und ausgegeben. Die Umsetzung dieser Ausgabe ist im Flussdiagramm in Abbildung 6 beschrieben.

 
IF klopfmode_enable = '1' THEN
 
 
CASE data_keyboard IS
 
--Buchstaben
WHEN x"1C" => token  <="00000000000011"; -- A
              token2 <="00000000000011"; -- A
WHEN x"32" => token  <="00000000000011"; -- B
              token2 <="00000000001111"; -- B
WHEN x"21" => token  <="00000000000011"; -- C
              token2 <="00000000111111"; -- C
WHEN x"23" => token  <="00000000000011"; -- D
              token2 <="00000011111111"; -- D
WHEN x"24" => token  <="00000000000011"; -- E
              token2 <="00001111111111"; -- E
WHEN x"2B" => token  <="00000000001111"; -- F
              token2 <="00000000000011"; -- F
WHEN x"34" => token  <="00000000001111"; -- G
              token2 <="00000000001111"; -- G
WHEN x"33" => token  <="00000000001111"; -- H
              token2 <="00000000111111"; -- H
WHEN x"43" => token  <="00000000001111"; -- I
              token2 <="00000011111111"; -- I
WHEN x"3B" => token  <="00000000001111"; -- J
              token2 <="00001111111111"; -- J
WHEN x"42" => token  <="00000000000011"; -- K
              token2 <="00000000111111"; -- K
WHEN x"4B" => token  <="00000000111111"; -- L
              token2 <="00000000000011"; -- L
WHEN x"3A" => token  <="00000000111111"; -- M
              token2 <="00000000001111"; -- M
WHEN x"31" => token  <="00000000111111"; -- N
              token2 <="00000000111111"; -- N
WHEN x"44" => token  <="00000000111111"; -- O
              token2 <="00000011111111"; -- O
WHEN x"4D" => token  <="00000000111111"; -- P
              token2 <="00001111111111"; -- P
WHEN x"15" => token  <="00000011111111"; -- Q
              token2 <="00000000000011"; -- Q
WHEN x"2D" => token  <="00000011111111"; -- R
              token2 <="00000000001111"; -- R
WHEN x"1B" => token  <="00000011111111"; -- S
              token2 <="00000000111111"; -- S
WHEN x"2C" => token  <="00000011111111"; -- T
              token2 <="00000011111111"; -- T
WHEN x"3C" => token  <="00000011111111"; -- U
              token2 <="00001111111111"; -- U
WHEN x"2A" => token  <="00001111111111"; -- V
              token2 <="00000000000011"; -- V
WHEN x"1D" => token  <="00001111111111"; -- W
              token2 <="00000000001111"; -- W
WHEN x"22" => token  <="00001111111111"; -- X
              token2 <="00000000111111"; -- X
WHEN x"1A" => token  <="00001111111111"; -- Y
              token2 <="00000011111111"; -- Y
WHEN x"35" => token  <="00001111111111"; -- Z
              token2 <="00001111111111"; -- Z
 
WHEN x"29" => token  <="10000000000000"; -- " "
              token2 <="00000000000000";
WHEN x"5A" => token  <="11000000000000"; -- "ENTER"
              token2 <="00000000000000";
 
WHEN x"66" => token  <="11010000000000"; -- "Backspace"
              token2 <="00000000000000";
WHEN x"71" => token  <="11011000000000"; -- "Entf"
              token2 <="00000000000000";
WHEN x"76" => token  <="11011010000000"; -- "Esc"
              token2 <="00000000000000";
 
when x"72" => token  <= "11011011000000"; -- "left Arrow"
              token2 <="00000000000000";
when x"74" => token  <= "11011011010000"; -- "Right Arrow"
              token2 <="00000000000000";
 
 
WHEN OTHERS => token <= (OTHERS => '0');  
 
END CASE;

Abbildung 6: Flussdiagram Keyboard_Encoder

Zum Empfangen des Klopfcodes wird ebenfalls die bereits bestehenden Auswertungen des Morsecodecs verwendet. Somit erzeugt das Shift-Register aus den eingehenden Signalen die Token-Signale: dah_low, dit_high, dah_high und word_end. Diese werden, wie in Abbildung 7 dargestellt verarbeitet, um beide Klopfsignal nacheinander zur LCD-Entity weiterzugeben. Die LCD-Entity detektiert die eingehenden Klopfsignale und gibt nach dem Erhalt von Zeilen- und Spaltenwert den Datenbereich an das LCD weiter.

Abbildung 7: Flussdiagram symbol_generation


 4. Fazit und Ausblick

Das bereits bestehende Morseprogramm von Herr Nessit konnte erfolgreich erweitert werden. Hierbei ist es uns in der von uns erstellten Erweiterung des VHDL-Programms gelungen Lichtsignale vollständig einzubinden und einen Modus für eine alternative Klopfverschlüsselung zu integrieren. Schwierigkeiten, die nacheinander eingehenden Klopfsignale in einem getakteten Prozess zu bündeln und auszugeben konnten erfolgreich gelöst werden. Als Ausblick für diese Umsetzung des Klopfmodus ist eine weitere Verschlüsselungsstufe denkbar. So könnte die gegebene Klopftabelle je nach „Passwort“ veränderlich sein, um eine noch stärker codierte Übertragung zu ermöglichen.


 Downloads

Projektdateien:

Klopfcodeerweiterung_Laser_VHDL

Autoren: Lukas van Buer und Alexander Kramer

Gelsenkirchen 2022


Bildquellen:

Abbildung 2: https://www.amazon.de/Aihasd-KY-008-650nm-Sensor-Arduino/dp/B00W3GS5Q0 am 25.03.2022

Abbildung 3: https://www.vennfuessler.de/service/pfadfindertechnik/das-morsealphabet/hintergruende/ am 25.03.2022

Abbildung 5: https://de.wikipedia.org/wiki/Klopfcode#:~:text=Klopfcode%2DTabelle,(englisch%20to%20tap)%20%C3%BCbertragen. am 25.03.2022

_______________________________________________________________________________________________________________________________________________________________________


 

Related Articles

Free Joomla! templates by Engine Templates