mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik 16bit-Register im ATmega8 ASM


Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo

Ich hab das Forum schon durchsucht und mir einige Codes angeschaut, habe 
aber irgendwie keine befriedigende Lösung gefunden.

Ich möchte ein Register bis 1000 hoch- bzw. runterzählen über 
Tastendruck (Befehle dec und inc), gleichzeitig soll die Zahl im Display 
ausgegeben werden und anschließend mit dieser Zahl gearbeitet werden 
können im speziellen Fall soll sie einer Verzögerung dienen sprich sie 
wird einfach wieder runtergezählt.

Das alles hab ich schon hinbekommen mit einem normalen Register nur geht 
das dann ja leider mal nur bis 255. Jetzt hab ich mir schon jede Menge 
überlegt mit Überlauffunktionen etc, damit das Ganze auf zwei Register 
verteilt wird. Problem ist nur, dass das dann viel Speicher und Zeit 
braucht. Schöner währe es während des Zählvorgangs nicht noch prüfen zu 
müssen ob die Zahl auch nicht 255 übersteigt sondern wenn man einfach 
ein Register hat, dass bis 1000 zählen kann.

Es gibt da diese Z-Register (r30+31) nur habe ich diese in diesem 
Zusammenhang noch nirgen gesehen, immer nur als Zeigerregister. Kann man 
diese Register auch als ein einziges 16bit Register verwenden? und wenn 
ja wie, gibts da codesamples oder ist das so trivial, dass ich darüber 
längst gestoplert sein müsste?


vielen dank für die Hilfe

Gruß

Matze

Autor: Niels Hüsken (monarch35)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Bevor man sich hier in den Untiefen der Binärarythmetisk stürzt, 
empfehle ich dem Anfänger bei sowas selbst Hand anzulegen und in BCD 
zählen.

Aber als Tipp für die Zukunft solltest du dir angewöhnen gleich in Hex 
zu arbeiten und erst im letzten Schritt nach Dezimal umrechnen. Du 
willst also von 0 - 0x3e8 zählen. Den Dezimalwert erhälst du 
Stellenweise, wenn du den Wert immer durch 10 (0x0a) teilst.

0x3e8 / 0xa =  0x64 rest 0
0x064 / 0xa =  0x0a rest 0
0x00a / 0xa =  1 rest 0

Die "1" mit den drei "0" sind dein Ergebnis bei der Wandlung nach 
dezimal. Aber wie ich Eingangs bereits sagte: dem Anfänger empfehle ich 
erstmal mit BCD zu arbeiten. Das ist aufwenider aber weniger 
kompliziert.

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Es gibt bei einem 8-Bit-Controller nunmal nur 8-Bit-Register. Punkt. 
Wenn Du aus zwei solcher Register ein 16-Bit-Register machen willst, 
dann musst Du in Kauf nehmen, dass i.d.R. zwei Operationen erforderlich 
sind, um eine Addition oder Subtraktion zu machen. Selbstverständlich 
kannst Du aber r31 und r30 (und natürlich auch r25+r24, r27+r26 und 
r29+r28) für beliebige Zwecke nutzen, wenn Du sie nicht gerade als 
Zeiger benötigst. Und Du kannst dann natürlich auch mit adiw oder sbiw 
darauf zugreifen.

Allerdings solltest Du beachten, dass das gegenüber 
Inkrementieren/Dekrementieren und Carry-Flag-Addieren/Subtrahieren 
keinerlei Zeitvorteil mitbringt, da sbiw und adiw 2 Zyklen brauchen (inc 
bzw. dec und sbc bzw. adc nur je einen, macht zusammen auch jeweils 2 
Zyklen...).

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Niels Hüsken wrote:
> Bevor man sich hier in den Untiefen der Binärarythmetisk stürzt,
> empfehle ich dem Anfänger bei sowas selbst Hand anzulegen und in BCD
> zählen.
Ist hoffentlich nicht wirklich Dein Ernst, oder? Dem OP ist die 
"normale" Zählerei schon zu langsam, und er kommt mit einem einzigen 
Übertrag schon ins Schleudern, und Du willst ihm BCD-Zahlenspielchen 
empfehlen, mit denen er sich nicht nur (beim Zählen bis 1000) mit jedem 
Inkrement drei Überträge einhandelt, sondern auch noch 3 
Vergleichsoperationen? Also bitte...

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Matthias:
Schau bitte mal ins Instruction Set Summary oder in die Hilfe vom 
AVRStudio (falls Du das, was ich vermute, noch nicht getan hast). Da 
stehen bei den Subtraktions- und Additionsbefehlen Beispiele, wie man 
16-Bit-Werte behandelt.

