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.
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:
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.
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.
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.
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
Morse De- und Encoder 2.0 Projektdateien
Autoren: Tim Michalek und Nina Häselhoff
Gelsenkirchen 2021
_______________________________________________________________________________________________________________________________________________________________
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.
Abbildung 1 Ein- und Ausgabe
Nummer | Funktionalität |
1 | Netzteil |
2 | Audio-Output |
3 | 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
4. Clock Divider
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.
Abbildung 6 ClockDivider 50 MHz auf 12MHz
Die Abbildung 6 zeigt das Ergebnis des Testbenches:
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.
Zum Testen der Funktionalität des Tastendrucks wurde ein Testbench erstellt.
Abbildung 9 zeigt das erhaltene Ergebnis.
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.
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 ISPORT (
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' THENIF 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-ResetWHEN 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.
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.
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.
Abbildung 15Testbench AudioCodec Teil 1
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:
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)
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.
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)
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.
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.
"""
-- 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 messageboxglobal 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 Falsedef 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
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
_______________________________________________________________________________________________________________________________________________________________________