Moin liebe Programmierfreunde,
Mein Ziel ist es über den UART Daten von meinem Rechner zum STM32F100 zu
empfangen.
Der Empfang soll wie folgt von statten gehen:
Der Sender(PC) schickt ein genaue Anzahl an Daten auf dem UART des
STM32.
Der STM32 empfängt diese Daten und wartet auf das Zeichen "\n",
wenn er das Zeichen empfangen hat soll der UART buffer gelert werden und
wieder auf anfang gestzt werden.
So ein Code sagte mehr als Tausend Worte:
Völlig verkehrter Ansatz. Dein RX-Interrupt kann in den Buffer
reinschreiben, während er gelöscht wird.
Nimm einen Ringbuffer und lies im Applikationsteil zeichenweise daraus
aus und in einen Applikationsbuffer, solange selbiger nicht voll ist.
Mach Deine Verarbeitung, wenn der Applikationsbuffer ein return liest.
Setz Deinen Zähler für den Applikationsbuffer dann zurück (Buffer
löschen ist überflüssig).
Gibt Dir jemand Geld für übermäßige Verwendung von Unterstrichen?
Ich würde bei verschachtelten Schleifen unterschiedliche Variablen Namen
verwenden, damit klar ist, wann welches "i" verwendet werden soll.
Vielleicht ist das sogar der Knackpunkt.
Um ganze Arrays zu kopieren, nimmt man üblicherweise die memcpy()
Funktion. Und zum Leeren des Puffers die memset() Funktion.
Ich denke, du hast da einen Logikfehler:
HAL_UART_Receive_IT() empfängt eine fest gelegte Anzahl von Bytes
asynchron. Dass heißt: In der danach folgenden Zeit wird der Puffer nach
und nach gefüllt. Dein Programm hält an dieser Stelle aber nicht an!
Danach untersuchst du den Inhalt des Puffers auf den Zeilenumbruch.
Tatsächlich enthält der Puffer zu diesem Zeitpunkt aber nur zufällige
Bytes oder was auch immer du vorher bei der Initialisierung hinein
geschrieben hast.
Hallo Stefan,
vielen dank erstmal für den Tipp mit memcpy() und memset(), habe meinen
Code auch gleich umgeändert. Jetzt wirkt es auch bedeutent
übesichtlicher.
Das Datenpaket kommt nur alle Skunde einmal.
Ich versuche das mal genauer zu beschreiben.
Das Datenpaket welches von dem PC kommt hatt immer ein "\n" als
Abschluss.
Das hier soll mal mein Buffer im Idealfall darstellen.
| T | E | S | T | \n |
Jetzt kann es natürlich sein, dass mal ein nicht vollständiges Paket
ankommt.
| S | T | \n | | |
In jedem fall soll der buffer gelert werden und wieder auf Startposition
gesetzt werden, damit das nächste Paket wieder vollständig Empfangen
werden kann.
Also immer wenn ich ein \n empfange möchte ich den buffer leeren und
wieder auf Anfang setzen.
Ich hoffe das ist jetzt verständlicher.
Ach so ja ich bekomme Geld dafür das ich übermäßig viele Unterstriche
verwende ;-)
Bei deinem Konzept musst du eine blockierende HAL Funktion verwenden,
also HAL_UART_Receive (ohne _IT Suffix).
Und da du sofort auf den Zeilenumbruch reagieren willst (nicht erst wenn
der Puffer komplett gefüllt wurde), musst du die Funktion mit einem
Puffer für nur 1 Byte aufrufen und die einzelnen Bytes einsammeln.
So richtig sinnvoll erscheint mir das so allerdings nicht. Normalerweise
würde man die Daten mittels Interrupt-Handler in einen Ringpuffer
empfangen und den dann (außerhalb der ISR) abarbeiten, sobald ein
bestimmtes Ende-Zeichen (in deinem Fall wohl der Zeilenumbruch) erkannt
wurde.
Wie man die DMA basierte Funktion HAL_UART_Receive_IT in deinem Fall
sinnvoll nutzen kann, ist mir unklar. Solche Sachen programmiere ich
immer "zu Fuß". Gibt es dazu kein Tutorial?
Vielleicht muss man dort den Ringpuffer in zwei Hälften aufteilen, die
man immer abwechselnd per HAL_UART_Receive_IT füllt. Aber auch dann
brauchst du einen Interrupt-Handler, nämlich einen, der vom DMA
Controller aufgerufen wird und den Empfang der anderen Hälfte des
Puffers auslöst.
Mathias G. schrieb:> In jedem fall soll der buffer gelert werden und wieder auf Startposition> gesetzt werden, damit das nächste Paket wieder vollständig Empfangen> werden kann.
Das ist das kleinste Problem. Dein Grundkonzept ist unsinnig. So eine
Empfangsroutine kann man im allgemeinen NICHT als naive Funktion
gestalten, sondern muss sie als Interruptroutine aufbauen, welche wie
eine Statemachine arbeitet und erst durch mehrfachen Aufruf ein
Ergebnis liefert.
https://www.mikrocontroller.net/articles/Interrupt#UART_mit_Interrupts> Also immer wenn ich ein \n empfange möchte ich den buffer leeren und> wieder auf Anfang setzen.
Ja und?
1
if(rx_char=='\n'){
2
rx_buffer[0]=0;
3
}
in de allermeisten Fällen erübrigt sich ein Löschen des Empfangsbuffers,
es reicht, das 1. Zeichen auf 0 zu setzen, damit erkennen alle
C-konformen Stringfunktionen einen leeren String.
Man könnte zwei Puffer verwenden (z.B. zweidimensionales Array
buffer[2,size]).
Die Interruptroutine füllt den Puffer bis das Zeichen \n empfangen wurde
und schließt den Puffer. Dann wird der Puffer dem Hintergrund zur
Verfügung gestellt und der andere Puffer zum Empfang verwendet.
Signalisierung über "Buffer Descriptoren".
https://stackoverflow.com/questions/36625892/descriptor-concept-in-nic
Mache dich mit dem Konzept Ringbuffer vertraut und nutze es.
Mache dich mit dem Konzept Statemachine vertraut.
Um alle möglichen Betriebssituationen abzudecken, setze eine
Statemachine auf. Betriebssituationen bzw Ereignisse sind aus meiner
Sicht:
1. noch nie Daten empfangen (Init nach Programmstart)
2. Timeout Sendeinterval (hier war es glaube ich 1s)
3. Timeout während Datenempfang
3. Datenbyte empfangen obwohl \n kommen müsste
4. \n empfangen obwohl Datenbyte kommen müsste
5. Gut-Fall: Daten korrekt empfangen
6. Warte auf Beginn des nächsten Datenpaketes
Wenn ich es umsetzen würde, würde ich es etwa so machen:
Der Ringbuffer wird im Rx-Interrupt befüllt. Ansonsten macht der Rx
Interrupt nichts. Die Applikations-Schicht liest aus dem Ringbuffer und
arbeitet den Zustandsautomaten ab.
Der Ringbuffer muss bei korrekter Implementierung übrigens
nicht gesperrt werden, wenn es nur einen Leser und einen Schreiber gibt.
> Solche Sachen programmiere ich> immer "zu Fuß". Gibt es dazu kein Tutorial?
Ich benutze bei meinem MP3-Player immer drei Buffer, einer der von Hand
mit Daten vom MP3_Encoder beschrieben wird, einer vom DMA automatisch
abgespielt wird und einer als Reserve damit es beim umschalten etwas
spielraum gibt und man nicht auf ein Byte genau arbeiten muss.
Allerdings scheint mir DMA bei soetwas simplen wie Uart etwas
uebertrieben wenn man da nicht handfeste Gruende fuer hat (z.B soll
empfangen wenn MCU im Sleepmode ist).
Das sind aber alles keine Sachen mit denen sich ein Anfaenger
beschaeftigen will. Da sollte ein einfacher Ringbuffer ausreichen. Der
ist schon anstrengend genug weil man sich fragen muss wer da wann aus
dem IRQ den Positionszeiger weitersetzen darf und wann nicht und wie man
dann die Fuellstandsanzeige abfragen kann.
Olaf
Wenn das Datenpaket wirklich nur einmal/s ankommt, reicht EIN einfacher
Empfangspuffer. Man braucht nicht mal 2, geschweige denn einen
Ringpuffer.
K.I.S.S.!
Olaf schrieb:> Das sind aber alles keine Sachen mit denen sich ein Anfaenger> beschaeftigen will. Da sollte ein einfacher Ringbuffer ausreichen.
Ja, nur leider scheint die HAL das nicht bereit zu stellen. Schon sind
wir an dem Punkt, wo wir um eine eine bereits unnötig komplexe Funktion
noch mehr drum-herum bauen, damit es tut, was wir brauchen.
> Ja, nur leider scheint die HAL das nicht bereit zu stellen.
Der lernt der Anfaenger gleich das es sinnvoll ist sich direkt mit der
Hardware zu beschaeftigen. .-)
Wenn er es eines Tages mal beruflich macht dann muss er das ja sowieso.
Olaf
Hier mal ein kleiner Code, der Textkommandos empfängt:
1
voidreceive_command(void)// receive from UART
2
{
3
staticcharcbuf[CMD_MAX_LEN];// command buffer
4
staticuint8_tidx=0;
5
if(ukbhit0()==0)
6
return;
7
charch=ugetchar0();
8
switch(ch)
9
{
10
default:
11
cbuf[idx]=ch;
12
if(idx<sizeof(cbuf)-1)
13
idx++;
14
return;
15
case'\b':
16
if(idx)
17
idx--;// remove last char (interactive mode)
18
return;
19
case'\n':
20
case'\r':
21
if(idx==0)// do nothing on empty commands
22
return;
23
cbuf[idx]=0;
24
idx=0;
25
execute(cbuf);
26
uputs0(cbuf);// send answer
27
}
28
}
ukbhit0() prüft, ob mindestens ein Zeichen in der UART-FIFO ist.
ugetchar0() liest ein Zeichen aus der UART-FIFO.
Puffer löscht man nicht. Man schreibt einfach die neuen Zeichen rein.
Wird das Endezeichen erkannt, übergibt man den Puffer an die weitere
Verarbeitung.
Olaf schrieb:> Da sollte ein einfacher Ringbuffer ausreichen. Der> ist schon anstrengend genug weil man sich fragen muss wer da wann aus> dem IRQ den Positionszeiger weitersetzen darf und wann nicht und wie man> dann die Fuellstandsanzeige abfragen kann.
Aus meiner Sicht ist das recht einfach. Wie meinst du das? Übersehe ich
etwas?
Mathias G. schrieb:> In jedem fall soll der buffer gelert werden
Lass die Finger vom Schreibpointer des Puffers. In den Puffer hinein
schreibt nur die UART-ISR.
Puffer werden "geleert", indem der Lesepointer auf den selben Index wie
der Schreibpointer gesetzt wird. Du solltest also einfach den
Lesepointer so setzen, dass er das korrupte Kommando igonriert.
Stefan ⛄ F. schrieb:> nur leider scheint die HAL das nicht bereit zu stellen.
Das ist mir auch schon arg unangenehm aufgefallen. Die dort
implementierten UART-Funktionen muss ein Praktikant im ersten Lehrjahr
gebastelt haben...
Heinz schrieb:> Um alle möglichen Betriebssituationen abzudecken, setze eine> Statemachine auf. Betriebssituationen bzw Ereignisse sind aus meiner> Sicht:
7. beliebig gestörte Daten empfangen (1)
8. viele Daten empfangen, ohne dass ein \n kommt (2)
(1) durch Störspikes auf der Empfangsleitung umgekippte Bits oder
Geisterdaten, die durch ein "falsches" Startbit erzeugt werden
(2) der klassische Buffer-Overflow
Mathias G. schrieb:> Mein Ziel ist es über den UART Daten von meinem Rechner zum STM32F100 zu> empfangen.> Der Empfang soll wie folgt von statten gehen:> Der Sender(PC) schickt ein genaue Anzahl an Daten auf dem UART des> STM32.> Der STM32 empfängt diese Daten und wartet auf das Zeichen "\n",> wenn er das Zeichen empfangen hat soll der UART buffer gelert werden und> wieder auf anfang gestzt werden.
Eine genaue anzahl von Daten? Und was ist mit den empfangenen Daten,
wenn da mal etwas dazwischen gekommen ist? Mit sowas muß man doch bei
einer seriellen Strippe zwischen dem PC und deinem µC auch rechnen.
Normalerweise schreibt man sich einen Treiber für den UART, den man
abfragen kann, ob er denn Empfangsdaten hat und der die empfangenen dann
zeichenweise herausgibt.
Dann ist es für die höheren Schichten in der Firmware nur noch
interessant, daraus das sicher zu erkennen, was gebraucht wird - und das
macht man zum Beispiel so, daß man nicht eine genaue Anzahl von Zeichen
abzählt, sondern sozusagen Kommandos empfängt und bei Bedarf auch noch
eine Prüfsumme dabei hat.
Schau dir mal den gewöhnlichen Intel Hexcode an. Dort wird auch
blockweise Informationen übertragen und das Ganze ist dank
Kennbuchstaben und Prüfsumme einigermaßen verläßlich. Man läßt eine
ganze Zeile in einem Puffer auflaufen, kann die Kennung überprüfen und
die Prüfsumme abtesten und wenn alles OK ist, den Inhalt verwenden.
Natürlich gibt es auch andere Verfahren - aber so wie du das angegangen
bist, ist es nicht gut.
W.S.
Lothar M. schrieb:> Du solltest also einfach den> Lesepointer so setzen, dass er das korrupte Kommando igonriert.
Bringst du gerade den Empfang von Bytes und das Bilden von Kommandos
durcheinander? Das sind eigentlich zwei verschiedene Layer in der
Software.
Die Uart ISR hat (so mache ich es jedenfalls) nur die Aufgabe,
empfangene Bytes in den Ringbuffer zu schreiben. Höhere Schichten lesen
den RIngbuffer aus, bilden Kommandos daraus, berechnen ggf Checksummen
und geben die Kommandos zru Bearbeitung weiter oder kümmern sich um das
Fehlerhandling (Timeout, Kommando korrupt etc).
Man könnte auch in der ISR Bytes sammeln und die Kommandos dort direkt
zusammenbauen, aber ich sehe nur sehr selten. Im Normalfall werden ISRs
sehr kurz gehalten - aber keine Regel ohne begründete Ausnahme ... :)
@falk
kiss:
Knights In Service of Satan
https://www.kissonline.com/
ja,ja
keep it stupid simple
ist nicht offensichtlich welche kiss du so meinen könntest
@motze
>Sender(PC) schickt ein genaue Anzahl an Daten auf dem UART des STM32
Empfang per DMA mit entsprechender Länge und IRQ nach Empfang des
Datenpakets,
Timeout nicht vergessen wenn Übertragungsfehler
oder so:
https://stm32f4-discovery.net/2017/07/stm32-tutorial-efficiently-receive-uart-data-using-dma/
Tilen Majerle ist mittlerweile FAE bei ST, hat vielleich Ahnung ;)
Heinz schrieb:> Im Normalfall werden ISRs sehr kurz gehalten - aber keine Regel ohne> begründete Ausnahme
Dem kann man nur voll zustimmen!
Wichtig ist nur die Unterbindung eines Puffer überlaufen in der
Interruptroutine, wenn die Applikation die Daten nicht abholt, sonst
gibt es böse Speicherüberschreiber.
Vielen dank für die ganzen Information, leider bin ich gestern nicht
mehr dazu gekommen eure ganzen Informationen zu verarbeiten. Wie gesagt
ich verdiene ja mein Geld mit Unterstrichen und gestern waren sehr viele
Unterstriche.
Ich hoffe das ich heute dazu kommen werde mich ein wenig einzulesen.
Bei weiteren problemen zu dem Thema werde ich mich wieder melden.
Vielen dank erstmal
Heinz schrieb:> Die Uart ISR hat (so mache ich es jedenfalls) nur die Aufgabe,> empfangene Bytes in den Ringbuffer zu schreiben.
So habe ich das auch für mich als optimal ermittelt. Die UART-FIFO
unterscheidet nichtmal zwischen Binärdaten und Textdaten.
Timeouts als Protokollelement finde ich evil und benutze sie nicht.
Daten im FIFO werden daher nie verworfen. Wenn die übergeordnete Schicht
die Daten nicht parsen kann, antwortet sie mit einer Fehlermeldung. In
der Regel wiederholt dann der Sender das Paket. Das passiert z.B beim
Stecken des Kabels oder Einschaltreset eines Teilnehmers.
Timeouts gibt es erst in höheren Schichten, d.h. ein Teilnehmer schickt
ein Kommando und der andere antwortet nicht innerhalb einer bestimmten
Zeit.
So gerade arbeite ich mich in das Thema Statemachine ein, um den
überblick nicht zu verlieren. Ihr Redet ja auch immer über Statemachine
und ich will ja mitreden können.
Kennt ihr ein gutes Buch zu diesem Thema? Vielleicht auch eine Software
wo ich Statemachine Simulieren kann?
Danach werde ich mich in das Thema Ringspeicher einarbeiten und dann
schaue ich weiter mit dem RS232 problem.
Mathias G. schrieb:> Kennt ihr ein gutes Buch zu diesem Thema? Vielleicht auch eine Software> wo ich Statemachine Simulieren kann?
Wie bitte?
Na klar gibt es theoretisch genau diese Software: nämlich deine noch zu
schreibende Firmware für deinen STM32F1xx.
Ich schätze, daß es jetzt allerhöchste Zeit ist, daß du mal die Katze
aus dem Sack läßt, also mal schreibst, was all dieses Daten-Empfangen zu
bedeuten hat und wozu es gut sein soll.
Ich glaube auch nicht, daß du auf einem µC in Software eine
Zustands-Maschine implementieren mußt. Stattdessen vermute ich, daß du
einfach nur anhand empfangener Daten irgendwelche Portpins wackeln
lassen willst. Also schreib lieber mal den finalen Zweck, sonst
entgleist dieser Thread völlig.
Und wenn du nach Beispielen suchst, wie man einen Treiber für den UART
schreibt, oder ein Kommandoprogramm schreibt, oder so was, dann findest
du hier im Forum einen Haufen Zeugs dazu.
Für mich ist es schon erstaunlich, wie man so etwas Einfaches wie einen
UART-Treiber (mit 'innenliegendem' Ringpuffer natürlich!) zu einem
Riesenproblem machen kann.
W.S.
W.S. schrieb:> Ich glaube auch nicht, daß du auf einem µC in Software eine> Zustands-Maschine implementieren mußt
Komisch, das wird doch allgemein empfohlen, im Warteschleifen zu
vermeiden und Multi-Tasking zu ermöglichen.
Mathias G. schrieb:> Kennt ihr ein gutes Buch zu diesem Thema? Vielleicht auch eine Software> wo ich Statemachine Simulieren kann?
Ich denke, dass ein Simulator wenig Sinn macht. Immerhin hat der Chip
eine schnelle Debugger Schnittstelle, man kann den Code daher direkt auf
dem Chip testen und untersuchen.
Ein Buch zum Thema kenne ich nicht. Notfalls kommst du vielleicht mit
diesem Artikel weiter:
http://stefanfrings.de/multithreading_arduino/index.htmlW.S. schrieb:> Für mich ist es schon erstaunlich, wie man so etwas Einfaches wie einen> UART-Treiber (mit 'innenliegendem' Ringpuffer natürlich!) zu einem> Riesenproblem machen kann.
Das kommt davon, wenn man mit der HAL einsteigt. Gerade an diesem Punkt
wirft sie einem regelrecht Stöcke zwischen die Füße.
Stefan ⛄ F. schrieb:> W.S. schrieb:>> Ich glaube auch nicht, daß du auf einem µC in Software eine>> Zustands-Maschine implementieren mußt>> Komisch, das wird doch allgemein empfohlen, im Warteschleifen zu> vermeiden und Multi-Tasking zu ermöglichen.
Es ist doch allgemein bekannt, dass W.S. nunmal keine Ahnung hat.
W.S. schrieb:> Ich glaube auch nicht, daß du auf einem µC in Software eine> Zustands-Maschine implementieren mußt.
Schaden kann das Wissen darüber nicht. Vielleicht hilfts dem TO.
Falk B. schrieb:> Na die gehören doch bei C zum guten Ton ;-)
Zu wissen was man tut ist überall Voraussetzung. Auch bei
Assemblerprogrammierung ist man vor diesen Fehlern nicht gefeit. Nur
Codereviews sind schwieriger durchzuführen.
Bei Realisierung über Arrays kann man den Index in dieses eingrenzen.
Bei Zeiger auf Maximalwerte prüfen.
Stefan ⛄ F. schrieb:> Komisch, das wird doch allgemein empfohlen, im Warteschleifen zu> vermeiden und Multi-Tasking zu ermöglichen.
Hä? Du schreibst in Rätseln. Wozu ist beim UART-Treiber irgend eine
Warteschleife erforderlich? Der Datentransfer erfolgt aus dem und in den
jeweiligen Ringpuffer und der eigentliche Transfer wird per Interrupt
abgewickelt. Nix Warteschleife.
Allenfalls kann man für die Leute, die sowas nicht verstehen wie:
1
if(RxAvail(toUART1)
2
{c=GetChar(toUART1);
3
Behandle_empfangenes_Zeichen(c);
4
}
eine Routine zum Warten bis Empfang anbieten.
W.S.
Gerald K. schrieb:> Schaden kann das Wissen darüber nicht. Vielleicht hilfts dem TO.
Ja, vielleicht.
Schaden kann das Wissen um die Schrödingersche Wellengleichung wohl auch
nicht - aber hilfreich ist das zum Schreiben zweier
Ringpuffer-Behandlungsroutinen (eine rein, die andere raus) wohl nicht
so sehr.
Das Grund-Übel ist eigentlich, daß die Programmieranfänger heutzutage
sich einfach keine ausreichenden Gedanken über Programmierstrategien
machen, also wie man für den vorliegendne Fall am zweckmäßigsten
vorgeht und (soweit möglich) das Ganze so ausarbeitet, daß man es auch
für spätere anders gelegene Fälle benutzen kann. Stattdessen will man
sich das, was man eigentlich selbst an Strategie gelernt haben sollte,
von einem Hersteller-Tool generieren lassen. Nein, so lernt man es eben
nicht. Dieser Thread zeigt es mal wieder deutlich.
Natürlich kann man auch durch Abgucken dazu lernen - die Chinesen
haben uns auf diese Weise bereits überholt - und hier im Forum gibt es
genug Codebeispiele für das hier abgehandelte Thema, selbst bereits
passend für einen STM32F10x. Hab ja selber sowas vor Zeiten gepostet.
Siehe Lernbetty, siehe STM32F103C8T6.ZIP und so weiter. Dort überall
sind fertige UART-Treiber drin, auf die man bloß seine werte Nase
richten müßte. Aber nein, selbst zum Suchen in diesem Forum sind genau
DIE Leute nicht bereit, die derart auf dem Schlauch stehen wie der TO.
W.S.
W.S. schrieb:> Das Grund-Übel ist eigentlich, daß die Programmieranfänger heutzutage> sich einfach keine ausreichenden Gedanken über Programmierstrategien> machen, also wie man für den vorliegendne Fall am zweckmäßigsten vorgeht> und (soweit möglich) das Ganze so ausarbeitet, daß man es auch für> spätere anders gelegene Fälle benutzen kannDem kann man nur beipflichten !
Man kann sich die Stratige bei der Heimfahrt mit den Öffsis (bitte nicht
beim Autolenken) oder Abends statt dem Schäfchenzählen zurecht legen.
Vielleicht wacht man am Morgen, mit HEUREKA, mit dem Programm im Kopf
auf (
https://www.br.de/fernsehen/ard-alpha/sendungen/entdeckungen-grosser-forscher/kekule-august-102.html).
W.S. schrieb:> Natürlich kann man auch durch Abgucken dazu lernen - die Chinesen haben> uns auf diese Weise bereits überholt
Jetzt können wir bei den Chinesen abgucken, oder gleich kaufen.
Vielen dank für eure Hilfe, habe es jetzt geschafft und der UART läuft
wie ich es mir wünsche:
Bei einem \r wird der empfang gelöscht und der text ausgegeben, aber ein
Code sagt mehr als tausend Worte.
Mathias G. schrieb:> Bei einem \r wird der empfang gelöscht und der text ausgegeben, aber ein> Code sagt mehr als tausend Worte.
Nochmal, Löschen ist Unsinn.
Und Gratulation, Du hast den Bufferoverflow erfunden.
Der Code wird Dir bei falschen Daten, Baudrate oder floatendem RXD-Pin
irgendwann den gesamten RAM zersemmelt haben.
Falk B. schrieb:> empfangen[count++] = rx;> if (count >= BUFFER_SIZE) count = BUFFER_SIZE-1;
Sicherer ist erst testen und dann schreiben.
Es kann ja ein anderer Programmteil Deinen Index mit Müll überschrieben
haben.