Autor: Niels Hüsken (monarch35)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Johannes M. wrote:

> Niels Hüsken wrote:
>> Bevor man sich hier in den Untiefen der Binärarythmetisk stürzt,
>> empfehle ich dem Anfänger bei sowas selbst Hand anzulegen und in BCD
>> zählen.

> Ist hoffentlich nicht wirklich Dein Ernst, oder? Dem OP ist die

Ich bin der Meinung, BCD ist für Anfänger einfacher. Das es Aufwendiger 
ist, steht ausser Frage, aber es ist weniger kompliziert...

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Niels Hüsken wrote:
> Ich bin der Meinung, BCD ist für Anfänger einfacher. Das es Aufwendiger
> ist, steht ausser Frage, aber es ist weniger kompliziert...
BCD ist das allerletzte! Einen Rechner im Dezimalsystem rechnen zu 
lassen ist völlig sinnfrei. Anfänger sollten auf jeden Fall sofort 
anfangen, binär bzw. hexadezimal zu denken. Da führt kein Weg dran 
vorbei! Und Zählen in Dezimal ist der allergrößte Unfug. Gerade beim 
Zählen muss man sich schließlich überhaupt nicht um das Zahlensystem 
kümmern. Dafür gibt es schließlich die Befehle adc, sbc und für 
Vergleiche cpc. Und für alle stehen genau für den aktuellen 
Anwendungsfall Beispiele im Instruction Set Summary. Und für die Eingabe 
im Code kann man schließlich immer noch Dezimalzahlen benutzen (und bei 
16-Bit-Werten low() und high()).

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Matthias wrote:

> Das alles hab ich schon hinbekommen mit einem normalen Register nur geht
> das dann ja leider mal nur bis 255. Jetzt hab ich mir schon jede Menge
> überlegt mit Überlauffunktionen etc, damit das Ganze auf zwei Register
> verteilt wird. Problem ist nur, dass das dann viel Speicher und Zeit
> braucht.

Wow, Du kannst also 100.000 neue Werte pro Sekunde ablesen!!!
Ich kann nur etwa 5.

In der Regel zählt eine CPU viel zu schnell und man muß große 
zusätzliche Zeitverzögerungen einbauen, damit man überhaupt was sieht 
und auch zum Entprellen.


Peter

Autor: Niels Hüsken (monarch35)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich denke, ich rede gegen eine Wand...offensichtlich willst du nicht 
verstehen, was ich meine...

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hier mal die drei erforderlichen Operationen (ohne Gew(a)ehr):
;Inkrementieren 16 Bit:
inc r16     ;Low-Byte inkrementieren
adc r17, 0  ;High-Byte plus Carry

;Dekrementieren 16 Bit:
dec r16     ;Low-Byte dekrementieren
sbc r17, 0  ;High-Byte minus Carry

;Vergleich 16 Bit:
ldi r18, high(1000) ;High-Byte vom Vergleichswert in Register
cpi r16, low(1000)  ;Low-Byte vergleichen
cpc r17, r18        ;High-Byte mit Carry vergleichen
brlo irgendwo       ;Verzweige irgendwohin, wenn kleiner als Vergleichswert

@Niels:
Nö, ich kann tatsächlich keinen Sinn in Deinen Aussagen entdecken, 
obwohl ich es gern würde... Vielleicht kann die Wand Dich ja 
verstehen...

Autor: Niels Hüsken (monarch35)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Johannes M. wrote:

> @Niels:
> Nö, ich kann tatsächlich keinen Sinn in Deinen Aussagen entdecken,
> obwohl ich es gern würde... Vielleicht kann die Wand Dich ja
> verstehen...

Aha, ok, ich habe mich auch mehr auf die Umwandlung nach Dezimal 
bezogen, als auf die Nutzung einfacher Funktionen und Grundrechenarten.

