|
Antwort |
Registriert seit: 30. Jan 2007 Ort: Frankfurt/M. 34 Beiträge |
#11
Ah, Ok, Ich muss als erstes nochmal ein grosses Dankeschön aussprechen, ich bin ganz baff was für Unterstützung man hier im Forum bekommt.
So, ich will nochmal zusammenfassen, was ich verstanden habe: 1.Ich sende mit dem µC am besten eine binäre 13 als Ende der Nachricht, angenommen ich will den wert 255 übertragen, sende ich : | 1111 1111 | 0000 1101 | 2.Der PC empfängt ständig , und speichert das Empfangene in einem Puffer. 3. Ich sage mit EventChar , das bei dem empfangenen Wert 13 ein Ereignis ausgelöst wird, Das Ereignis kann eine Prozedur mit Namen OnRxFlag , oder OnRxChar sein 4. Ich bekomme von OnRxChar die Zahl der Bytes im Puffer, in dem Fall 2 ( 255 und 13) 5. So jetzt kann ich in der Prozedur OnRxChar den String mit Readstr(stringWert, 2) auslesen, die 2 für die 2 letzten Bytes im Puffer, von der "StringWert" Variablen ziehe ich das LowByte ab und erhalte mein Byte mit dem Wert 255. Wenn nach dem Aufruf des Interrupts OnRxChar noch weitere Bytes in den Puffer gekommen sind, lese ich halt den Puffer in eine Variable ein , und check dort halt anhand der 13 wo mein erwarteter Wert ist. Ist das so? Heisst im Prinzip, wenn ich mit dem µC nach jedem Messergebnis, das er sendet, einmal 13 anhänge, und per InputCount praktisch sage wie gross das gesendete Messergebnis ist (im µC definiert), kann ich aus der Byteflut im Puffer den Wert den ich haben will rausfiltern. Ist das so? Jetzt noch 2 Sachen: Wie gross ist der Puffer, bzw. Wann läuft er über und wird wieder resettet? Wie kann ich den Puffer resetten? Ok, (die Abende werden immer später... ) Gruss Moritz |
Zitat |
Registriert seit: 13. Dez 2003 Ort: Berlin 1.756 Beiträge |
#12
Zitat von Manado:
ich bin ganz baff was für Unterstützung man hier im Forum bekommt.
Zitat von Manado:
1.Ich sende mit dem µC am besten eine binäre 13 als Ende der Nachricht, angenommen ich will den wert 255 übertragen, sende ich : | 1111 1111 | 0000 1101 |
Zitat von Manado:
2.Der PC empfängt ständig , und speichert das Empfangene in einem Puffer.
Zitat von Manado:
3. Ich sage mit EventChar , das bei dem empfangenen Wert 13 ein Ereignis ausgelöst wird, Das Ereignis kann eine Prozedur mit Namen OnRxFlag , oder OnRxChar sein
OnRxFlag hingegen wird dann und nur dann ausgelöst, wenn unter den neuen Daten (die im Puffer landen) das EventChar bei war. Kommt z.B. das Datum $1,$1,$1 gefolgt von $1,$2 und dein EventChar ist $2, dann würde hier OnRxChar zwei mal aufgerufen werden und Du hättest erst ein Datum der Länge 3, dann ein Datum der Länge 2 im Puffer. Das OnRxFlag würde hingegen nur einmal ausgelöst werden, nämlich dann wenn das zweite Datum empfangen wurde, im Puffer wären in diesem Fall dann schon 5 Byte.
Zitat von Manado:
Heisst im Prinzip, wenn ich mit dem µC nach jedem Messergebnis, das er sendet, einmal 13 anhänge, und per InputCount praktisch sage wie gross das gesendete Messergebnis ist (im µC definiert), kann ich aus der Byteflut im Puffer den Wert den ich haben will rausfiltern.
Ist das so? Was Du jetzt mit InputCount sagen möchtest ist mir ehrlich gesagt unklar. InputCount gibt an wieviele Daten schon im Puffer liegen (die Funktion InputCount). Da es sich um eine Funktion handelt, kannst Du der nichts sagen, die gibt Dir nur einen Wert zurück. Dem Puffer ist es völlig egal wieviele Bytes Du erwartest, Du kannst nur aus ihm lesen was schon da ist. Liest Du mehr Bytes aus, würdest Du Speicher lesen, dessen Wert undefiniert/zufällig ist. Deshalb wird die Komponente das schon für Dich abfangen. Das eigentliche rausfiltern geschieht dann dadurch, dass Du ebend einfach alles was ankommt einer eigenen Variable speicherst und hier nach den Trennzeichen suchst. Findest Du eins, so kannst Du an dieser Stelle trennen und erhälst das gewünschte Datum.
Zitat von Manado:
Jetzt noch 2 Sachen:
Wie gross ist der Puffer, bzw. Wann läuft er über und wird wieder resettet? Die Größe des Puffers lässt sich sicherlich auslesen (müsste auch in die Hilfe schauen wo und wie, also verweise ich nochmal auf eben diese), sie sollte Dir aber egal sein. Wichtig ist einfach, dass die Größe unterschiedlich sein kann/ist. Der eigentliche Puffer hängt schon mit der eingesetzten HW zusammen. Im Embedded Bereich wird man Platz und kosten sparen und eher sehr sehr kleine Puffer einsetzen (jeder Speicher benötigt auch Energie). Im PC kann das schon anders aussehen (z.B. kann man die Daten im HW-Puffer cachen und in einen größeren Speicher übernehmen). Hier wäre dann eine Art virtueller Puffer (durch den Treiber) denkbar (und eine Festplatte dürfte schon einiges aufnehmen). Man sollte sich einfach nie darauf verlassen, dass genug Platz zur Verfügung steht. Am Besten ist es deshalb, wenn Du immer auf das OnRxChar Event reagierst. Liest Du den vollen Inhalt des Puffers aus, so brauchst Du Dir keine Sorgen zumachen, dass der Puffer voll wird. Da ein Datum aber in mehreren Schritten übertragen werden kann, musst Du dann immer die neu empfangenen Bytes anhängen und selbst prüfen, wann ein vollständiges Datum in deiner Variablen steht. Das wäre das, was halt das OnRxFlag für Dich übernimmt (die Prüfung ob das spezielle Zeichen empfangen wurde). Mit der OnRxChar-Variante hast Du dann den Vorteil, dass Du wirklich sicher bist, was die Füllung des Puffers angeht (wird mit dem Lesen auch geleert). Der Nachteil ist die Einbusse an Komfort (die sich aber sehr sehr stark in Grenzen hält). Speicherst Du die empfangenen Daten in einem String, so liegt der Unterschied in einer Zeile (kannst die Pos Funktion verwenden). Mehr als zu prüfen ob das entsprechende Zeichen im String ist wird Dir das OnRxFlag-Event nicht abnehmen. Das trennen der Daten ist weiterhin deine Aufgabe. Um es also nochmal klar zu sagen, Du verwendest einfach immer OnRxChar, hängst die neuen Daten an eine Instanz-Variable an. Die eigentlichen Daten erhälst Du dann, indem Du sie aus dieser Variablen extrahierst. Wie Du das machst, kannst Du selbst entscheiden. Du kannst Trennzeichen verwenden, Du kannst die Länge der Daten festlegen, Du kannst beides mischen, Du kannst ganz anders vorgehen. Haben Daten bei Dir immer eine feste Länge, so wirst Du kein Trennzeichen benötigen. Allerdings hast Du natürlich ein Problem, wenn Du hier irgendwann die Länge der Daten änderst. Natürlich kannst Du das leicht in der Software anpassen (wenn Du z.B. die Länge als Konstante an einer Stelle definiert hast). Aber da bleibt dann natürlich die Abwärtskompatiblität auf der Strecke. Ist natürlich für dein jetziges Problem eher unwichtig, aber allgemein solltest Du das halt immer im Hinterkopf behalten. Das gleiche gilt dann natürlich sehr generell für dein Protokoll. Die RS-232-Schnittstelle übernimmt nur den Transport für Dich. Ob alle Daten korrekt übertragen wurden wird ebenso wenig geprüft wie bestätigt (oder eben negiert). Das alles solltest Du halt berücksichtigen. Was davon wirklich nötig ist hängt natürlich sehr stark vom Einsatzgebiet ab. Ich denke nicht wirklich, dass Du hier mit dem Overhead einer Checksumme (oder ähnlichem) arbeiten solltest, aber mag ja Gebiete und Situationen geben, wo das mal nötig wäre. I.d.R. hat man aber wirklich kurze Strecken mit wirklich geringer Störranfälligkeit. Wie gesagt, letztlich kannst Du alles deinen Ansprüchen/Wünschen anpassen, die Schnittstelle dient nur zur Übertragung der Daten. Gruß Der Unwissende |
Zitat |
Registriert seit: 30. Jan 2007 Ort: Frankfurt/M. 34 Beiträge |
#13
Ok, Vieln Dank erstmal.
Ich werde dein "Tutorial" jetzt mal gründlichst durcharbeiten. Vielleicht dauert das ein, zwei Tage (Schule!). Dann (oder früher) schreib ich wie's geklappt hat. Gruss, Moritz |
Zitat |
Registriert seit: 30. Jan 2007 Ort: Frankfurt/M. 34 Beiträge |
#14
also,
ich hab noch ein doofes Problem mit dem Aufruf des OnRXChar (oder Flag) EREIGNISSES. --> Was ist ein "Ereignis". Wie wird das, was im Aufruffall passieren sollte, deklariert? (Ich bin Hobbyelektroniker, und eher der Bedarfs-informatiker, deswegen muss man mir meine Fragen verzeihen ...µC - Assembler ist sooo schön einfach .) Also ich versuch das mal zu sortieren: In der Hauptunit ist eine Klasse aufgeführt, mit dem Namen TMainForm. Da sind alle Buttons, usw. drin. Dann ist in diese Hauptunit eine andere unit eingebunden, Cport. In Cport gibts tausende Klassen, alle mit verschiedensten Methoden, Parametern, Funktionen und Proceduren. So jetzt will ich in meiner Hauptunit eine Procedur haben, in der erstmal nix grossartiges drinsteht, die aber genau dann aufgerufen wird, wenn OnRXChar "passiert". Das Eventchar hab ich so gesetzt: comport.EventChar := '!'; (oben ist dazu: ComPort: TComPort; deklariert worden) {komisch ist dabei schon, dass ich TComPort als klasse nirgendwo gefunden habe, müsste doch eigentlich in Cport aufgeführt sein? naja vllt. hab ichs nur übersehen...?} So ,EventChar ist ja eine Eigenschaft (property) von irgendeiner comport-klasse.. Sie steht unter der Klassse "TCustomComPort = class(TComponent)" property EventChar: Char read FEventChar write SetEventChar default #0; ;------------------------------------------------------ Soweit so gut, jetzt wollte ich das Ereignis in meiner normalen Hauptunit deklarieren (OnRXChar) ich habs mal so versucht: procedure TCustomComPort.OnRxChar(Sender: TObject); begin Showmessage('wenn das zu lesen ist, hat's geklappt'); end; Aber das ging nicht. in dsdt.info steht was von Zeigern,...Bahnhof (Sender: TObject); <---Das mit dem Sender zu definieren hab ich auch versucht mit procedure of objekt, ich brauch halt eigentlich nur das Wort, was man anstelle von TCustomComPort einsetzt, damit das Ereignis aus der cport-unit bei eintreten die Prozedur ausführt. Das ist eigentlich mein Problem soweit. Ich probier das ständig weiter aus, aber mit dieser Objektorientierung kann man schon sehr schnell stark verwirrt werden . Wenn ich neue Erkenntnisse habe, werde ich sie posten !!!! gute nacht....und vielen Dank, das macht mir echt Spass hier(!),.. grüsse von Moritz |
Zitat |
Registriert seit: 13. Dez 2003 Ort: Berlin 1.756 Beiträge |
#15
Vorwort:
Ok, bin gerade fertig geworden und glaube es ist doch etwas umfangreicher. Deswegen vorab die Warnung, lass Dich nicht davon beirren, dass ich etwas viel geschrieben habe. An sich ist ein guter Teil mehr der Vollständigkeit halber erwähnt, als dass er wirklich direkt mit Deinem Problem zu tun hat (wirst schon merken wann was zutrifft). Versuche einfach nicht zusehr an Details hängen zu bleiben, dann sollte alles gut klappen. Bei Problemen frag einfach nochmal gezielt nach. Ende Vorwort
Zitat von Manado:
ich hab noch ein doofes Problem mit dem Aufruf des OnRXChar (oder Flag) EREIGNISSES.
--> Was ist ein "Ereignis". Wie wird das, was im Aufruffall passieren sollte, deklariert? (Ich bin Hobbyelektroniker, und eher der Bedarfs-informatiker, deswegen muss man mir meine Fragen verzeihen ...µC - Assembler ist sooo schön einfach .) Aber da Du fragst und ausgerechnet ich antworte, musst Du halt mit etwas mehr Text rechnen! Jedenfalls zum Problem der Ereignisse und die Frage was das ist. Ereignisse in Delphi entsprechen erstmal dem, was man gemeinhin als Ereignis versteht, es passiert irgendwas. Soweit so klar. Jetzt gibt es zwei Möglichkeiten zu merken, dass etwas passiert ist:
Beides findest Du, z.B. auch bei µC. Gerade bei diesen legt man viel Wert darauf, dass man nicht pollt. Polling impliziert natürlich, dass man ständig etwas tut, nicht sehr energiesparend. Viel schöner ist es, wenn man alles (insbesondere auch die CPU) schlafen legt und nur einen minimalen Teil laufen lässt. Dieser hat einfach die Möglichkeit einen Interrupt auszulösen, der dann die CPU wieder aufweckt, die das Ereignis (den Interrupt) behandelt. Bei einer CPU wird das über eine Interrupt Service Routine (ISR) gemacht. Zu jedem Interrupt kann hier ein Routine registriert werden, die direkt angesprungen wird, wenn ein Interrupt eintritt (kennst Du vielleicht schon?). An sich ist das auch schon die Grundlage dessen was hinter einem Ereignis in Delphi steckt. Du bekommst ein Signal (im Beispiel die Unterbrechung) und gehst in die Behandlung (die ISR). In Delphi wäre im Prinzip das gleiche denkbar. Auch die normale x86-CPU verwendet schließlich Interrupts. Das Problem ist aber, dass jedes Interrupt die CPU auch echt unterbricht. Dann gibt es auch sehr viele Programme, mit sehr vielen Ereignissen, da müsste man schon sehr sehr viele verschiedene Interrupts anbieten und einen Mechanismus, der die eindeutig zuordnet. Schließlich möchtest Du nicht das jmd. deine Behandlung überschreibt und Du über dein Ereignis nie informiert wirst. Viel schöner (und leichter) ist es, wenn Du Dich gar nicht soweit runter begeben musst, sondern das ganze in der Software löst. Die Idee bleibt aber die gleiche, Du verwendest ein spezielles (Software-)Signal, dass das Eintreten eines Ereignisses anzeigt. Deine Behandlung findet dann ebenfalls in der SW statt, hier kannst Du ja leicht eine Tabelle erstellen, die jedem solchen Signal eine Routine zuordnet. Wie eine Komponente feststellt ob ein Ereignis eintritt kann nicht pauschal gesagt werden, hängt natürlich stark von der Komponente ab. Für einen einfachen Fall könnten wir ja annehmen, dass Du eine Komponente zum Laden von AZB -Dateien baust. In einer Methode Load wird das Format ausgelesen. Sagen wir jetzt, dass das Auslesen asynchron in einem eigenen Prozess stattfindet. Die Prozedur Load kehrt sofort zurück, bevor die ganze Datei geladen ist. Der Prozess füllt in Ruhe eine Datenstruktur und löst sobald er fertig ist einfach ein Ereignis aus (Fertig). Ok, ist nicht unbedingt ein sinnvolles Beispiel, aber ich hoffe Du siehst dass hier ein Ereignis ausgelöst werden sollte (sonst weiß der Benutzer nie wann das Laden fertig ist). Was Du in dem nebenläufigen Thread machen würdest ist einfach das normale Laden der Datei. Bist Du damit fertig weißt Du das und musst das jetzt auch nach außen weiter reichen. Hier kommt das Signal ins Spiel. Für die Signalisierung hast Du aber gleich wieder mehrere Möglichkeiten. Fangen wir mit der Standardmöglichkeit in Delphi an: Funktionszeiger. Ja, Du magst Assembler aber keine Zeiger? Interessant Die Idee eines Zeigers ist sicherlich klar, Du speicherst darin einfach eine Adresse. Der Nutzen einer Adresse ist auch sehr einfach zu erklären. Ein Zeiger ist immer ein CPU-Wort (im Moment 32 Bit) breit (was eine ideale Größe für ein Datum darstellt!). Hast Du eine große Datenstruktur (z.B. ein Record der Größe 128 Byte), dann würde diese bei Übergabe Call-By-Value auch kopiert werden. Das gilt (um es kompliziert zu machen) aber nur für statische Arrays (Arrays fester Länge) und Records. Klassen und dyn. Arrays werden eh anders übergeben (nur der Vollständigkeit halber erwähnt). Statt 128 Byte zu kopieren kann man auch einfach die Adresse des Records (4 Byte) übergeben. Da die Adresse auf das Datum im Speicher "zeigt" und Du weißt was für einen Typen Du an dieser Adresse erwartest hast Du so Zugriff auf die selben Daten. Veränderst Du jetzt auch diese Daten, so wird der Unterschied auch nochmal deutlich. Beim Aufruf Call-By-Value wird eine echte Kopie übergeben. Änderungen finden nur auf der Kopie statt, das Original merkt nichts davon. Übergibst Du die Adresse, so arbeitest Du direkt auf dem Speicher des Originals, Änderungen werden also sofort übernommen. So, genug über allgemeine Zeiger, Sinn und Nutzen kannst Du sicherlich an verschiedenen Stellen (zum Beispiel hier in der DP ) nachlesen. Was also wird ein Funktionszeiger sein? Richtig, es handelt sich um die Adresse einer Funktion. An sich werden wir im folgenden eher Methodenzeiger betrachten. Zwischen beiden besteht ein wichtiger Unterschied, auch wenn sie sich sehr ähneln! Von einer Methode spricht man immer nur im Zusammenhang mit einer Klasse. Alle Prozeduren/Funktionen, die zu einer Klasse gehören werden als Methoden bezeichnet.
Delphi-Quellcode:
Ein Funktionszeiger kann nicht auf eine Methode zeigen und ein Methodenzeiger kann nur auf Methoden zeigen! Das alles (jede Variable, Methode, Funktion, ...) eine eigene Adresse hat ist sicherlich klar. Hinter jedem deiner normalen Funktionsaufrufe steckt auch nichts anderes, als dass man die Adresse der Funktion nimmt und den Code an dieser Stelle ausführt. Das ist Dir aber normalerweise völlig egal (zurecht).
type
TFoo = class(TObject) public procedure doFoo(); end; .... procedure doFoo(); // <-- normale Funktion, hat nichts mit TFoo zu tun! begin //... end; procedure TFoo.doFoo(); // <-- Methode der Klasse TFoo! begin //... end; Jetzt fehlt nur noch die Zusammenführung von Methodenzeigern mit Ereignissen (die Du vielleicht schon siehst/erahnst). Es ist auch hier wieder sehr einfach, Du gibst der Komponente einfach die Adresse einer Deiner Methoden, die immer dann aufgerufen werden soll, wenn das entsprechende Ereignis auftritt. Diese Adresse speichert die Komponente als Variable. Der Typ der Variable ist eben ein Methodenzeiger. Tritt das Ereignis ein, so kann die Komponente prüfen ob die Variable gesetzt wurde (Adresse <> nil). Ist dies der Fall, wird einfach der Code an dieser Adresse ausgeführt. Man spricht hier von einem Call-Back (sollte klar sein warum). Das wichtige ist, dass Du hier eine beliebige Rückrufadresse übergeben kannst, Du also selbst eine beliebige Behandlung erstellen kannst. Das gilt mit einer wichtigen Einschränkung, der Zeiger ist immer typisiert, die Signatur der Methode (Parameter und Rückgabetyp) muss also mit der deklarierten übereinstimmen. Das ganze klingt jetzt etwas kompliziert, ist es aber eigentlich gar nicht. Als erstes überlegst Du Dir, was für Ereignisse Du hast, die eintreten können. Hier schaust Du Dir dann an, was für Informationen Du weiterreichen möchtest. Nimm hier ruhig Ereignisse, die es schon gibt. Beim einem Maus-Click reicht es Dir zu wissen, was angeklickt wurde und welche Taste das Ereignis auslöste. Bei einer Mausbewegung sieht das schon anders aus, hier kann jede Taste gedrückt sein (oder nicht), die Position ist wichtig, ... Für alle Informationen, die jmd. interessieren könnten legst Du einfach einen Parameter fest und erstellst Methodenzeiger für jede Art von Benachrichtigung. So wirst Du die selben Informationen für das Drücken oder Loslassen eines Buttons übergeben, hier sind also nicht zwei Typen nötig! Ich mache an dieser Stelle der Einfachheit mit der Datei AZB weiter, hier legen wir einfach zwei mögliche Ereignistypen fest. Der eine informiert über den Fortschritt, der andere über das Fertigwerden/Beginnen des Laden. Es wird immer die Datei (Typ TAzb) übergeben, die das Ereignis auslöste. Beim Fortschritt kommt zusätzlich ein Integer Wert hinzu, der den prozentualen Fortschritt angibt. In Delphi sieht das dann so aus: [delphi] type TAzbEvent = procedure(const Sender: TAzb) of Object; TAzbProgressEvent = procedure(const Sender: TAzb; const Progress: Integer) of Object; [/quote] An sich ist das wie gesagt ganz einfach. Du hast auf der Linken-Seite des Gleichheitszeichen wie immer den Namen des neuen Datentyp. Rechts kommt zunächst ein prozedure (oder eben function). Das zeigt an, dass es sich hier um einen Funktions/Methodenzeiger handelt. Diesem Schlüsselwort folgen direkt die Argumente (oder nichts), bei einer Funktion kommt wie gewohnt dahinter noch ein : Rückgabetyp. Würdest Du es dabei belassen, hast Du einen Funktionszeiger. Das of Object hingegen zeigt an, dass es sich um den Zeiger auf eine Methode handelt. Die beiden Datentypen können jetzt wie jeder andere Datentyp auch behandelt werden. Du kannst ganz normale Variablen von diesem Typen anlegen. [pre] type TAzb = class(TObject) .... public OnBegin: TAzbEvent; OnFinish: TAzbEvent; .... end; [/pre] Wird jetzt eine Datei geladen, kannst Du ganz einfach prüfen ob OnBegin <> nil ist und ggf. die Methode dahinter aufrufen [pre] procedure TAzb.start(const fileName: String); begin // Start signalisieren if assigned(self.OnBegin) then begin self.OnBegin(self); end; // .... end; [/pre] Das assigned ist nichts anderes als <> nil. Der Aufruf der Methode findet so statt, wie bei jeder anderen Methode auch (geht auch ohne qualifizierendes self.). Als Parameter möchte die Methode das auslösende TAzb-Objekt bekommen. Nun ja, da es sich um ein Callback handelt ist ja das Objekt selbst auch der Auslöser dieser Benachrichtigung. Also übergibt es sich selbst. Beim Fortschritt würde es sich selbst und den eigenen Fortschritt beim Laden übergeben. Das wirkt vielleicht etwas ungewohnt, aber Du musst nur im Hinterkopf behalten, dass OnBegin auf eine Methode zeigt, die eben nicht im gleichen TAzb-Objekt liegen muss. Jetzt bleibt zuletzt noch die Frage, wie man jetzt dieser Variablen einen Wert zuweißt. Aber auch das ist wiederum einfach. Du erstellst einfach eine Klasse, die eine Methode mit der vorgegebenen Signatur enthält. Diese kannst Du dann übergeben: [pre] type TAzbEventHandlerClass = class(TObject) private azb: TAbz; protected procedure OnAzbBegin(const sender: TAzb); ... end; // z.B. im Constructor .... begin azb := TAzb.Create; // hier jetzt die eigentliche Zuweisung azb.OnBegin := self.OnAzbBegin; ... end; procedure OnAzbBegin(const sender: TAzb); begin // eigentliche Behandlung // Wird immer aufgerufen, wenn azb's OnBegin aufgerufen wird // Aber erst nachdem azb.OnBegin := self.OnAzBegin aufgerufen wurde! end; [/pre] Wie Du hier siehst ist der Name der Prozedur und die Sichtbarkeit (usw.) völlig egal. Wichtig ist nur, dass die die gleichen Parameter besitzt. Wie Du auch siehst muss man nicht auf jedes Ereignis reagieren. Die Abfrage assigned(X) wird schon prüfen, ob eine Behandlung registriert wurde oder nicht (ohne hättest Du sonst ein Problem). Ok, ich hoffe so ganz grob ist Dir die Idee jetzt klar. Der Auslöser einer Nachricht legt einen Mehtodenzeiger fest. Über diese Art von Methode wird das Eintreten eines Ereignisses signalisiert. Informationen an den "Informierten" können über die Parameter dieser Methode übergeben werden. Der Auslöser besitzt für jedes Ereignis eine Variable, die die Adresse einer Methode speichern kann. Tritt ein Ereignis ein (über das Informiert werden soll), schaut der Auslöser nach, ob die Adresse <> nil ist und benachrichtigt ggf. die Methode an der gespeicherten Adresse. Dem Auslöser ist nur die Adresse bekannt, mehr interessiert den nie! Auf der anderen Seite gibt es jmd. der sich benachrichtigen lassen möchte. Dieser jmd. legt einfach eine Methode an, deren Signatur der des Ereignisses entspricht, über das man sich informieren lassen möchte. Diese Methode wird dann einfach der Eigenschaft/Variable des Auslösers zugewiesen, die für die Benachrichtigung zuständig ist. Das ist dann alles! Ja, wie gesagt, es gibt noch andere Wege. Dieser hier ist der Standard-Delphi-Weg. Auf die anderen werde ich deswegen nicht ganz so detailliert eingehen (außerdem hast Du ja auch noch was vor die Woche! gut, ich auch!). Das was jetzt alles vermeintlich etwas kompliziert wirkt geht natürlich auch leichter. Du hast doch die TComPort Komponente installiert. D.h. Du findest die irgendwo in deiner Palette? Nimm Dir dort so ein Exemplar raus und platzier die auf dem Formular (wie ein Button). Dann gehst Du in den ObjectInspector. Der hat zwei Tabs, Eigenschaften und Ereignisse. Hier wählst Du Ereignisse aus und findest u.A. OnRxChar. Daneben ist ein leeres weißes Feld, da klickst Du doppelt rein und Delphi erstellt für Dich automatisch eine Behandlungmethode. Die hat dann schon die richtige Signatur und ist auch sofort beim TComPort als Call-Back eingetragen. Ja, wie gesagt, es gibt noch andere Mechanismen um Ereignisse zu signalisieren. Der nächste Weg, der auch in Delphi eingesetzt wird ist etwas Windows-spezifischer. Es handelt sich dabei um die Windows-Botschaften. Windows basiert grob gesagt auf zwei Dingen, Fenster und Fenster-Botschaften. Ein Fenster befindet sich eigentlich nur in einer Endlosschleife, in der auf die nächste Nachricht gewartet wird. Wird eine solche Nachricht empfangen, wird diese entsprechende behandelt. Danach wird wieder auf die nächste Nachricht gewartet. Alle Ineraktionen finden über solche Botschaften statt. Wird ein Fenster verschoben, neu gezeichnet, die Maus gedrückt, eine Taste auf der Tastur gedrückt, ... alles löst eine Nachricht aus. Auf die Details möchte ich hier nicht weiter eingehen, wichtig ist nur zu wissen, dass es diese Botschaften gibt und dass die verschickt werden können. In Delphi kannst Du Methoden so deklarieren, dass diese aufgerufen werden, sobald eine Nachricht ankommt. Zudem kannst Du natürlich auch eigene Nachrichten erzeugen (gilt nicht nur für Delphi!). Der dritte Weg der mir noch einfällt ist dem ersten nochmal sehr ähnlich. Es gibt noch das Observer-Pattern. Dabei handelt es sich um ein Designpattern (Nähreres kannst Du ergooglen). Das Muster ist sehr einfach, Du hast etwas, das Beobachtet wird (das Observable) und einen oder mehrere Beobachter (Observer). Das Observable bietet die Möglichkeit, dass sich Beobachter bei ihm registrieren (und natürlich auch wieder deregistrieren). Die Observer implementieren einfach ein bestimmtes Interface. Damit sichern die Observer zu, dass sie eine Methode mit einer bestimmten Signatur (Parameter, Rückgabetyp aber auch Namen!) besitzen, die öffentlich ist. Was genau die Methode macht bleibt hinter der Schnittstelle verborgen, man weiß nur, dass die Methode(n) vorhanden ist(/sind). Tritt das Ereignis ein, so wird das Observable einfach die entsprechende Methode aller registrierten Observer aufrufen (wie ein Call-Back). Anders als beim Methodenzeiger (es gibt genau einen) können hier also beliebig viele Observer benachrichtigt werden. Dieser Ansatz entspricht übrigens dem Objekt Orientierten Ansatz. Dazu muss ich aber sagen, dass das Observer-Pattern unabhängig von der OOP ist. Design-Pattern können in beliebiger Weise implementiert werden. Sie beschreiben nur ein Problem (Beobachten eines Ereignisses durch mehrere Beobachter) und dessen Lösung (Registrierte Observer mit bestimmter Schnittstelle). Ob es sich bei den Observern um Objekte handelt oder Module, Komponenten oder Methodenzeiger, egal, das gehört nicht mehr zum Pattern. Methodenzeiger kann man zwar für OOP halten, zumal eine Methode nur im Zusammenhang mit Objekten auftauchen, aber da würdest Du jetzt falsch liegen. Klassen/Objekte sind keineswegs der OOP vorbehalten. OOP ist nur ein Paradigma, es gibt gewisse Dinge, die man zusichern möchte. Dazu werden bestimmte Vorraussetzungen getroffen, die ein paar Dinge leichter möglich machen. In streng OOen Sprachen würdest Du nur mit Klassen und Schnittstellen arbeiten können und kannst damit sehr viel leichter einen Teil der Eigenschaften aufrecht halten. Die wenigsten Sprachen sind aber streng OO, insbesondere sind es nicht C++, Java, Delphi,... Eine der bekannten "echten" OO Sprachen wäre hier eher SmallTalk. An sich versucht man aber in der OOP explizite Zeiger zu vermeiden. Objekte werden hier immer als Referenz übergeben, wobei eine Referenz nur ein impliziter Zeiger ist. Die Arbeit mit einer Referenz entspricht dem transparenten Arbeiten mit Zeigern, Du merkst hier nichts von den Nachteilen/Problemen mit Zeigern, musst Dich auch nicht um das Dereferenzieren kümmern! Lange Rede, kurzer Sinn, dass was Du als OO eingestuft hattest fällt nicht darunter Ja, das wär's dann auch, Gruß Der Unwissende |
Zitat |
Registriert seit: 30. Jan 2007 Ort: Frankfurt/M. 34 Beiträge |
#16
supi, es klappt!!!
also ich hab das Ereignis eingefügt, wie beschrieben, und kann nun perfekt daten empfangen. nur ich knobel noch mit den eventchars rum, aber ich werd das schon rausfinden, in deinen vorigen Beiträgen hast du das ja schon alles erklärt, erst jetzt (Ereignis angesprochen) kann ich die tipps wirklich verwenden, und arbeite diese jetzt schritt für schritt durch! Doch, aber das wird schon was, ich berichte dann, wenn ich alles fertig hab, oder wenn eine unüberwindbare Frage auftaucht... Deine Erklärungen gewinnen immer mehr an Wert, je mehr ich davon verstehe und ausprobiere! Grüsse Moritz |
Zitat |
Registriert seit: 30. Jan 2007 Ort: Frankfurt/M. 34 Beiträge |
#17
Ok, das mit den Daten rausfiltern klappt!
Ich hab hier mal mein Programm und den Quellcode reingestellt! @ Der Unwissende: Vielen Dank!!!!!! Mit meinem ATmega8 über UART geht das Programm köstlich. Man kann die Länge und das "Zwischen-den-Daten--Byte" angeben (Eventchar). Ok, hier der Quellcode:
Delphi-Quellcode:
unit Steuerung;
interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs,Cport, StdCtrls; type TForm2 = class(TForm) Edit1: TEdit; Label1: TLabel; connbutton: TButton; portbutton: TButton; Button3: TButton; ComPort: TComPort; Button1: TButton; Memo1: TMemo; CheckBox1: TCheckBox; Edit2: TEdit; Label2: TLabel; Button2: TButton; Button4: TButton; procedure Button4Click(Sender: TObject); procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); procedure ComPortAfterOpen(Sender: TObject); procedure ComPortAfterClose(Sender: TObject); procedure portbuttonClick(Sender: TObject); procedure connbuttonClick(Sender: TObject); procedure ComPortRxFlag(Sender: TObject); procedure wertrausfiltern(var bote:string); private { Private-Deklarationen } public { Public-Deklarationen } end; var Form2: TForm2; botschaft:string; eve:string; evec:char; los:boolean; laenge:integer; implementation {$R *.dfm} procedure TForm2.ComPortRxFlag(Sender: TObject); var tat:integer; begin comport.ReadStr(botschaft,1); if botschaft= evec then los:=true; if los=true then begin comport.ReadStr(botschaft,laenge); Memo1.Lines.Add(botschaft); end; end; procedure TForm2.wertrausfiltern(var bote:string); //var i,v:integer; begin //i:=0; //v:=0; //repeat i:=i + 1 until bote[i] = chr(15); //Showmessage(bote[i]+ bote[i+1]); //if i<>1 then delete(bote,1,i); //i:=0; //repeat i:=i + 1 until ((bote[i] = evec) or (i=length(bote))) ; //Showmessage(bote[i]+ bote[i+1]); //Delete(bote,i,length(bote)); end; procedure TForm2.ConnButtonClick(Sender: TObject); begin Comport.Connected := not comport.Connected; los:=false; botschaft:=''; Memo1.Lines.Add('---STOP---'); end; procedure TForm2.ComPortAfterOpen(Sender: TObject); begin ConnButton.Caption := 'Disconnect'; end; procedure TForm2.ComPortAfterClose(Sender: TObject); begin ConnButton.Caption := 'Connect'; end; procedure TForm2.PortButtonClick(Sender: TObject); begin ComPort.ShowSetupDialog; end; procedure TForm2.Button3Click(Sender: TObject); //übernehmen button vom Eventchar begin if checkbox1.Checked =false then begin if length(Edit1.Text)>0 then begin eve:=Edit1.Text; evec:= eve[1]; comport.eventchar:= evec; end; end else if length(Edit1.Text)=2 then begin eve:=Edit1.Text; evec:= chr(strtoint(eve)); comport.eventchar:= evec; end; edit1.Text := evec; end; procedure TForm2.Button2Click(Sender: TObject); //übernehmen button von länge begin if Edit2.Text <> '' then laenge :=strtoint(Edit2.Text); end; procedure TForm2.FormCreate(Sender: TObject); begin comport.eventchar:= #15; evec:=#15; laenge:= 16; end; procedure TForm2.Button1Click(Sender: TObject); begin close; end; procedure TForm2.Button4Click(Sender: TObject); begin Memo1.Clear; end; end. //übrigens: "Stormy Encryption Products" ist nur von mir aus Spass an der Freude so ein Label, keine Firma oder sowas, hat dann immer nur sonen proffessionelleren Touch!! Grüsse Moritz |
Zitat |
Registriert seit: 27. Jul 2006 8 Beiträge |
#18
Hi!
Ich stehe grade auch vor so einem Problem. Ich will eine Terminal software proggramieren. Benutze eien AT Mega32 ist sehr ähnlich hat aber mehr I/0 leitungen als der mega8 das mit dem empfangen funktioniert gut, da habe ich einfach das fertige Terminal aus der Comport libary genommen. aber jetzt möchte ich auch daten (integer zahlen) an den µC schicken, hab aber keine ahnung wie ich das bewekstelligen sollte! |
Zitat |
Registriert seit: 13. Dez 2003 Ort: Berlin 1.756 Beiträge |
#19
Zitat von jo_gammler:
aber jetzt möchte ich auch daten (integer zahlen) an den µC schicken, hab aber keine ahnung wie ich das bewekstelligen sollte!
also erstmal das reine Verschicken geht ganz einfach. Erstell Dir einfach eine Instanz von TComport. Diese Instanz besitzt die Methode Write, der kannst Du beliebige Daten übergeben, die werden dann verschickt. Die Methode erwartet zwei Argumente, dass erste ist der Start des zu verschickenden Datums. Hast Du hier ein Integer, wäre es eine Variable, die eben vom Typ Integer ist. Hast Du hingegen ein Array, wäre es das Array an der Position 0 (bei statischen Arrays 0 ggf. durch angegebenen Index ersetzen), bei Strings wäre es der String an der Stelle 1. Der zweite Parameter gibt an, wie groß das Datum ist das übertragen werden soll. Die Größe wird dabei in Byte zurückgegeben. Die Größe kannst Du mit sizeOf ermitteln. Bei Integern solltest Du aber aufpassen, die sind generisch. Auf aktuellen 32-Bit Systemen wird ein integer noch 32 Bit haben, auf 64 Bit Systemen hingegen wird es 64-Bittig (usw.). Bei Arrays musst Du diese Größe noch mit der Anzahl der Elemente multiplizieren. Hier ist es wichtig, dass Du nicht sizeOf auf das gesamte Array anwendest, sondern wirklich sizeOf(deinArray[0]) * length(deinArray). Ansonsten bekommst Du eine falsche Größe, da Du sonst nur die Größe der Variable, die auf das Array zeigt speichern würdest. Alles zusammen könnte dann so aussehen:
Delphi-Quellcode:
Was Du dabei überträgst sind aber nur Bytes. Dein µC weiß also nicht, dass Du hier eigentlich Integer Werte verschickst. Da musst Du in dem Programm, dass die eintreffenden Daten weiterverarbeitet, selbst dafür sorgen, dass die wieder zusammengesetzt werden. Ggf. musst Du dann natürlich auch den Unterschied zwischen Big- und Little-Endian ausgleichen.
var i: Integer;
numbers: Array of Integer; begin // Array mit 10 Elementen erstellen setLength(numbers, 10); for i := 0 to 9 do begin // mit den Zahlen 0 .. 9 füllen number[i] := i; end; // Array verschicken comPort1.Write(numbers[0], sizeOf(numbers[0]) * length(numbers)); end; Gruß Der Unwissende |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |