Erweiterung eines Morsedecoders auf VHDL-Basis und Implementierung
auf einem FPGA-Entwicklungsboard unter Verwendung eines HC-SR04
-- --- .-. ... . -.. . -.-. --- -.. . .-. .-.-.- ..- .-.. - .-. .- ... --- -. .. -.-.
1. Einführung
Das Fach Entwicklung Digitaler Systeme (EDS) von Prof. Jorczyk an der Westfälische Hochschule in den Studiengängen Mikrosystemtechnik und Digitale Systeme erfordert die eigenständige Entwicklung einer Erweiterung für einen bestehenden Morsedecoder auf Basis des FPGA Entwicklungsboards DE2 von Terasic. Der von uns entwickelte Morsedecoder basiert grundlegend auf dem Projekt einer Abschlussarbeit von Herrn Nessitt. Dabei wurden einige Funktion ergänzt, aber auch die Encodierung mittels Tastatur entfernt. Der Grund für diese Entscheidung war die Fokussierung auf der Verbesserung bzw. Erweiterung der Decoder-Funktion.
Realisiert wurde dies mithilfe eines Ultraschallsender/-empfängers, inspiriert durch die Umsetzung der Übertragung von akustischem Morsecode in einem ähnlichen Projekt. Es wurde die Möglichkeit untersucht, Morsecode ohne kabelgebundene Verbindung von Sender und Entwicklungsboard zu ermöglichen, da die verwendete Ultraschallfrequenz außerhalb des sich für den Menschen hörbaren Bereich befindet. Somit kann die gesendete Nachricht nur von einem entsprechenden Empfänger empfangen und im Anschluss nur vom Decoder auf dem FPGA verarbeitet werden. Gleichzeitig sollte die Übertragung sowohl störunempfindlich sein als auch ohne die Einbindung umfangreicher Bibliotheken auskommen. Schlussendlich sollte die Umsetzung eine leichte Replikation für Andere ermöglichen bei gleichzeitig geringem finanziellem Aufwand.
1.1 Funktionslayout und Demovideo
2. Aufbau des HC-SR04 Ultraschall-Entfernungsmessmoduls
Das HC-SR04 Ultraschall Entfernungsmessmodul besteht aus einem Piezolautsprecher zum Senden und einem weiteren zum Empfangen von Ultraschallsignalen mit 40.000kHz Frequenz. Das Modul wurde über die letzten zehn Jahre mehrfachen Revisionen unterzogen, so dass die aktuell erhältliche Revision „2020“ von der ursprünglichen und den zwischenzeitlichen abweicht. Als Anhaltspunkt kann dennoch die Hardwareanalyse von PC Services UK zu der Revision von 2017 dienen (http://www.pcserviceselectronics.co.uk/arduino/Ultrasonic/electronics.php), die im Folgenden von uns verifiziert und in den abweichenden Punkten analysiert wurde.
Über einen RCWL9300 bezeichneten Mikrocontroller wird ein Piezolautsprecher zum Senden von Ultraschall-Impulsen angesteuert. Im Gegensatz zur ursprünglichen Version steuert der Mikrocontroller den Piezolautsprecher direkt an und nicht über einen weiteren Prozessor. Der Piezo wird mit 40.000kHz und einem 50% Dutycycle betrieben, Vss beträgt ca. 3,4V DC. Spätere Messungen werden zeigen, dass es keinen Unterschied im Empfangssignal gibt, wenn mit einem symmetrischen Signal gesendet werden würde, so wie es nach der Analyse von PC Services UK in der Ursprungsversion der Fall war.
Auf der Empfangsseite sitzt ein Piezolautsprecher, der dem Sendebauteil identisch zu sein scheint. Das Signal wird vom Piezo aufgenommen und in einen LM324 (Bezeichnung U1 auf der Platine) Vierfach-Operationsverstärker gespeist. Der erste OP (OP4) wird für eine Vorverstärkung um den Faktor 5 genutzt, bevor es in den zweiten OP (OP3) gespeist wird. Im Schaltplan zu den Revisionen bis 2017 ist der zweite OP (OP3) als Bandpassfilter mit einer Mittenfrequenz von 40kHz ausgelegt.
Durch Entfernen des Empfangs-Piezos und Direktansteuerung des LM324 mit Hilfe eines Signalgenerators konnte erkannt werden, dass der Bandpass in der Revision von 2020 nicht mehr vorhanden ist: bei einem Rechteck-Eingangssignal von 0-1V mit 50% Tastgrad ist keine Dämpfung der Amplitude bei halber und doppelter Mittenfrequenz von 40kHz zu beobachten. Eine FFT-Analyse des Signals mit Hilfe eines Wobble-Eingangssignals war auf Grund der minimalen Rasterauflösung in den mathematischen Funktionen des verwendeten Oszilloskops von 250kHz nicht sinnvoll durchführbar.
Abbildung 1: Vergleich der Ausgangssignale von OP3/LM324 bei 20kHz und 40kHz Input
Stattdessen lassen der Vergleich von Eingangs- und Ausgangssignal erkennen, dass diese OP-Stufe für eine erneute Signalverstärkung genutzt wird.Das Ausgangssignal des zweiten OP (OP3) wird in den dritten OP (OP2) gespeist, welcher eine weitere Verstärkung bewirkt. Im Gegensatz zum 2017er Design wird in der aktuellen Revision der vierte Operationsverstärker (OP1) im LM324 Baustein nicht benutzt. Zusammen mit dem fehlenden Bandpass erklärt dies auch die im Vergleich zur 2017er Revision geringe Anzahl an Widerständen und Kondensatoren.
Das Signal wird nach der Verarbeitung durch den dritten OP zur Auswertung in den mittleren Mikroprozessor (U2) gespeist und nicht in U3 wie in alten Revisionen. In der aktuellen Revision übernimmt der mit RCWL-9206 bezeichnete Baustein U3 lediglich die Kommunikation des Moduls mit dem dafür eigentlich vorgesehenem Arduino Mikrocontroller.
Abbildung 2: Unterseite HC-SR04 Modul Rev. 2020, Markierung der Pins von OP1
Abbildung 3: Oberseite des Moduls HC-SR04 nach Entfernen der Piezolautsprecher
Abbildung 4: Isolationsmaskierung digital "entfernt“ und Abbildung gespiegelt, um Pin-Deckung mit Oberseite darzustellen
Abbildung 5: Vergrößerung Pins OP1, keine Kontaktierung
Abbildung 6: Vergrößerung Pins-OP4 Unterseite. Keine Leiterbahnen oder Kontakte.
3. Signale des HC-SR04 Ultraschall Entfernungsmessmoduls
Die Ansteuerung des Piezo-Senders erfolgt auf dem HC-SR04 Modul durch einen Digitalausgang des Mikroprozessors. Hierbei wird das 0-5V Rechtecksignal mit einem Tastgrad von 50% und einer Frequenz von 40kHz gegenüber einem Widerstand an Vcc in seiner Amplitude auf ca. 3,4V gedämpft. Seitens des Mikrocontrollers werden im eigentlichen Verwendungszweck lediglich 8 Perioden des 40kHz Signals ausgegeben und die Laufzeit bis zum Eingang des Echos gemessen.
Für die Verwendung des Piezo-Senders in einer eigenen Sendeeinheit wird daher ein einfacher Mikrocontroller wie ein Arduino Nano verwendet werden. Dieser bietet so die Möglichkeit, die Parameter der Ansteuerung leicht über Änderungen im Quellcode umzusetzen. Zugleich erlaubt er künftige Erweiterungen der Funktionalität.
Abbildung 7: CH2 mit 40kHz Ausgangssignal des Mikroprozessors an den Sender-Piezo
Abbildung 8: 40kHz Rechteck-Signal, ausgegeben durch den Digitalausgang des Arduino Nano in der Morse-Sendereinheit.
Abbildung 9: 3D-Rendering der Sendergehäuse-Konstruktion.
Abbildung 10: Sendereinheit in 3D Druck. Piezosender oben, Hauptschalter seitlich, Morsetaster vorne.
Verwendetes Filament: BASF Ultrafuse rPET, hergestellt aus Recycling-PET.
Abbildung 11: Geöffnete Sendereinheit mit 9V-Blockbatterie, Arduino Nano, Taster und Hauptschalter.
// ARDUINO NANO V3 CODE FÜR SENDEEINHEIT const byte piezopin = 3; // Timer 2 "B" output: OC2B const long frequency = 40000L; // PWM Frequenz in Hz const byte buttonPin = 9; // PIN mit angeschlossenem Taster const byte led_builtin = 13; int buttonState = 0; void setup() { pinMode (piezopin, OUTPUT); // Code von Nick Gammon (http://www.gammon.com.au/forum/?id=11504&reply=6#reply6) TCCR2A = (1<<WGM20) | (1<<WGM21) | (1<<COM2B1); // fast PWM, OC2B löschen bei Vergleich TCCR2B = (1<<WGM22) | (1<<CS21); // fast PWM, Prescaler auf 8 OCR2A = ((F_CPU / 8) / frequency) - 1; // Relative Null setzen OCR2B = ((OCR2A + 1) / 2) - 1; // 50% Tastgrad pinMode(buttonPin, INPUT_PULLUP); pinMode(led_builtin, OUTPUT); } void loop() { buttonState = digitalRead(buttonPin); // Status buttonpin einlesen if (buttonState == LOW) { // wenn Taster gedrückt digitalWrite(LED_BUILTIN, HIGH); // LED an TCCR2A = (1<<WGM20) | (1<<WGM21) | (1<<COM2B1); TCCR2B = (1<<WGM22) | (1<<CS21); } else { digitalWrite(LED_BUILTIN, LOW); // LED aus TCCR2A = (1<<WGM20) | (1<<WGM21) | (0<<COM2B1); TCCR2B = (1<<WGM22) | (1<<CS21); } }
Sofern keines dieser Features gewünscht oder benötigt wird und die reinen Kosten die höchste Priorität besitzen, ließe sich die 40kHz PWM alternativ auch über diskrete Bauteile realisieren. Als Beispiel wäre hier der LM555 von Texas Instruments zu nennen, dessen Frequenz durch Rückkopplung spannungsunabhängig über die Wahl der Widerstandspaarung gewählt werden kann, während der Tastgrad durch die Wahl der Kondensatorpaarung getroffen wird ( Datenblatt lm555.pdf, Seite 10, „7.4.2 Astable Operation“).
Der Empfangs-Piezo reagiert sehr schnell auf eintreffende Signale. Nach 9 Perioden ist bereits die maximale Anregung erfolgt und die Ausgangsspannungsamplitude am Empfangs-Piezo maximal.
Abbildung 12: Aufschwingen des Empfangs-Piezos nach Empfang eines akkustischen 40kHz Signals, Spannungsmessung direkt am Empfangs-Piezo.
Um das Signal im Empfangsteil besser untersuchen zu können, wurde der Sender-Piezo ausgelötet und über einen Signalgenerator mit einem Dauersignal 40kHz 0-2V Rechteck mit 50% Tastgrad betrieben. Wie zuvor erwähnt, zeigte sich im Empfangssignal keine Signaländerung, wenn der Sender-Piezo mit -1V bis +1V statt mit 0-2V betrieben wurde. Das Empfangssignal wurde am Ausgang von OP2 abgegriffen, d.h. nach dreimaliger Verstärkung durch OP4, OP3 und OP2.
Die Operationsverstärker arbeiten mit einer jeweils über Widerstände von Vcc +5V abgeleiteten Vergleichsspannung, wodurch das Empfangssignal in seiner Amplitude zwischen ca. 1,9V und maximal GND schwankt. Das Platteau bei 1,9V stellt ein Clipping dar, welches erst bei nur äußerst schwachem Empfang sichtbar wird, wenn das Empfangssignal als Sinus sichtbar ist. Die Empfangssignalstärke ist maximiert, wenn das Minimum des Signals GND erreicht.
Abbildung 13: Schwaches Empfangssignal auf CH1. Der Sinus ist fast vollständig intakt, das Clipping gegenüber dem Maximalpegel-Signal weniger ausgeprägt.
Abbildung 14: Empfangssignal bei maximaler Empfangsstärke. Zu erkennen dadurch, dass das der Minimalwert bei GND liegt.
Wird der Sende-Piezo im Direktbetrieb am Funktionsgenerator mit Frequenzen zwischen 5kHz und 50kHz betrieben, so können auch andere Frequenzen als 40kHz ein Signal am Empfangs-Piezo bzw. am abgegriffenen Ausgang des 3. OP vom LM324 auslösen. Bei diesem so empfangenen Signalen handelt es sich jedoch weiterhin ausschließlich um 40kHz Signale. Da die signalauslösenden Sendefrequenzen ganzzahlige Bruchteile von 40kHz darstellen ist anzunehmen, dass es sich hierbei um Resonanzfrequenzen handelt, welche die 40kHz Resonanzmode des Empfangspiezos anregen können. Dadurch, dass lediglich 40kHz angeregt werden können oder der Piezolautsprecher selbst einen entsprechenden Bandpass besitzt, konnte ggf. auf den Bandpass verzichtet werden, wie es in der aktuellen Revision zu erkennen ist.
Obwohl es alternative Frequenzen gibt, mit denen der 40kHz Empfangspiezo angeregt werden kann, müssten diese vergleichbar präzise mit einem Dauerton abgegeben werden, um eine Störung des Empfangs zu verursachen. Die alleinige Resonanz des Empfangs-Piezos bei anderen Frequenzen u.a. im hörbaren Bereich spielt bei der Beeinflußung durch stochastische Signale wie Rauschen und Sprache keine Rolle. Der hierdurch erzeugte Signalpegel und die sehr kurzfristige, willkürliche Anregung des Piezos ist zu gering, um den Auswertungsalgorithmus zu stören.
4. Verarbeitung des Empfangssignals über einen Komparator
Zur Auswahl stand die Auswertung des Signals über den Audio-Codec des DE2 Boards als auch die Auswertung als rein periodisches Signal mit TTL Pegel über einen der digitalen Eingänge. Da die Charakteristik des Signals bekannt ist und dessen Abweichungen sich in nur einem kleinen Bereich bewegen, wurde beschlossen, das Signal durch eine geeignete Komparatorschaltung weiter zu verarbeiten und dessen Ausgangssignal nach Abgriff über einen digitalen Eingang des DE2 Boards weiter per Software in VHDL auszuwerten.
Für die Verarbeitung des Empfangssignals aus dem Abgriff des OP2 Ausgangs wurde ein LM311(D) von STMicroelectronics eingesetzt (LM211.pdf, STMicroelectronics IC Familie LM111/LM211/LM311). Hierbei handelt es sich um einen IC, welcher einen OP mit integrierter Komparatorschaltung darstellt. Im Gegensatz zu der Mehrzahl an OP benötigt der LM311 nicht zwingend eine symmetrische Betriebsspannung sondern kann auch mit 0-5V betrieben werden. In diesem Modus kann der Komparator zur Erzeugung einer TTL kompatiblen Ausgangsspannung mit einem HIGH-Pegel geringfügig unterhalb der Betriebsspannung von 5V genutzt werden. Zusätzlich ist der LM311 spezifiziert für Eingangssignale mit Frequenzen von bis zu 1MHz, da er geringe Sättigungskapazitäten besitzt. Daher eignet er sich gut für die Verarbeitung des dargestellten Empfangssignals.
Der LM311 ist als Open Collector ausgeführt und schaltet daher nicht zwischen Versorgungsspannung und GND sondern zwischen Offen und GND, wobei ein geringfügiger Spannungsabfall nach GND erzeugt wird. Für die Nutzung zur Erzeugung eines TTL-Signals muss daher ein Pull-Up Widerstand nach Vcc eingesetzt werden, der von uns als 1kOhm Widerstand ausgelegt wurde, wodurch der Stromfluss mit 5mA nur 10% des spezifizierten Maximalstroms beträgt.
Die Vergleichsspannung wird durch einen Spannungsteiler mit zwei Widerständen erzeugt. Die Spannungskennlinie des LM311 besitzt eine anfängliche Nichtlinearität zwischen 0 und 0,1V , weshalb die Vergleichsspannung mindestens um diesen Betrag von der Basisspannung des Eingangssignals (hier: ca. 1,9V) entfernt liegen sollte. Ein unnötig großer Abstand von der Basisspannung ist ebenfalls zu vermeiden, da hierdurch die High-Flanke kurz ausfallen würde, insbesondere bei geringer Empfangsstärke.
Nach Simulation verschiedener Spannungen über ein Labornetzteil wurde die Vergleichsspannung unter Berücksichtigung der genannten Anforderungen auf 1,51V festgelegt, welche durch einen Spannungsteiler von 3,3kOhm und 7,62kOhm (6,8kOhm+0,82kOhm) an Vcc 5V realisiert wurde.
Abbildung 15: Empfangssignal an OP2-Ausgang (CH2, blau) und Vergleichsspannung aus Spannungsteiler (CH1, gelb).
Abbildung 16: Empfangssignal an OP2-Ausgang (CH2, blau) und Komparator-Ausgangssignal (CH1, gelb).
Gegenüber Abbildung 13 wurde im finalen Design die Einspeisung von Invertiertem und Nicht-Invertiertem Eingangssignal in den Komparator getauscht, so dass dessen Ausgangssignal invertiert zu dem dort abgebildeten ist und dessen Pegel auf HIGH liegt, wenn sich die Spannung des Empfangssignals unterhalb der Vergleichsspannung befindet. Der sich so ergebende Tastgrad beträgt ca. 40% bei maximalem Empfangssignalpegel und fällt auf 0 bei Unterschreiten der Vergleichsspannung von 1,51V, in dessen Fall das Signal keine ausreichend stark ausgebildete Amplitude mehr besitzt. Das so gebildete Signal wird über den von uns gewählten GPIO1_35 Pin des JP2 Ports in das Entwicklungsboard eingespeist. Die Spannungsversorgung der HC-SR05 Empfangsplatine und des Komparators geschieht über die +5V und GND Pins des JP2 Ports.
Abbildung 17: Anschlussplan Komparator, HC-SR04 Board und Oszilloskop.
5. Verarbeitung des Komparator Ausgangssignals in VHDL
5.1 Übersicht der Signale
Abbildung 18: Schematische Darstellung der Integration der Ultraschall-Detektor Entity in die Originalprogrammstruktur von F. Nessit.
Grau hinterlegt sind diejenigen Komponenten, welche keine wesentlichen Funktionserweiterungen zu vorherigen Modifikationen anderer Kursteilnehmer erfahren haben, sondern lediglich erforderliche Anpassungen.
Zur Verarbeitung der Signale aus dem Komparator wurde eine Komponente mit der Bezeichnung us_detection (ultrasonic detection) programmiert und diese als Unterkomponente der vorhandenen Komponente press_duration verknüpft.
Die Komponente us_detection benötigt zur Funktion sowohl bereits implementierte als auch zusätzliche Signale, um die bereits vohandene Komponenten ergänzt wurden:
- die Signale sys_clk, reset und clk_wpm wurden lediglich aus der übergeordneten Komponente press_duration weitergeleitet
- die Signale clk_us_360k und clk_us_40k wurden in der Komponente clock_divider neu generiert und über die Top Level Entity (TLE) und die press_duration eingespeist
- das Signal us_switch dient zur Erkennung der Schalterposition von SWITCH[13]. Es wird aus der TLE über press_duration an die us_detection weitergeleitet, um die rechenintensive Prozesse nur bei aktivierter US-Erkennung laufen zu lassen. In der TLE wird er genutzt, um bei Aktivierung das US-Signal auf das Tastendruck-Signal aufzuspielen, so dass ein Tastendruck und ein US-Signal gleichwertig weiterverarbeitet werden.
- das Signal us_data dient des Datenempfangs aus dem Komperator über GPIO1_35/JP2 und wird wie schon us_switch aus der TLE über die press_duration weitergeleitet
- das Signal us_pulses wird in der us_detection generiert und an die übergeordnete press_duration übergeben
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.ALL; entity us_detection is Port (sys_clk : in std_ulogic; --Systemtakt 50MHz reset : in std_ulogic; --Reset-Signal clk_wpm : in std_ulogic; --WPM Takt übergabe us_pulses : out std_ulogic; --Übergabe kurzes Ultraschall Signal an press_duration us_data : in std_ulogic; --Dateneingangs-Pin GPIO1_35 (JP2) für Ultraschall-Erkennung, durchgereicht von Top Level Entity (TLE) über press_duration.vhd clk_us_360k : in std_ulogic; --Übergabe der Ultraschall-Abtast-Clock aus dem clock_divider über die TLE nach press_duration und darüber hier hin clk_us_40k : in std_ulogic; --Übergabe der Ultraschall-Akkumulations-Clock aus dem clock_divider über die TLE nach press_duration und darüber hier hin clk_400 : in std_ulogic; --Übergabe der 400Hz clock aus der clock_divider über die TLE us_switch : in std_ulogic); --Umschalten zwischen Button- oder Ultraschall-Eingabe, Switch[13] end us_detection; architecture behavioral of us_detection is signal us_signal_pulse : std_ulogic_vector (8 downto 0); --enthält kompletten 40kHz Scan-Frame mit 360kHz Abtastung, d.h. 9 Einzelwerten signal us_signal_period : std_ulogic; --gibt an, ob Duty Cycle des 40kHz Signals (entspricht hierbei der Signalstärke vor dem Komparator) hoch genug war, um Signal über die Dauer einer Peridode als stabil zu kennzeichnen. signal us_100_periods : std_ulogic_vector (99 downto 0); --akkumulierter Mittelwert der Signalauswertungen einzelner Perioden über (1/400Hz) = 100 Einzelperioden begin scansignalframe: process (reset, clk_us_360k, us_switch) --Der Prozess scansignalframe dient der Erfassung von Pegeln im Signal us_data (GPIO1_35 Port, liegt am Ausgang des Komparators) im Abstand von 1/360kHz begin if reset='0' or us_switch='0' then --Reset gedrückt? Dann alle Variablen zurück setzen. us_signal_pulse <= (others=>'0'); elsif clk_us_360k 'event and clk_us_360k = '1' and clk_us_360k'last_value = '0' then --us_360k clock Event führt zur Ausführung der nächsten Anweisung us_signal_pulse <= us_signal_pulse (7 downto 0) & us_data; --aktueller Pegel am digitalen Eingangspin GPIO1_35 (us_data zugewiesen) wird in das Schieberegister us_signal_pulse gespeist end if; end process; full_40k_period: process (sys_clk, reset, clk_us_40k, us_switch) --Der Prozess ermittelt den Duty Cycle des Signals 40kHz Signals, welches über us_data im Prozess scansignalframe ausgelesen wird variable count_ones : unsigned(4 downto 0) := "00000"; begin if reset='0' or us_switch='0' then --Reset gedrückt oder US-Modus abgeschaltet? Dann alle Variablen zurück setzen. us_signal_period <= '0'; us_100_periods <= (others=>'0'); elsif us_switch='1' then if clk_us_40k 'event and clk_us_40k = '1' and clk_us_40k'last_value = '0' then --us_40k clock Event führt zur Ausführung der nächsten Anweisung count_ones := (others=>'0'); --Zähler count_ones wird zurück gesetzt for i in 0 to 8 loop --Zähler count_ones wird genutzt, um alle mit 360kHz abgetasteten HIGH Level des us_data Signals innerhalb des Vektors us_signal_pulse zu finde, in dem die Signalpegeldurch scansignalframe abgelegt wurden if(us_signal_pulse(i) = '1') then count_ones := count_ones + 1; end if; end loop; if count_ones >= 1 and count_ones <= 5 then --werden mehr als 5 High Level detektiert, d.h. ist in einer 40kHz Periode (=25µs) für mindestens 16,667µs (=6*(1/360kHz)) ein HIGH-Level detektiert und ist kein Dauer-Pegel HIGH detektiert worden (wäre = 9)... us_signal_period <= '1'; --...wird die Signalperiode als OK qualifiziert... us_100_periods <= us_100_periods (98 downto 0) & us_signal_period; --...und diese korrekt übertragene 40kHz Signalperiode in den Speichervektor us_100_periods eingespeist else us_signal_period <= '0'; us_100_periods <= us_100_periods (98 downto 0) & us_signal_period; end if; end if; end if; end process; stable_40k_signal: process (reset, clk_400, us_switch) --der Prozess trifft die Aussage darüber, ob das 40kHz über einen Zeitraum von 1/400Hz ausreichend stabil ist, um an die Verarbeitung weitergeleitet zu werden variable count_ones : unsigned(6 downto 0) := (others=>'0'); begin if reset='0' or us_switch='0' then --Reset gedrückt? Dann alle Variablen zurück setzen. us_pulses <= '0'; elsif clk_400 'event and clk_400 = '1' and clk_400'last_value = '0' then count_ones := (others=>'0'); for i in 0 to 99 loop --Die Schleife zählt alle erfolgreichen Signalperioden innerhalb von 1/400Hz (bei 40kHz Signal entspricht das einer Summe von 100 Perioden) if(us_100_periods(i) = '1') then count_ones := count_ones + 1; end if; end loop; if count_ones >= 50 then --bei mehr als 50% erfolgreich übertragenen Signalperioden innerhalb von 1/400Hz (=2,5ms) gilt das Signal als stabil und setzt das Ausgangssignal us_pluses auf HIGH us_pulses <= '1'; else us_pulses <= '0'; end if; end if; end process; end behavioral;
5.2 Der Prozess "scansignalframe"
Der Prozess schreibt den aktuellen Pegel des digitalen Eingangspin GPIO1_35 (us_data zugewiesen) in das Schieberegister us_signal_pulse. Bei dem anliegenden Signal handelt es sich um das Ausgangssignal des LM311 Komparators. Dies geschieht bei jeder steigenden Flanke des Clock Signals clk_us_360k, welches mit 360kHz arbeitet und aus der Komponente clock_divider über die Top Level Entity und der press_duration in die us_detection geleitet wird. Hierdurch entstehen innerhalb der Periode eines 40kHz Signals 9 Abtastwerte.
Der Prozess wird nur ausgeführt, wenn weder der Reset Schalter betätigt ist noch der Wechselschalter zur Auswahl von Tasten- oder Ultraschalleingabe auf die Tasteneingabe gestellt ist.
5.3 Der Prozess "full_40k_period"
Der Prozess ermittelt den Tastgrad des Signals 40kHz Signals, welches über us_data im Prozess scansignalframe im Vektor us_signal_pluse abgebildet wird. Hierzu wird in einer Schleife die Anzahl aller HIGH-Pegel einer Signalperiode gebildet, wie sie durch die 360kHz Abtastung im Prozess scansignalframe abgelegt wurden. Der Prozess bzw. die Schleife wird durch die steigende Flanke der Clock clk_us_40k ausgelöst, so dass der Vektor 9 Abtastungen mit 360kHz enthält.
Da das Ausgangssignal des LM311 Komparators gemäß eigener Auslegung einen maximalen Tastgrad von 40% bei maximalem Empfangspegel besitzen kann und der Tastgrad mit abnehmender Empfangsqualität sinkt, wird für mindestens einem HIGH Pegel das Kriterium eines ausreichend starken Signals definiert. Ein HIGH Pegel bei 9 Abtastwerten entsprechen einem Tastgrad von 11% und dementsprechend rund ein Viertel des maximalen Tastgrads von 40%. Ein solches Signal wird als „1“ in das Shiftregister us_100_periods gespeichert und kennzeichnet den Empfang einer korrekten 40kHz Periode.
Für 5 oder mehr HIGH Pegel liegt ein Fehler in der Hardware vor, da dieser Zustand unter normalen Bedingungen von der Schaltung auch unter Berücksichtigung von Jitter-Effekten durch ungünstige Timer-Konstellationen nicht erzeugt werden kann. Daher führen 7 oder mehr HIGH Pegel zu keinem Setzen einer erfolgreich detektieren 40kHz Signalperiode.
Der Prozess wird nur ausgeführt, wenn weder der Reset Schalter betätigt ist noch der Wechselschalter zur Auswahl von Tasten- oder Ultraschalleingabe auf die Tasteneingabe gestellt ist.
5.4 Der Prozess „stable_40k_signal“
Mit einer Frequenz von 400Hz aus der Clock clk_400, welche ebenfalls für die Ansteuerung des LCD-Displays verwendet wird, durchläuft der Prozess stable_40k_signal eine Schleife. Innerhalb dieser wird die Summe aller einwandfrei detektierten Ultraschall-Signalperioden gebildet, welche als „1“ durch den Prozess „full_40k_period“ in den Vektor us_100_periods geschrieben wurden.
Sind mehr als 50% aller 100 Signale innerhalb der 400Hz Periode der clk_400 als „1“ gespeichert worden, gilt das Ultraschallsignal als stabil. Diese Aussage wird als „1“ dem Signal „us_pulses“ übergeben, welches als Ausgangssignal in die press_duration Komponente übergeben wird.
5.5 Verwendung des Signals „us_pulses“ in der Top Level Entity
Das Signal "us_pulses" wird in der Top Level Entity in das Signal "btn" gespeist, sofern der Wahlschalter SW13 zum Signal "us_switch" aktiviert ist. Ein detektiertes Ultraschall-Signal wird dadurch den Druck eines Tasters gleichgesetzt. Alle weitere Verarbeitunsschritte bleiben hierdurch automatisch erhalten. Das Detektieren eines stabilen Ultraschallsignals ersetzt hierdurch die Funktion eines Tastendrucks. Alle weiteren Funktionen des Programms bleiben so erhalten.
btn <= '0' when morse_btn = '0' or ext_btn = '0' or (us_pulses_out = '1' and us_switch = '1') else '1';
5.6 Verwendung des Signals „us_switch“ zusätzlich in der "us_detection"
Das Signal "us_switch" des Ultraschall-Detektions-Wahlschalters SW13 wird ausser in der TLE zusätzlich in der us_detection ausgewertet. Hier wird bei deaktivierter US-Detektion verhindert, dass die rechenintensiven Schleifenprozesse ausgeführt werden, um Ressourcen zu sparen. Würde das "us_switch" Signal lediglich in der TLE bei der Zuweisung des Tastendruck-Signals "btn" ausgewertet werden, so erfolge die Ausführung der Schleifen in der "us_detection" weiterhin, obwohl das resulierende Signal "us_pulses" in der TLE überhaupt nicht weiter verarbeitet wird.
5.7 Testbench zur Überprüfung des Algorithmus
Im Verlauf der Entwicklung sollte sichergestellt werden, dass eine produktionsbedingte Varianz der Piezosendefrequenz die korrekte Erkennung des Ultraschallsignals nicht beeinträchtigt. Hierzu wurde in ModelSim eine entsprechende Testbench erzeugt, in der das Eingangssignal um +/- 10% variiert. Nachfolgend sind die Simulationsergebnisse dargestellt. Zu erkennen ist das trotz höherer oder niedriger Eingangsfrequenz, die korrekte Detektion eines validen Signals möglich ist (us_pulses = 1).
Abbildung 19: Simulationsergebnis mit Modelsim bei einer Eingangsfrequenz von 40kHz
Abbildung 20: Simulationsergebnis mit Modelsim bei einer Eingangsfrequenz von 44kHz
Abbildung 21: Simulationsergebnis mit Modelsim bei einer Eingangsfrequenz von 38kHz
--Testbench zur Untersuchung der Signale der Entity "us_detection". LIBRARY ieee ; LIBRARY std ; USE ieee.NUMERIC_STD.all ; USE ieee.std_logic_1164.all ; USE ieee.std_logic_textio.all ; USE ieee.std_logic_unsigned.all ; USE std.textio.all ; ENTITY usdetec_tb IS END ; ARCHITECTURE usdetec_tb_arch OF usdetec_tb IS constant cf : integer := 50e6; -- 50 MHz constant cp : time := 1000 ms / cf; -- Periode des Clocks constant cf_lcd : integer := 400; -- 400 Hz constant cp_lcd : time := 1000 ms / cf_lcd; -- Periode des Clocks constant cf_us : integer := 40e3; -- 40 kHz constant cp_us : time := 1000 ms / cf_us; -- Periode des Clocks constant cf_us2 : integer := 360e3; -- 360 kHz constant cp_us2 : time := 1000 ms / cf_us2; -- Periode des Clocks constant cf_wp : integer := 15; -- 15 Hz constant cp_wp : time := 1000 ms / cf_wp; -- Periode des Clocks --Zeitkonstanten für die Simulation constant t_360: time := 8333 ns; --3 / 360kHz constant t2_360 : time := 16666 ns; --6 / 360kHz constant t_396: time := 9166 ns; --3 / 396kHz (110%) constant t2_396 : time := 18333 ns; --6 / 396kHz (110%) constant t_324: time := 7500 ns; --3 / 324kHz (90%) constant t2_324 : time := 15000 ns; --6 / 324kHz (90%) SIGNAL clk_us_360k : STD_ULOGIC :='1' ; SIGNAL sys_clk : STD_ULOGIC :='1' ; SIGNAL us_switch : STD_ULOGIC :='1' ; SIGNAL clk_400 : STD_ULOGIC :='1' ; SIGNAL us_data : STD_ULOGIC :='0' ; SIGNAL clk_wpm : STD_ULOGIC :='1' ; SIGNAL us_pulses : STD_ULOGIC :='0' ; SIGNAL clk_us_40k : STD_ULOGIC :='1' ; SIGNAL reset : STD_ULOGIC :='1' ; COMPONENT us_detection PORT ( clk_us_360k : in STD_ULOGIC ; sys_clk : in STD_ULOGIC ; us_switch : in STD_ULOGIC ; clk_400 : in STD_ULOGIC ; us_data : in STD_ULOGIC ; clk_wpm : in STD_ULOGIC ; us_pulses : out STD_ULOGIC ; clk_us_40k : in STD_ULOGIC ; reset : in STD_ULOGIC ); END COMPONENT ; BEGIN DUT : us_detection PORT MAP (clk_us_360k => clk_us_360k, sys_clk => sys_clk, us_switch => us_switch, clk_400 => clk_400, us_data => us_data, clk_wpm => clk_wpm, us_pulses => us_pulses, clk_us_40k => clk_us_40k, reset => reset) ; -- Takterzeugung sys_clk <= not sys_clk after cp / 2; clk_400 <= not clk_400 after cp_lcd / 2; clk_wpm <= not clk_wpm after cp_wp / 2; clk_us_40k <= not clk_us_40k after cp_us / 2; clk_us_360k <= not clk_us_360k after cp_us2 / 2; process is begin -- 4 Perioden for i in 0 to 399 loop us_data <= '1'; wait for t_360; us_data <= '0'; wait for t2_360; end loop; wait for 1 ms; for i in 0 to 399 loop us_data <= '1'; wait for t_396; us_data <= '0'; wait for t2_396; end loop; wait for 1 ms; for i in 0 to 399 loop us_data <= '1'; wait for t_324; us_data <= '0'; wait for t2_324; end loop; wait; end process;
END;
6. VGA-Schnittstelle zur visuellen Ausgabe
Die Codebasis stammt hauptsächlich aus dem Projekt "vga-kbd-terminal" von Johnnyjax, welches bei Github unter folgenden Link verfügbar ist (https://github.com/Johnnyjax/vga-kbd-terminal). Einige nicht benötigte Entitys wurden unsererseits entfernt und der übrige Code entsprechend angepasst. Zur Ausgabe von Zeichen erhält die VGA-Entity ihre Eingabe von der LCD-Entity, dabei werden die ASCII-Daten des LCD übernommen und entsprechend weitergeleitet.
--Angepasst von: https://github.com/Johnnyjax/vga-kbd-terminal library ieee; use ieee.std_logic_1164.all; entity vga_txt_main is port (sys_clk, output_mode : in std_logic; reset : in std_ulogic; rx_done_tick_in : in std_ulogic; vga_dout : in std_ulogic_vector (7 downto 0); vga_hs, vga_vs : out std_logic; vga_r, vga_b, vga_g : out std_logic_vector(7 downto 0); vga_clk : out std_logic; vga_blank_n : out std_logic; vga_sync_n : out std_logic); end vga_txt_main; architecture arch of vga_txt_main is signal pixel_x, pixel_y : std_ulogic_vector(9 downto 0); signal video_on, pixel_tick : std_ulogic; signal rgb_reg, rgb_next : std_ulogic_vector(2 downto 0); signal scan_data, w_data : std_ulogic_vector(7 downto 0); signal kb_not_empty, kb_buf_empty : std_ulogic; signal key_code, ascii_code : std_ulogic_vector(7 downto 0); signal enter_tick : std_ulogic; signal up_tick, down_tick, left_tick, right_tick : std_ulogic; signal bck_spc_tick : std_ulogic; signal clk_25MHz :std_ulogic; begin vga_blank_n <= '1'; vga_sync_n <= '0'; vga_clk <= clk_25mhz; vga_sync: entity work.vga_sync port map (clk => sys_clk, reset => reset, vsync => vga_vs, hsync => vga_hs, video_on => video_on, p_tick => pixel_tick, pixel_x => pixel_x, pixel_y => pixel_y); text_gen: entity work.vga_text port map (clk => sys_clk, key_code => key_code(6 downto 0), video_on => video_on, pixel_x => pixel_x, pixel_y => pixel_y, we => kb_not_empty, enter_tick => enter_tick, text_rgb => rgb_next, up_tick => up_tick, down_tick => down_tick, left_tick => left_tick, right_tick => right_tick, bck_spc_tick => bck_spc_tick, output_mode => output_mode); kb_code: entity work.kb_code(arch) port map(clk => sys_clk, reset => reset, rd_key_code => kb_not_empty, key_code => key_code, enter_tick => enter_tick, kb_buf_empty => kb_buf_empty, up_tick => up_tick, down_tick => down_tick, left_tick => left_tick, right_tick => right_tick, bck_spc_tick => bck_spc_tick, rx_done_tick_in => rx_done_tick_in, vga_dout => vga_dout); process(sys_clk) begin if(sys_clk'event and sys_clk = '1') then clk_25mhz <= not clk_25mhz; if(pixel_tick = '1') then rgb_reg <= rgb_next; end if; end if; end process; kb_not_empty <= not kb_buf_empty; vga_r <= (others => rgb_reg(2)); vga_g <= (others => rgb_reg(1)); vga_b <= (others => rgb_reg(0)); end arch;
6.1 Funktionserweiterung und Modi
Neben der Darstellung von Buchstaben auf dem Bildschirm, besteht die möglich die dargestellten Zeichen als Morsecode anzuzeigen. Dazu wurde die Entity „font_rom“ dupliziert, umbenannt und bearbeitet. Die erstellte Entity „morsecode_rom“ enthält die Darstellung der Großbuchstaben A bis Z als untereinander geschriebene Morsezeichen (Einen Pixelpunkt für kurz / mehrere Pixelpunkte ergeben einen Strich für lang). Zum Wechseln zwischen den Modi wird der Schiebeschalter [1] verwendet, deklariert und an die Entitys übergeben wird dieser als „output_mode“. Der Code der Entity „vga_text“ wurde entsprechend ergänzt.
--Angepasst von: https://github.com/Johnnyjax/vga-kbd-terminal library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity vga_text is port( clk, output_mode : in std_ulogic; key_code : in std_ulogic_vector(6 downto 0); video_on : in std_ulogic; pixel_x, pixel_y : in std_ulogic_vector(9 downto 0); enter_tick : in std_ulogic; up_tick, down_tick , left_tick, right_tick : in std_ulogic; bck_spc_tick : in std_ulogic; we : in std_ulogic; text_rgb : out std_ulogic_vector(2 downto 0) ); end vga_text; architecture arch of vga_text is signal char_addr : std_ulogic_vector(6 downto 0); signal rom_addr : std_ulogic_vector(10 downto 0); signal row_addr : std_ulogic_vector(3 downto 0); signal bit_addr : unsigned(2 downto 0); signal font_word : std_ulogic_vector(7 downto 0); signal font_word2 : std_ulogic_vector(7 downto 0); signal font_bit : std_ulogic; signal addr_r, addr_w : std_ulogic_vector(11 downto 0); signal din, dout : std_ulogic_vector(6 downto 0); constant MAX_X : integer := 80; constant MAX_Y : integer := 30; signal we_wth_bck : std_ulogic; signal cur_x_reg, cur_x_next : unsigned(6 downto 0); signal cur_y_reg, cur_y_next : unsigned(4 downto 0); signal move_x_tick, move_y_tick : std_ulogic; signal cursor_on : std_ulogic; signal pix_x1_reg, pix_y1_reg : unsigned(9 downto 0); signal pix_x2_reg, pix_y2_reg : unsigned(9 downto 0); signal bck_spc : std_ulogic; signal font_rgb, font_rev_rgb : std_ulogic_vector(2 downto 0); begin font_unit : entity work.morsecode_rom port map(clk => clk,addr => rom_addr, data => font_word); font_unit2: entity work.font_rom port map(clk => clk,addr => rom_addr, data2 => font_word2); video_ram : entity work.altera_dual_port_ram_sync generic map(ADDR_WIDTH => 12, DATA_WIDTH => 7) port map(clk => clk, we => we_wth_bck, addr_a => addr_w, addr_b => addr_r, din_a => din, dout_a => open, dout_b => dout); process(clk) begin if(clk'event and clk = '1') then cur_x_reg <= cur_x_next; cur_y_reg <= cur_y_next; pix_x1_reg <= unsigned(pixel_x); pix_x2_reg <= pix_x1_reg; pix_y1_reg <= unsigned(pixel_y); pix_y2_reg <= pix_y1_reg; bck_spc <= bck_spc_tick; end if; end process; we_wth_bck <= we or bck_spc; addr_w <= std_ulogic_vector(cur_y_reg & cur_x_reg); din <= (others => '0') when bck_spc = '1' else key_code; addr_r <= pixel_y(8 downto 4) & pixel_x(9 downto 3); char_addr <= dout; row_addr <= pixel_y(3 downto 0); rom_addr <= char_addr & row_addr; bit_addr <= pix_x2_reg(2 downto 0); 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')else cur_x_reg + 1 when we = '1' or right_tick = '1' else "1001111" when ((left_tick = '1' or bck_spc_tick = '1') and cur_x_reg = 0) 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') else "11101" 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; font_rgb <= "000" when font_bit = '1' else "111"; font_rev_rgb <= "000" when font_bit = '1' else "010"; cursor_on <= '1' when pix_y2_reg(8 downto 4) = cur_y_reg and pix_y2_reg(3 downto 0) >= "1110" and pix_x2_reg(9 downto 3) = cur_x_reg else '0'; process(video_on, cursor_on, font_rgb, font_rev_rgb, output_mode, font_bit, bit_addr, font_word, font_word2) begin if video_on = '0' then text_rgb <= "000"; else if cursor_on = '1' then text_rgb <= font_rev_rgb; else text_rgb <= font_rgb; if output_mode = '1' then -- Wechsel zwischen Ausgabe als Morsezeichen oder Buchstaben font_bit <= font_word2(to_integer(not bit_addr)); else font_bit <= font_word(to_integer(not bit_addr)); end if; end if; end if; end process; end arch;
--Angepasst von: https://github.com/Johnnyjax/vga-kbd-terminal -- Listing 13.1 -- ROM with synchonous read (inferring Block RAM) -- character ROM -- - 8-by-16 (8-by-2^4) font -- - 128 (2^7) characters -- - ROM size: 512-by-8 (2^11-by-8) bits -- 16K bits: 1 BRAM library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity morsecode_rom is port( clk: in std_ulogic; addr: in std_ulogic_vector(10 downto 0); data: out std_ulogic_vector(7 downto 0) ); end morsecode_rom; architecture arch of morsecode_rom is constant ADDR_WIDTH: integer:=11; constant DATA_WIDTH: integer:=8; signal data_reg: std_ulogic_vector(DATA_WIDTH-1 downto 0); type rom_type is array (0 to 2**ADDR_WIDTH-1) of std_ulogic_vector(DATA_WIDTH-1 downto 0); -- ROM definition constant ROM: rom_type:=( -- 2^11-by-8 ... -- code x41 (A) "00011000", -- 0 "00011000", -- 1 "00000000", -- 2 "00000000", -- 3 "01111110", -- 4 "00000000", -- 5 "00000000", -- 6 "00000000", -- 7 "00000000", -- 8 "00000000", -- 9 "00000000", -- a "00000000", -- b "00000000", -- c "00000000", -- d "00000000", -- e "00000000", -- f -- code x42 (B) "01111110", -- 0 "00000000", -- 1 "00000000", -- 2 "00011000", -- 3 "00011000", -- 4 "00000000", -- 5 "00000000", -- 6 "00011000", -- 7 "00011000", -- 8 "00000000", -- 9 "00000000", -- a "00011000", -- b "00011000", -- c "00000000", -- d "00000000", -- e "00000000", -- f -- code x43 (C) "01111110", -- 0 "00000000", -- 1 "00000000", -- 2 "00011000", -- 3 "00011000", -- 4 "00000000", -- 5 "00000000", -- 6 "01111110", -- 7 "00000000", -- 8 "00000000", -- 9 "00011000", -- a "00011000", -- b "00000000", -- c "00000000", -- d "00000000", -- e "00000000", -- f -- code x44 (D) "01111110", -- 0 "00000000", -- 1 "00000000", -- 2 "00011000", -- 3 "00011000", -- 4 "00000000", -- 5 "00000000", -- 6 "00011000", -- 7 "00011000", -- 8 "00000000", -- 9 "00000000", -- a "00000000", -- b "00000000", -- c "00000000", -- d "00000000", -- e "00000000", -- f ...
6.1.1 Zeilen einfügen und Bildschirm leeren
Zum Einfügen einer neuen Zeile auf dem Bildschirm wird der Schiebeschalter [14] verwendet. Dieser ist deklariert und an die Entitys übergeben als „vga_next“. Bei einem Signalwechsel von 0 (low) auf 1 (high) wird ein Zeilensprung auf dem Bildschirm ausgeführt, sowie die Zeichenfolge „MORSECODE ->“ eingefügt. Der Cursor befindet sich dann immer an der Position hinter dem Pfeil, in der jeweils eingefügten Zeile. Die dekodierten Zeichen vom LCD werden fortlaufend an diese Stelle geschrieben. Um den Bildschirm unabhängig von anderen Prozessen zu leeren, wird der Push-Button [1] „vga_clear“ benutzt. Dies funktioniert ähnlich wie die Backspace Taste auf einer Tastatur, nur das entsprechend alle Zeichen gelöscht und der Cursor zurück an den Anfang springt.
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity display is port (clk_400 : in std_ulogic; --400Hz Taktsignal sys_clk : in std_ulogic; --Systemtakt 50Mhz reset : in std_ulogic; --Reset-Signal data_input : in std_ulogic_vector (13 downto 0); --Token-Signal des Morsedecoders enable_char : in std_ulogic; --Pausen-Signal der Entity morsedecoder ob ein Token-Signal abgeschlossen ist vga_next : in std_ulogic; --Schalter zum Ändern der Zeile bei Videoausgabe vga_clear : in std_ulogic; --Taste zum vollständigen Löschen des gesamten Bildschirm Push-button[1]/KEY[1] del_switch : in std_ulogic; --Schalter zum Aktivieren der Löschfunktion mittels Morsetaste data_out : out std_ulogic_vector (7 downto 0); --Datensignale für das LCD (Zeichen) lcd_control : out std_ulogic_vector (3 DOWNTO 0); --Steuersignale für das LCD rx_done_tick: out std_ulogic; --Zeichenausgabe-Signal (VGA) vga_out : out std_ulogic_vector (7 downto 0)); --Datensignale für den Bildschirm (Hex) end display; architecture structure of display is signal LCD_DATA : std_ulogic_vector (7 downto 0); --Datensignal für das Display signal LCD_ON, LCD_RS, LCD_EN, LCD_RW : std_ulogic; --Steuersignale für das Display signal sig_old : std_ulogic; --Signale zum Prüfen eines Signalwechsels signal sig_rise : std_ulogic; --Signale für die VGA-Steuerung signal rx_done_tick_sig :std_ulogic; signal vga_out_sig : std_ulogic_vector (7 downto 0); signal lcd_en_fall : std_ulogic; signal lcd_en_old : std_ulogic; signal en_old : std_ulogic; signal en_fall : std_ulogic; signal valid : std_ulogic := '0'; signal state: natural range 0 to 25; signal state2: natural range 0 to 6; signal state3: natural range 0 to 3; signal cnt : natural; signal del : std_ulogic := '0'; begin vga_control: process (sys_clk, reset, LCD_DATA, LCD_EN, LCD_RS, vga_next, lcd_en_old, lcd_en_fall, valid, state, state2, state3, en_old, en_fall) 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'; state <= 0; state2 <= 0; state3 <= 0; cnt <= 0; elsif sys_clk'event and sys_clk ='1' then lcd_en_old <= LCD_EN; en_old <= vga_next; cnt <= cnt + 1; if en_old ='1' and vga_next = '0' then en_fall <='1'; rx_done_tick_sig <= '0'; else en_fall <='0'; end if; if lcd_en_old ='1' and LCD_EN = '0' then -- Die eingegebenen Zeichen können nur übertragen werden, wenn LCD_EN = '0' lcd_en_fall <= '1'; else lcd_en_fall <= '0'; end if; if (en_fall ='1' or state > 0 ) and valid = '1' then case state is when 0=> vga_out_sig <= x"5A"; --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"4D"; --"M" rx_done_tick_sig <= '1'; state <= state +1; when 3=> rx_done_tick_sig <= '0'; state <= state +1; when 4=> vga_out_sig <= x"4F"; --"O" rx_done_tick_sig <= '1'; state <= state +1; when 5=> rx_done_tick_sig <= '0'; state <= state +1; when 6=> vga_out_sig <= x"52"; --"R" rx_done_tick_sig <= '1'; state <= state +1; when 7=> rx_done_tick_sig <= '0'; state <= state +1; when 8=> vga_out_sig <= x"53"; --"S" rx_done_tick_sig <= '1'; state <= state +1; when 9=> rx_done_tick_sig <= '0'; state <= state +1; when 10=> vga_out_sig <= x"45"; --"E" rx_done_tick_sig <= '1'; state <= state +1; when 11=> rx_done_tick_sig <= '0'; state <= state +1; when 12=> vga_out_sig <= x"43"; --"C" rx_done_tick_sig <= '1'; state <= state +1; when 13=> rx_done_tick_sig <= '0'; state <= state +1; when 14=> vga_out_sig <= x"4F"; --"O" rx_done_tick_sig <= '1'; state <= state +1; when 15=> rx_done_tick_sig <= '0'; state <= state +1; when 16=> vga_out_sig <= x"44"; --"D" rx_done_tick_sig <= '1'; state <= state +1; when 17=> rx_done_tick_sig <= '0'; state <= state +1; when 18=> vga_out_sig <= x"45"; --"E" rx_done_tick_sig <= '1'; state <= state +1; when 19=> rx_done_tick_sig <= '0'; state <= state +1; when 20=> vga_out_sig <= x"20"; --"Leer" rx_done_tick_sig <= '1'; state <= state +1; when 21=> rx_done_tick_sig <= '0'; state <= state +1; when 22=> vga_out_sig <= x"1A"; --">" rx_done_tick_sig <= '1'; state <= state +1; when 23=> rx_done_tick_sig <= '0'; state <= state +1; when 24 => en_fall <='0'; state <= 25; when others => state <= 0; end case; elsif (cnt >= (10*10**6) and del_switch = '1') and (sig_rise = '1' or state2 > 0) then del <= not del; cnt <= 0; case state2 is when 0=> vga_out_sig <= x"6B"; --Cursor springt nach links rx_done_tick_sig <= '1'; state2 <= state2 +1; when 1=> rx_done_tick_sig <= '0'; state2 <= state2 +1; when 2=> vga_out_sig <= x"20"; --Leerzeichen "Löschen" rx_done_tick_sig <= '1'; state2 <= state2 +1; when 3=> rx_done_tick_sig <= '0'; state2 <= state2 +1; when 4=> vga_out_sig <= x"6B"; --Cursor springt nach links rx_done_tick_sig <= '1'; state2 <= state2 +1; when 5=> rx_done_tick_sig <= '0'; state2 <= state2 +1; when others => state2 <= 0; end case; elsif vga_clear = '0' then case state3 is when 0=> vga_out_sig <= x"66"; --Backspace "Clear All" rx_done_tick_sig <= '1'; state3 <= state3 +1; when 1=> rx_done_tick_sig <= '0'; state3 <= state3 +1; when others => state3 <= 0; end case; elsif valid = '0' then rx_done_tick_sig <='0'; elsif lcd_en_fall = '1' then vga_out_sig <= LCD_DATA; rx_done_tick_sig <='1'; lcd_en_fall <= '0'; else rx_done_tick_sig <='0'; end if; rx_done_tick <= rx_done_tick_sig; vga_out <= vga_out_sig; end if; end process; -------------------------------------------------------------- --Ansteuerung des Displays mit Initialisierung und Ausgabe der Zeichnen-- -------------------------------------------------------------- LCD_init: process (clk_400, reset, data_input, enable_char, valid) variable ini : natural range 0 to 11 := 0; variable next_command : natural range 0 to 11 := 0; variable char_cnt : natural range 0 to 34 := 0; variable char : integer range 0 tO 12 := 0; begin --LCD reinitialisieren if reset = '0' then LCD_ON <= '1'; --LCD Power ON/OFF LCD_EN <= '1'; --LCD Aktivierung LCD_RW <= '0'; --LCD Schreiben(0)/Lesen(1) LCD_RS <= '0'; --LCD Kommando(0)/Daten(1) LCD_DATA <= x"38"; --Display aus / Beide Zeilen leer (=reset) valid <= '0'; ini := 0; next_command := 0; char_cnt := 0; elsif clk_400'event and clk_400 = '1' then case ini is --LCD Initialisierung when 0 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"38"; --Display aus / Beide Zeilen leer (=reset) valid <= '0'; next_command := next_command+1; ini := 8; when 1 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"38"; --Display aus / Beide Zeilen leer (=reset) next_command := next_command+1; ini := 8; when 2 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"38"; --Display aus / Beide Zeilen leer (=reset) next_command := next_command+1; ini := 8; when 3 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"38"; --Display aus / Beide Zeilen leer (=reset) 4x notwendig zum Initialisieren next_command := next_command+1; ini := 8; when 4 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"0D"; --Display ein / Cursor blinkt an Position next_command := next_command+1; ini := 8; when 5 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"01"; --Display leeren next_command := next_command+1; ini := 8; when 6 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"0D"; --Display ein / Cursor blinkt an Position next_command := next_command+1; ini := 8; when 7 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"06"; --Addresse wird automatisch inkrementiert und der Cursor nach rechts verschoben valid <= '0'; next_command := 11; ini := 8; -- Initialisierung ist ab hier beendet when 8 => --Dieser Zyklus dient dazu das Enable-Signal in LCD_EN auch auf '0' zu setzen. --Ansonsten können die Daten nicht an den Bildschirm übertragen werden!!! LCD_ON <= '1'; LCD_EN <= '0'; ini := next_command; when 9 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '1'; LCD_DATA <= x"20"; --" " next_command:= 10; ini := 8; when 10 => LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"10"; --scrollt cursor nach links next_command := 11; ini := 8; when 11 => if char_cnt = 16 then LCD_RS <= '0'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_ON <= '1'; LCD_DATA <= x"C0"; --Setzt den Cursor an den Anfang der zweiten Zeile des Displays char_cnt := char_cnt + 1; ini := 8; elsif char_cnt = 34 then LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '0'; LCD_DATA <= x"01"; --Display leeren char_cnt := 0; ini := 8; else valid <= '1'; sig_rise <= '0'; --Das Signal "sig_rise" wird standardmäßig auf ein LOW gesetzt, da dass enable_char-Signal nicht immer eine steigende Flanke hat sig_old <= enable_char; --Das aktuelle enable_char-Signal wird "sig_old" zugewiesen if sig_old = '0' AND enable_char = '1' then --Überprüfung ob sich Signale unterscheiden, ist dies der Fall (positive Flanke) sig_rise <= '1'; --Die Zuweisung erfolgt erst im nächsten Taktzyklus, deshalb ist die vorherige Zuweisung zu prüfen end if; ---------------------------------------- --Ansteuerung des LCD im Decoder-Modus-- ---------------------------------------- If sig_rise = '1' then LCD_ON <= '1'; LCD_EN <= '1'; LCD_RW <= '0'; LCD_RS <= '1'; case data_input is when "00000000000000" => LCD_EN <= '0'; --Buchstaben when "00000000000111" => LCD_DATA <=x"41"; -- A when "00000011010101" => LCD_DATA <=x"42"; -- B when "00000011011101" => LCD_DATA <=x"43"; -- C when "00000000110101" => LCD_DATA <=x"44"; -- D when "00000000000001" => LCD_DATA <=x"45"; -- E when "00000001011101" => LCD_DATA <=x"46"; -- F when "00000000111101" => LCD_DATA <=x"47"; -- G when "00000001010101" => LCD_DATA <=x"48"; -- H when "00000000000101" => LCD_DATA <=x"49"; -- I when "00000001111111" => LCD_DATA <=x"4A"; -- J when "00000000110111" => LCD_DATA <=x"4B"; -- K when "00000001110101" => LCD_DATA <=x"4C"; -- L when "00000000001111" => LCD_DATA <=x"4D"; -- M when "00000000001101" => LCD_DATA <=x"4E"; -- N when "00000000111111" => LCD_DATA <=x"4F"; -- O when "00000001111101" => LCD_DATA <=x"50"; -- P when "00000011110111" => LCD_DATA <=x"51"; -- Q when "00000000011101" => LCD_DATA <=x"52"; -- R when "00000000010101" => LCD_DATA <=x"53"; -- S when "00000000000011" => LCD_DATA <=x"54"; -- T when "00000000010111" => LCD_DATA <=x"55"; -- U when "00000001010111" => LCD_DATA <=x"56"; -- V when "00000000011111" => LCD_DATA <=x"57"; -- W when "00000011010111" => LCD_DATA <=x"58"; -- X when "00000011011111" => LCD_DATA <=x"59"; -- Y when "00000011110101" => LCD_DATA <=x"5A"; -- Z --Zahlen when "00001111111111" => LCD_DATA <=x"30"; -- 0 when "00000111111111" => LCD_DATA <=x"31"; -- 1 when "00000101111111" => LCD_DATA <=x"32"; -- 2 when "00000101011111" => LCD_DATA <=x"33"; -- 3 when "00000101010111" => LCD_DATA <=x"34"; -- 4 when "00000101010101" => LCD_DATA <=x"35"; -- 5 when "00001101010101" => LCD_DATA <=x"36"; -- 6 when "00001111010101" => LCD_DATA <=x"37"; -- 7 when "00001111110101" => LCD_DATA <=x"38"; -- 8 when "00001111111101" => LCD_DATA <=x"39"; -- 9 when others => LCD_DATA <= x"3F"; end case; char_cnt := char_cnt + 1; ini := 8; if del_switch = '1' and char_cnt = 0 then next_command := 5; elsif del_switch = '1' and char_cnt > 0 then LCD_RS <= '0'; LCD_DATA <= x"10"; valid <= '0'; next_command := 9; char_cnt := char_cnt - 2; end if; end if; end if; end case; --Übertragung der Steuersignale und Datenbits an das LCD lcd_control(3) <= LCD_ON; lcd_control(0) <= LCD_EN; lcd_control(1) <= LCD_RW; lcd_control(2) <= LCD_RS; data_out <= LCD_DATA; end if; end process; end structure;
6.1.2 Zeichen löschen auf dem Bildschirm und LCD
Um falsche oder versehentliche Eingaben zur korrigieren wurde eine Löschfunktion implementiert. Zum Aktivieren wird ebenfalls ein Schiebeschalter [12] verwendet, dieser ist als „del_switch“ deklariert. Ist der Modus aktiv, also der Schalter (1), kann durch Drücken des Morsetasters oder eines externen Tasters das letzte Zeichen einer Zeichenfolge gelöscht werden. Die betrifft sowohl den LCD als auch die VGA-Ausgabe auf dem Bildschirm. Der Cursor springt dabei nach links und ersetzt das Zeichen an der Cursorposition durch ein Leerzeichen, anschließend bleibt er an seiner Position. Es können weitere Zeichen gelöscht werden oder nach dem Deaktivieren kann dann erneut eine Eingabe erfolgen.
Siehe Code der Display-Entity unter 6.1.1
Downloads
CAD STL-Files für 3D-Druck der Teile der Sendereinheit
CAD STL-Files für 3D-Druck der Teile des Empfängers
Anschlussplan Komparator, HC-SR04 Board und Oszilloskop (PDF)
Autoren: Christian Stromberg & Julian Dieckhöfer
Gelsenkirchen 2022
_______________________________________________________________________________________________________________________________________________________________