Aber die Wandlung von Hex nach Dezimal ist wesentlich komplexer, als die 
von BCD nach Dezimal, da hier praktisch keine Wandlung mehr stattfinden 
muss.

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
das hilft mir doch schonmal sehr weiter. nur ich bin noch zu sehr am 
anfang um genau zu verstehen was du machst. arbeitest du jetzt mit den 
X, Y, Z-registern? wennja, wie definiere ich die, lade da werte ein etc. 
wahrscheinlich alles anfängerfragen aber ich finde in den tutorials 
immernur einen bezug zu der verwendung als pointer und das hilft mir 
irgendwie nicht weiter. kann man mir sonst vielleicht ne gute quelle 
nennen wo ich das nachlesen kann mit diesen zeiger-registern r30-r31 
usw?

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Dannegger wrote:

> Wow, Du kannst also 100.000 neue Werte pro Sekunde ablesen!!!
> Ich kann nur etwa 5.

naja, ich machs halt so, dass ich den taster abfrage, dann entscheide ob 
ich einen hochzähle, einen runterzähle oder im programmtext weiter gehe 
("enter"), nach dem weiterzählen gebe ich dann den wert über das display 
aus und starte die abfrage von neuem. kann man alles sicherlich 
eleganter lösen nur bin ich grade mal ne woche dabei mich auch nur 
ansatzweise damit zu beschäftigen und bin schon froh soweit alleine 
gekommen zu sein ;-)

Autor: Niels Hüsken (monarch35)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Die Nutzung eines pseudo-16Bit registers bringt dir nichts, weil du 
damit nicht arbeiten kannst (keine Vergleiche oder ähnliches). Sie sind 
tatsächlich auschliesslich dazu gedacht, auf den Speicher zu zeigen.

Du kommst nicht dran vorbei 8Bit Register zu paaren. Wie man das macht, 
steht in einem Link, den Johannes schon vorher gepostet hatte.

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
vielen dank, dann werd ich das inst.summary mal durchforsten

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Matthias wrote:

> naja, ich machs halt so, dass ich den taster abfrage, dann entscheide ob
> ich einen hochzähle, einen runterzähle oder im programmtext weiter gehe
> ("enter"), nach dem weiterzählen gebe ich dann den wert über das display
> aus und starte die abfrage von neuem. kann man alles sicherlich
> eleganter lösen

Um Eleganz geht es nicht.

Du hattest aber behauptet, 16Bit Operationen dauern zu lange und 
benötigen zuviel Speicher, aber das ist keineswegs der Fall.


Peter

Autor: Spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi

X, Y und Z sind Namem für zusammengefasste Registerpaare r26:r27, 
r28:r29 und r30:r31. Die Verwendung als Pointer resultiert in den 
Adressierungsarten für  die Loadbefehle 'ld/ldd' und den Zugriff per 
'lpm' auf den Flashspeicher. Ansonsten sind das ganz stinknormale 
Register wie r16,r17... . Definiert sind die in den entsprechenden 
'XYZdef.inc-Dateien'. D.h. du brauchst da nichts selbst definieren. Auf 
die einzelnen 8-Bit-Register kannst du auch mit beispielsweise XL(r26) 
oder XH(r27) zugreifen.
Für deine Zwecke würden sich die Befehle adiw/sbiw anbieten. Mit diesen 
Befehlen lassen sich von X, Y, Z sowie r24:r25 0..63 addieren bzw. 
subtrahieren.
Die obere Grenze kannst du wie Johannes oben beschrieben hat erkennen. 
Null kann z.b. so getestet werden
  
  push XL
  or XL,XH
  pop XL
  breq... oder brne...

@Johannes sbc r17,0/adc r17,0 gibt es nicht. Geht nur mit Registern.

MfG Spess

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Spess53 wrote:
> @Johannes sbc r17,0/adc r17,0 gibt es nicht. Geht nur mit Registern.
Jau, hab ich auch grad gemerkt. Und mit dec und inc gehts übrigens 
natürlich auch nicht, weil die das Carry nicht beeinflussen. Muss also 
korrekt(er) heißen:
;Inkrementieren 16 Bit:
clr r19       ;Register mit 0 vorbelegen
subi r16, -1  ;Low-Byte inkrementieren
adc r17, r19  ;High-Byte plus Carry

;Dekrementieren 16 Bit:
clr r19       ;Register mit 0 vorbelegen
subi r16, 1   ;Low-Byte dekrementieren
sbc r17, r19  ;High-Byte minus Carry
Sorry, war meine Blödheit...

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
so, falls es noch jemanden interessier (andere anfänger?) ich habs jetzt 
einfach so gemacht:

ich zähle das eine register bis 99 hoch, dann fängt es wieder von vorne 
an und ein zweites register ist auf 01 gesetzt. bei der nächsten runde 
wird das zweite register dann auch 02 usw. das lässt sich dann einfach 
in zwei teilen im display darstellen und ich kann damit den timer dann 
auch ablaufen lassen, wenn ich die verstrichene zeit des zweiten 
registers ainfach mal hundert nehme.

denke das sind basics die den meisten hier bekannt sind aber ich musste 
das erstmal so rausfinden und bin damit auch ganz zufrieden. für andere 
rechenoperationen eignet sich das konstrukt dann vielleicht nicht mehr 
aber für meinen fall ist das ideal

auf jeden fall danke ans forum

matze

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Schön, wenns läuft. Damit hast Du jetzt allerdings das, was Du 
eigentlich vermeiden wolltest (nämlich eine ständige Abfrage, ob der 
Wert im Register "überläuft"), wieder drin, hast allerdings den Vorteil, 
dass Du vor der Ausgabe nicht mehr großartig umrechnen musst (wenn Dir 
das wichrtiger ist...).

Aber ich denke, Du wirst das, wenn Du tiefer in die Materie einsteigst, 
auch anders machen. Es ist nur wichtig zu wissen (und das führte 
letztendlich zu dem "Missverständnis" mit Niels), dass ein Controller 
nur binär arbeiten kann, und alles, was in Zahlensystemen gerechnet 
werden soll, die nicht direkt ins Binärsystem umschreibbar sind (also 
deren Basis keine Potenz von 2 ist), ziemlich aufwändig ist. Bei Deiner 
Version musst Du jetzt das erste Register inkrementieren, dann eine 
Abfrage machen, ob 99 überschritten ist, falls ja, das Register zu Null 
setzen und das andere inkrementieren. Im Gegensatz dazu ist die 
Binär-Variante mit genau zwei Befehlen (und zwei Taktzyklen) deutlich 
effizienter (was allerdings, wie Peter schon richtig sagte, zumindest 
bei Tasten eher unkritisch ist, weil da die Auswertung sowieso i.d.R. 
viel schneller ist als die Ereignisfolge).

Anmerkung:
Bei der Dekrementierung hatte ich noch was vergessen. Geht noch 
einfacher, weils bei der Subtraktion einen "Subtract Immediate With 
Carry"-Befehl gibt und man sich so das leere Register sparen kann:
subi r16, 1
sbci r17, 0
Aber das nur am Rande. Hab mal wieder gemerkt, dass ich mich viel zu 
lange nicht intensiv genug mit Assembler befasst habe. Wird mal wieder 
Zeit...

Autor: guido (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mal ne blöde Frage:

ADIW und SUBIW machen doch Wordarithmetik.
Oder verstehe ich da was falsch?

guido

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ guido (Gast)

>ADIW und SUBIW machen doch Wordarithmetik.
>Oder verstehe ich da was falsch?

Das siehst du richtig.

MFG
Falk

Autor: Sven (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi Leute :-D

Bin grad zufällig beim stöbern hier auf diesen Thread gestoßen. Ich 
befasse mich seit ca. nem halben Jahr mit der ASM Programmierung und 
genau mit diesem Problem hier war ich ebenfalls konfrontiert. Falls es 
mal jemand brauchen sollte. :-)

Hab das bisher immer so gelöst:

.equ c1 = 10000 ;Zählerkonstante festlegen
.
bla bla bla Eingang/Ausgang Programmcode usw....
.
.
.
          ldi r25, HIGH(c1) ;Konstante in Register H/L
          ldi r24, LOW(c1)
Loop1:
          sbiw r24, 1 ;Registerwert um 1 verringern
          nop
          nop
          nop
          nop
          brne Loop1 ;wenn null dann weiter sonst wieder zu Loop1
.
Programmcode

Funktioniert super für diverse Zeitverzögerungen oder für 
Tasterentprellung. Hier kann bis max. 65536 gezählt werden. Noch ein 
paar "nop"s dazwischen und das ganze vermehrfacht sich dann 
dementsprechend (hier x4). Die "nop"s kann man natürlich auch weglassen, 
ist nur ein Beispiel! Gibt sicher auch andere Lösungen, aber ich hab mir 
bisher alles selbst mit Internet-Lektüre beigebracht. Auf die 
komplizierten Lösungen sollte man als Anfänger denk ich erst mal 
verzichten, da man sich schnell in was verzetteln kann. Lieber einfach 
und solide, dafür lernt man die Funktion des µC besser kennen find ich.

Gruss Sven

Autor: Sam .. (sam1994)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Kleiner Tipp: 2x nop == rjmp pc+1

Autor: Sven (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Kleiner Tipp: 2x nop == rjmp pc+1

Was bedeutet hier rjmp pc+1

rjmp ist klar aber was macht er mit == pc+1 ??

Autor: Ein (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

ich würde das in etwa so schreiben.


(nicht getestet)

ldi r16,low(1000)
ldi r17,high(1000)


clr xl
clr xh

up:
cp xl,r16
cpc xh,r17
breq _1000erreicht
adiw x,1
...

down:
tst xl
brne decx
tst xh
brne decx
...


decx:
sbiw x,1


Autor: Sam .. (sam1994)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
pc+1 beschreibt die Adresse des folgenden Befehls. Man könnte es auch so 
schreiben:

rjmp L1
L1:
...

Autor: Ein (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Johannes M. schrieb:
> inc r16     ;Low-Byte inkrementieren
> adc r17, 0  ;High-Byte plus Carry

Seit wann setzt inc das Carryflag ?

Niels Hüsken schrieb:
> Aber als Tipp für die Zukunft solltest du dir angewöhnen gleich in Hex
> zu arbeiten und erst im letzten Schritt nach Dezimal umrechnen

Das halte ich für Quatsch. Wenn ich Dezimal bis 1000 zählen will, 
schreibe ich auch 1000 hin, umrechnen lass ich das vom Assembler.

Autor: Sven (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>pc+1 beschreibt die Adresse des folgenden Befehls. Man könnte es auch so
>schreiben:

>rjmp L1
>L1:

Ok...kapiert! :-)

da fällt mir grad noch was ein....ich meine mal irgendwo in den 
unendlichen Weiten des I-Net´s gelesen zu haben, dass man das HIGH 
Register zuerst laden soll, ist das richtig? Oder bezieht sich dass nur 
auf den Stack beim initialisieren?

z.B. so...

ldi r25, HIGH(1000)
ldi r24, LOW(1000)

oder....

ldi r24, LOW(1000)
ldi r25, HIGH(1000)

oder ist das schlicht egal?

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sven schrieb:

> da fällt mir grad noch was ein....ich meine mal irgendwo in den
> unendlichen Weiten des I-Net´s gelesen zu haben, dass man das HIGH
> Register zuerst laden soll, ist das richtig?

Das ist richtig.
Genauso umgekehrt: Erst das Low Register auslesen, dann erst das High

Der Grund dafür ist einfach: Bei einigen Register-Pärchen gibt es eine 
Logik im µC, die die Register verriegelt. Zb die ADC Register ADCH, 
ADCL.
Durch den Lesezugriff auf ADCL wird das ADC-Werte Registerpärchen gegen 
Veränderungen verriegelt. Erst der Lesezugriff auf ADCH hebt diese 
Verriegelung wieder auf. Dieser Mechanismus soll sicher stellen, dass 
die INhalte im H und L Register auch wirklich von derselben Messung 
stammen. Sonst könnte es passieren, dass du das eine Register ausliest 
und just in diesem Moment ist der ACD zb im Free Running Modus mit einer 
neuen Messung fertig und beschreibt die Register neu. Liest du dann das 
andere Register aus, dann hast du 1 byte von der alten Messung und das 
andere Byte von der neuen Messung.

Am einfachsten ist es daher sich einfach fix an diese Reihenfolge zu 
halten, selbst wenn sie bei einem Registerpärchen nicht notwendig wäre.

Autor: Sven (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Na dann ist gut...leuchtet auch ein ;-))

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.