Forum: Mikrocontroller und Digitale Elektronik Serial Communiction uint8_t* Arduino


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von René (ren_r)


Lesenswert?

Hallo,

ich versuche jetzt schon seit einigen Tagen in der ArduinoIDE einen 
vernünftigen Workaround zu schaffen, mit dem ich uint8_t AT-Commands via 
UART senden kann.
Ich habe das schon einige Mal mit FreeErtos gemacht, leider bekomme ich 
es aber in der ArduinoIDE nicht hin.

Also serielles senden und empfangen eines uint8_t Array via UART.
Und die Konvertierung in beiden Richtungen in Arduino::String klasse.

Kann mir diesbezüglich jemand weiterhelfen.

Vielen lieben Dank.

René

: Bearbeitet durch User
von STK500-Besitzer (Gast)


Lesenswert?

Serial.print
, Serial.println
und das andere Geraffel zum Thema "Serial".
uint8_t* ist bei AT-Kommandos fehl am Platz.

von René (ren_r)


Lesenswert?

> uint8_t* ist bei AT-Kommandos fehl am Platz.
Nicht immer bei meinen Modul werden uint8_t Zeichen erwartet und müssen 
auch gesendet werden.

Was hat uint8_t mit AT-Kommandos zu tun. AT ist nur ein Syntax soweit 
ich das Verstehe. Ich kann auch gerne mal einen Auszug eines RTOS 
Programms welches ich mal geschrieben habe Posten.
1
int mqtt_clean(mqtt_connect_configuration mqtt_connect_configuration)
2
{
3
  char *msg_mqtt_clean = (char*) malloc(sizeof(char)*21);
4
    if(msg_mqtt_clean == NULL)
5
  {
6
      return EXIT_ERROR;
7
  }
8
    sprintf(msg_mqtt_clean,"AT+MQTTCLEAN=%d\r\n",mqtt_connect_configuration.LinkID);
9
  HAL_UART_Transmit(&huart1, (uint8_t*)msg_mqtt_clean, strlen(msg_mqtt_clean), HAL_MAX_DELAY);
10
  HAL_UART_Receive(&huart1,(uint8_t*)RX_Buffer,TX_BUFFER_SIZE,TX_TIMEOUT_DURATION);
11
  if (strstr(RX_Buffer,"OK") != NULL)
12
  {
13
    uart_handler_tx_message("MQTT Close MQTT Connection -> Was successful.",UART_HANDLER_MSG_TYP_STATUS,__FILE__,__LINE__);
14
  }
15
  else
16
  {
17
    uart_handler_tx_message("MQTT Close MQTT Connection -> Was not successful.",UART_HANDLER_MSG_TYP_ERR,__FILE__,__LINE__);
18
    free(msg_mqtt_clean);
19
    return EXIT_FAILURE;
20
  }
21
  free(msg_mqtt_clean);
22
  memset(RX_Buffer,0,strlen(RX_Buffer));
23
  return EXIT_SUCCESS;
24
}
Sowas Ähnliches möchte ich auch in der ArduinoIDE auf sinnvollerweise 
implementieren. Nur das es dieses Mal um BLE geht.
Ich versuche eine serielle Kommunikation zum NINA B312 herzustellen und 
verwende als Enticklungsboard für meinen Brototypen Mikroe BLE 4 click.

René

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

René schrieb:
> Ich kann auch gerne mal einen Auszug eines RTOS
> Programms Posten.

Man muß nicht gleich mit Kanonen auf Spatzen schießen.
Wozu das umständliche malloc/free?
Es reicht einfach ein Array von 21 Byte anzulegen. Das wird automatisch 
beim Verlassen der Funktion wieder freigegeben. Und es kann auch nicht 
fehlschlagen.

Und da ja auf dem Arduino kein RTOS läuft, würde ich sowas eh nicht 
blockierend programmieren.

von René (ren_r)


Lesenswert?

> Man muß nicht gleich mit Kanonen auf Spatzen schießen.
> Wozu das umständliche malloc/free?
Hat manche Funktionen gegeben dier mehrere sachen gleichzeitig machen 
und wir hatten ein heap problem. Save ist Save.

Mich würde interessieren, wie ich sowas Ähnliches im Arduino Umfeld 
lösen könnte. Mir ist im Klaren, dass hier unterschiede gegeben sind.
Ich wollte nur auf den vorhergehenden Kommentar eingehen.

Hätte jemand ein Beispiel?

Ich vermute mal: Ich bin nicht der einzige der ein Board bzw. Modul mit 
dem Arduino seriell betreibt, welches die Zeichen Byte weise liest und 
schreibt.

LG
René

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

René schrieb:
> Ich bin nicht der einzige der ein Board bzw. Modul mit
> dem Arduino seriell betreibt, welches die Zeichen Byte weise liest und
> schreibt.

Typisch benutzt man für die UART je einen FIFO für TXD und RXD, die dann 
im Interrupt gesendet bzw. gefüllt werden.
Das Main kann also ohne Warten eine Nachricht in den FIFO schreiben. Aus 
dem RX-FIFO sammelt es dann die Bytes in einen Puffer, bis das 
Zeilenende erkannt wird und parst es dann.
Man sendet also ein Paket und parst das empfangene Paket.

von Forist (Gast)


Lesenswert?

René schrieb:
> Save ist Save.

Mit Fremdsprachen soll man vorsichtig sein - insbesondere, wenn man 
nicht damit umgehen kann.

Was genau willst du sparen?

von EAF (Gast)


Lesenswert?

STK500-Besitzer schrieb:
> Serial.print
> , Serial.println
Leist denn heute keiner mehr die Dokus?
Niemand mehr?
Auch nicht die Fragen?
Mit print und println kann man vieles ausgeben.
aber...


STK500-Besitzer schrieb:
> und das andere Geraffel zum Thema "Serial".
> uint8_t* ist bei AT-Kommandos fehl am Platz.

Naja...
Serial.write(buf, len)
Das ist dafür gemacht um uint8_t Arrays auszugeben


https://www.arduino.cc/reference/de/language/functions/communication/serial/write/

von EAF (Gast)


Lesenswert?

Peter D. schrieb:
> Typisch benutzt man für die UART je einen FIFO für TXD und RXD, die dann
> im Interrupt gesendet bzw. gefüllt werden.

Öhm...
Genau das tut Serial

von J. S. (jojos)


Lesenswert?

was hier wieder alles durcheinander geworfen wird...

René schrieb:
> in der ArduinoIDE einen

Die ArduinoIDE ist die (spartanische) Entwicklungsumgebung, die ist 
nicht das API das du für eine jeweilige Zielhardware brauchst.
FreeRTOS hat auch keine Devicetreiber, dein Beispiel nutzt Funktionen 
aus der STM32 HAL.

Peter D. schrieb:
> Und da ja auf dem Arduino kein RTOS läuft, würde ich sowas eh nicht
> blockierend programmieren.

Sehr falsch, beim ESP ist FreeRTOS im Kern eingebaut, und auch für den 
ESP gibt es das Arduino Framework. Und auch wenn FreeRTOS nicht im Core 
ist, kann man es einbauen. Auch für die AVR, nur macht es da keinen Sinn 
wg. wenig RAM.

Hier geht es aber offensichtlich um einen fetten Cortex-M7 mit reichlich 
Resourcen.
Da kommt es jetzt drauf welcher Arduino Core verwendet wird. Wenn es ein 
STM32Duino ist, dann baut der auf dem STM32 HAL auf und die UART 
Funktionen können genauso verwendet werden. Arduino krallt sich da 
allerdings gerne die Interruptvektoren, für die Interrupt getriebenen 
Funktionen muss dieser dann zurückgebogen werden.

Und bei so einem Projekt würde ich PlatformIO verwenden, das bietet da 
wesentlich mehr Komfort.

von René (ren_r)


Lesenswert?

> Naja...
> Serial.write(buf, len)
> Das ist dafür gemacht um uint8_t Arrays auszugeben
1
  if (Serial1.available()==0) {
2
    Serial1.write((uint8_t*)"AT+UBTLN=\"u-blox NINA René\"", strlen("AT+UBTLN=\"u-blox NINA René\""));
3
  }

Ja, das funktioniert bei mir, ich schaffe es das mein Modul reagiert.
Jedoch stelle sich für mich nun 2 Fragen:

Wie kann ich die Antwort danach lesen und wie kann ich diese nachher in 
einen Ardoino::String konvertieren?

Ja, du hast absolut recht:
von J. S. (jojos)

Ich möchte es auch früher oder später auf PlatformIO umsetzen. Jedoch 
vorerst auf der Ardoino Plattform, um zu testen, ob ich mit der 
Rechenleistung, sprich mit der Zeit der Berechnung auskomme. Da meinen 
yC ein UWD und ein BLE Sensor verbunden werden und ich damit TOA und 
RSSI von mehreren Notes messen möchte. Ja ich weiß, ich könnte auch den 
B312 als zentrale yC verwenden, aber dann habe ich nicht mehr die 
Möglichkeit die Module einfach und kompakt untereinander zu vergleichen. 
(Verschiedene Antennen ... verschiedene Chips).
Die Arduino Plattform ermöglicht es mir eine raschen Mikrochip wechsel 
ohne grobe Code Änderungen.

Normalerweise arbeite ich mit Microchip-Studio , Ertos oder PlatformIO.

Ich poste anschließend auch gerne eine komplette Funktionsbibliothek, 
welche die NINA Chips abdeckt.

Also so einfach wie möglich.
Alles andere nimmt e schon riesige ausmaße.
Nach den ganzen Test kann ich es in eine Richtige IDE überführen.

LG René

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

@ TO:
Hast du ein Übertragungsprotokoll? Ohne dem geht nichts.
Hast du einen Lesebuffer der vom Serial FIFO befüllt wird?
Hast du einen Schreibbuffer mit dem der Serial FIFO befüllt wird?

von EAF (Gast)


Lesenswert?

René schrieb:
> Ja, das funktioniert bei mir,
Was du da ausgibst, ist kein uin8_t Array , sondern ein char Array
Also liegt der Fehler schon in der Frage!

Richtig wäre damit:
Serial1.print("AT+UBTLN=\"u-blox NINA René\"");
oder eben println, wenn es ein Zeilenende benötigt

é ist ein utf8 Dingen?!?
Dann ist strlen() irgendwie komisch, denn das erkennt keine UTF8 Dinger

von Axel S. (a-za-z0-9)


Lesenswert?

EAF schrieb:
> René schrieb:
>> Ja, das funktioniert bei mir,
> Was du da ausgibst, ist kein uin8_t Array , sondern ein char Array
> Also liegt der Fehler schon in der Frage!

So ist es. Auf den allermeisten Platformen ist uint8_t einfach nur ein 
Alias für unsigned char. Und ein old school "String" in C ist ein 
Array derselben bzw. ein Pointer darauf. Dementsprechend kann man die 
Konvertierunsfunktionen jeglicher String-Klasse benutzen.

> é ist ein utf8 Dingen?!?
> Dann ist strlen() irgendwie komisch, denn das erkennt keine UTF8 Dinger

Es ist kompatibel. Mehrbyte-Sequenzen a'la UTF-8 werden einfach als die 
entsprechende Anzahl Zeichen gerechnet. Dös baschd scho!

von W.S. (Gast)


Lesenswert?

René schrieb:
> uint8_t AT-Commands via UART

Klassischerweise sendet oder empfängt man über UART Zeichen, also im 
Grunde Textzeichen, wofür eigentlich 'char' der richtige Datentyp ist. 
Rein physisch bestehen die herkömmlicherweise aus einem Byte, wobei bei 
ASCII das MSB nicht benutzt ist. Deine Ausführungen zeigen eigentlich 
nur eine erhebliche Oberflächlichkeit. AT-Kommandos sind blanker Text, 
aber keine uint8_t (also unsigned char). Und falls du in deiner Firmware 
einen ordentlichen Treiber für den UART hast, dann sendet und empfängt 
der die Zeichen auch ordentlich über den UART. Dabei ist es herzlich 
egal, was für ein Bitmuster da in einem Byte übergeben werden. 
Allenfalls kann es einem möglicherweise passieren, daß das MSB gelöscht 
wird, um dem Wertebereich von ASCII zu entsprechen. Aber das ist 
unerheblich für AT-Kommandos und heutzutage recht unüblich geworden.

Dei Problem ist wohl, daß du ein Array senden/empfangen willst. Bedenke 
mal, daß der serielle Transport über UART nicht arrayweise, sondern 
zeichenweise erfolgt. Also richte dich danach und erwarte nicht, daß der 
serielle Treiber dir die Arbeit abnimmt. Und der Rest deiner Probleme 
ist wohl, daß du mit den diversen Automatismen von C nicht klarkommst. 
So zum Beispiel mit dem Backslash in Zeichenketten. Das hat aber weder 
mit dem Editor noch mit einem RTOS noch mit einer IDE zu tun.

Forist schrieb:
> Was genau willst du sparen?

Scherzbold.
Frag ihn lieber "do you speak british?"
und erwart die Antwort "yes, a paar broken"

W.S.

von Émile (Gast)


Lesenswert?

Axel S. schrieb:
> Und ein old school "String" in C ist ein
> Array derselben bzw. ein Pointer darauf.

Ein C-String ist ein Array aus char, nicht ein Array aus unsigned char.

Ob char selbst wiederum signed oder unsigned ist, ist nicht festgelegt 
(bzw. kann als Compileroption spezifiziert werden).

von EAF (Gast)


Lesenswert?

Axel S. schrieb:
> Dös baschd scho!

Zufall, Glück, oder was auch Immer....
Hier geht es gerade mal gut.
Aber im nächsten unbedarften Augenblick kann das ins Auge gehen.
Dann, ist das ein schwer zu findender Fehler, da man sich sicher ist, 
dass das schon mal so (oder so ähnlich) geklappt hat.

sizeof()
strlen()
mblen()

Ich rate da zu ein klein wenig Aufmerksamkeit/Sorgfalt.



Émile schrieb:
> Ob char selbst wiederum signed oder unsigned ist, ist nicht festgelegt
So ist es, wenn ich meinen AVR-GCC frage, dann antwortet er auf beide 
Fragen is_signed() is_unsigned() mit false

von René (ren_r)


Lesenswert?

Absolut Richtig.

Komme noch von der Alten Schule.

Jedoch zurück zu meiner Frage. Gibt es einen einfachen Weg in der 
Arduinobibliothek, welche mir einen *"unsigned char []" oder "uint8_t 
char []" von UART lesen kann* und eine gute Methode um diesen danach 
wieder in einen *Arduin::String Objekt konvertieren* kann.

Liebe Grüße
René Ramsauer

von EAF (Gast)


Lesenswert?

René schrieb:
> um diesen danach
> wieder in einen *Arduin::String Objekt konvertieren* kann.
Du konvertierst zuviel!
Damit legst du dir selber Steine in den Weg.

Unter Vorbehalt:
Serial.readString()
Beachte dabei den Timeout

von W.S. (Gast)


Lesenswert?

René schrieb:
> Jedoch zurück zu meiner Frage. Gibt es einen einfachen Weg in der
> Arduinobibliothek, welche mir einen *"unsigned char []" oder "uint8_t
> char []" von UART lesen kann* und eine gute Methode um diesen danach
> wieder in einen *Arduin::String Objekt konvertieren* kann.

Ich glaub, ich krieg bei sowas Pickel.

Also, eben noch habe ich versucht, dir zu erklären, daß du dich um's 
Block machen selbst kümmern solltest und nun willst du einen char[] vom 
Treiber haben. Mal ganz abgesehen davon, daß ein char[ohne anzahl] hier 
fehl am Platze ist.

So, hoffentlich gibt's keine Rüge vom Moderator für so ein Stück 
Beipiel. Die Zeichen werden einzeln abgeholt und zur Kommandozeile 
zusammengesetzt. Ein bissel Primitiv-Editieren ist auch dabei, falls man 
sich mal vertippt hat:
1
  /* Kommandozeile hereinholen */
2
void Talk (word wo)
3
{ char c;
4
5
  if (RxAvail(wo))
6
  { c =  GetChar(wo);
7
    if (c==10) return;
8
    if (c==13)
9
    { CRLF_Out(wo);
10
      DoCommand (Kommandozeile, wo);
11
      InitCmd(wo);
12
    }
13
    else
14
    { if (kzi>=(sizeof(Kommandozeile)-1)) return;
15
      if (c==8)
16
      { Char_Out(8, wo);
17
        Char_Out(' ', wo);
18
        Char_Out(8, wo);
19
        if (kzi) --kzi;
20
        Kommandozeile[kzi]=0;
21
      }
22
      else
23
      { if (c<' ')
24
        { Char_Out('^', wo);
25
          Char_Out(c+0x40, wo);
26
          Kommandozeile[kzi++] = c;
27
          Kommandozeile[kzi] = 0;
28
    return;
29
        }
30
        Char_Out(c, wo);
31
        Kommandozeile[kzi++] = c;
32
        Kommandozeile[kzi] = 0;
33
      }
34
    }
35
  }
36
}
Das stammt aus einem uralten Beispiel, was ich vor etwa 5 Jhren hier mal 
gepostet hatte.

W.S.

von René (ren_r)


Lesenswert?

Ich lasse hirmit die Frage Fallen und melde mich sobalt ich einen 
Workeround gefunden habe.

Zu deinen Code:
Es ist mir auch im klaren wie ich das selber Implementieren kann.
Jedoch würde ich es anderst machen da das sauberer geht.

Nachdem es in der libary eine funktion giebt die einen String einlesen 
kann.
(im hintergrund zeichen für zeichen und über Pointer.)

Mir ist auch im klaren das die länge einen Rolle in einen char array hat 
jedoch nicht für eine saubere Funktion die würde die länge selber 
ermitteln.

Aber wie gesagt warum etwas selber impl wenn es eine Bibliothek giebt 
und ich kann es mir fast nicht vorstellen das es keine giebt.
Aber ja ansonsten werde ich es über eine Weilshleife Zeichen für Zeichen 
lesen und in einen String schreiben.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

welche Lib soll das sein die dir deine individuelle 
Zeichenverarbeitungsfunktion anbietet?
Das Arduino Framework hält für jede Serielle einen Ringbuffer bereit 
worin üblicherweise 63 Zeichen reinpassen. Darauf kann man mit 
read/write zugreifen. Die Zeichen muss man auslesen und verarbeiten. 
Dafür verwendet man üblicherweise einen eigenen char Buffer abgestimmt 
auf seine Aufgabe. Also auslesen und in eigenen Buffer schreiben. Wenn 
Übertragung fertig, Daten vom eigenen Buffer verarbeiten. Wenn fertig 
auf neue Daten warten und das Spiel beginnt von vorn.

von Émile (Gast)


Lesenswert?

W.S. schrieb:
> Ich glaub, ich krieg bei sowas Pickel.

Die bekommen wir hier auch, wenn Du weiterhin komplett ingnorierst, daß 
Arduinos in C++ programmiert werden und String eine Klasse ist, die 
einiges an Dir völlig unbekannter Funktionalität enthält.

von Stefan F. (stefanus)


Lesenswert?

René schrieb:
> Workeround

Oder vielleicht ein Würgaround?

Ein Workaround dient dazu, einen Fehler zu umgehen. Wenn z.B. dein PC 
abstürzt wenn du 100·2 rechnest, dann könnte ein Workaround sein, 
stattdessen 100+100 zu rechnen.

Dein NINA B312 ist offenbar ein Wifi/Bluetooth Modem, dass mittels AT 
Kommandos konfiguriert wird. Das hat mit Byte Arrays erst mal wenig zu 
tun. AT Kommandos sind Strings. Nach dem Verbindungsaufbau überträgt es 
alle Daten 1:1 so wie du sie seriell ausgibt. Das ist der Moment, wo 
dein eigenes Übertragungsprotokoll wichtig wird.

Du musst dir ein Übertragungsprotokoll ausdenken. Es ist leicht, über 
einen Seriellen Port eine Folge von Bytes zu senden. Aber woher soll der 
Empfänger wissen, wo genau diese Folge von Bytes beginnt und wo sie 
endet? Darüber musst du dir Gedanken machen.

Du sendest ja letztendlich nicht nur einmal diese eine Folge von Bytes 
und beendest dann das Programm. Außerdem können Übertragungsstörungen 
auftreten, von denen sich der Empfänger sinnvollerweise erholen können 
muss. Es wäre schlecht, wenn er nach einem verlorenen oder falschen Byte 
für immer nur noch Quatsch macht.

Ein einfacher Klassiker ist, die Daten mit Serial.printf() in Text 
umzuwandeln, zu gruppieren und Zeilenweise zu senden. Zum Beispiel:

Temperaturen:20;19,5;20;21<Zeilenumbruch>

Der Empfänger kann am Zeilenumbruch das Ende eines Datenpaketes 
erkennen. Die Arduino Funktion Serial.readString() ist dazu gut 
geeignet, wenn du vorher einen Timeout festlegst. Anschließend kannst du 
die Zeile mit String Funktionen in ihre Bestandteile zerlegen.

von EAF (Gast)


Lesenswert?

Falls es einen interessiert....

Hier mal ein primitiver Bufferfüller/Parser in Arduino.
1
#include <Streaming.h> // die Lib findest du selber ;-)
2
Print &cout = Serial; // cout Emulation für "Arme"
3
4
5
String buffer;
6
7
void verarbeitung(String &str)
8
{
9
  // hier tuwas mit str
10
  cout << str << endl;
11
}
12
13
void serialEvent()
14
{
15
  char c = Serial.read();
16
  switch(c)
17
  {
18
    case '\n': /* fall through */
19
    case '\r': if(buffer.length())  verarbeitung(buffer);
20
               buffer = "";
21
               break;
22
    default: buffer += c;            
23
  }
24
}
25
26
void setup() 
27
{
28
  buffer.reserve(50); // buffer mindestgröße
29
  Serial.begin(9600);
30
  cout << F("Start: ") << F(__FILE__) << endl;
31
}
32
33
void loop() {}

von Axel S. (a-za-z0-9)


Lesenswert?

Émile schrieb:
> Axel S. schrieb:
>> Und ein old school "String" in C ist ein
>> Array derselben bzw. ein Pointer darauf.
>
> Ein C-String ist ein Array aus char, nicht ein Array aus unsigned char.

Albern!

> Ob char selbst wiederum signed oder unsigned ist, ist nicht festgelegt

Eben. Es spielt für die Anwendung des Typs als Zeichen auch gar keine 
Rolle. Signedness ist nur im Kontext von Arithmetik relevant. Oder für 
Vergleiche, die über die Unterscheidung gleich/ungleich hinaus gehen.

Aber das ist ja überhaupt nicht der Fall. Du hast nur eine verk*ckte 
Funktion, die darauf besteht, daß "ihre" Strings ein Array von uint8_t 
sein müßten. Wenn du daran hängst, dann caste halt.

Aber wenn du schon eine Programmiersprache (C++) mit richtigen Strings 
verwenden willst, dann ist das sicherlich der falsche Weg. Millionen von 
C-Programmierern haben ebensoviele Bugs in ihren C-Programmen eingebaut, 
bloß weil die Sprache über keinen Typ für Strings verfügt, sondern statt 
dessen char* als solche mißbraucht. Da sollte man nicht auf Teufel komm 
raus dran festhalten. Sondern alte Zöpfe auch mal abschneiden.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

René schrieb:
> Nachdem es in der libary eine funktion giebt die einen String einlesen
> kann.

Solch eine Funktion gibt es schlichtweg nicht, da es keinen String als 
Protokoll gibt.
Es gibt Funktionen, die Bytes über die UART senden oder empfangen. Diese 
Bytes können Binärdaten, Text oder sonstwas sein. Daher werden die Bytes 
nicht interpretiert, sondern nur in FIFOs gepuffert, falls die CPU noch 
anderes zu tun hat, damit keine Bytes verloren gehen.

Du brauchst daher in jedem Fall noch ein Protokoll, das den Bytestrom 
zusammen setzt bzw. wieder interpretiert.
Ein Timeout ist mit Abstand das schlechteste Protokoll.

Ein sehr einfaches Protokoll ist eine Textzeile, d.h. ein Paket wird mit 
CR, LF oder beidem abgeschlossen. Du brauchst also eine Funktion, die 
die Bytes in ein Array sammelt und das CR,LF durch '\0' ersetzt. Fertig 
ist ein String, den man z.B. mit sscanf parsen kann.
Als Fehlerbehandlung kann man noch ein Timeout aufsetzen, was dann die 
Verbindung als gestört meldet. Das Timeout ist aber nicht Bestandteil 
des Protokolls.

Daneben gibt es noch haufenweise Binärprotokolle.

von J. S. (jojos)


Lesenswert?

das Protokoll ist gegeben, AT Kommandos mit CR+LF als Endekennung. Damit 
reicht das eingebaute Serial.readStringUntil('\n'). Und durch das 
Timeout kann auch ein Fehler durch unvollständige Sendungen abgefangen 
werden. Komplizierter wird es nur wenn das Modem auf Daten umschalten 
kann und dann auch ein LF im Datenstrom vorkommen kann.
Der TO muss nur nachsehen ob für die gewünschte Schnittstelle eine 
Serial_n im core als globales Objekt angelegt wurde, sonst kann man das 
mit einer Instanz von HardwareSerial selber machen.

von Peter D. (peda)


Lesenswert?

J. S. schrieb:
> Damit
> reicht das eingebaute Serial.readStringUntil('\n').

Ich mag solche blockierenden Funktion nicht, sondern programmiere sowas 
nebenläufig. D.h. die Funktion zum Sammeln der Bytes wird zyklisch 
aufgerufen. Und erst, wenn die Funktion eine Zeile erkannt hat, wird 
diese dem Parser übergeben. Die CPU kann derweil andere Sachen ausführen 
und muß nicht an dieser Stelle Däumchen drehen.

Als Master kann man ein Timeout aufsetzen, um eine Störung zu erkennen. 
Der Slave hingegen braucht kein Timeout. Er wartet geduldig (jahrelang), 
bis ihm der Master was sagt und antwortet dann. Warten heißt, der Slave 
macht alles andere, was er tun soll.

von J. S. (jojos)


Lesenswert?

deshalb benutze ich Arduino auch nicht, komplexere Anwendungen machen 
mit dem simplen API keinen Spaß. Aber die Frage war ja wie es damit 
geht.
Bei Targets mit STM kann man auf die asynchronen HAL Funktionen 
zugreifen, verliert dann aber die Portabilität die der TO auch haben 
wollte.
Dann gäbe es noch OS wie NuttX oder ChibiOS, in die muss man sich aber 
auch erstmal einarbeiten und man muss hoffen das seine Plattform 
unterstützt wird und die Implementierung auch funktioniert.

von Peter D. (peda)


Lesenswert?

René hat bisher noch nicht gesagt, welchen konkreten Arduino er 
überhaupt benutzt.

von EAF (Gast)


Lesenswert?

J. S. schrieb:
> deshalb benutze ich Arduino auch nicht,
Das ist dir ungenommen!
Deine Argumentation ist allerdings defekt.

J. S. schrieb:
> komplexere Anwendungen machen
> mit dem simplen API keinen Spaß.
Aha...
Die Ganze Welt des C++, C und ASM steht dir doch damit offen.
Niemand zwingt einen, sich auf das Arduino Framework zu beschränken.
Das sind selbst auferlegte, willkürliche, Grenzen im Kopf.
Darum sind sie auch nicht als Argument gegen Arduino gültig.

EAF schrieb:
> Hier mal ein primitiver Bufferfüller/Parser in Arduino.
Das ist "fast" so ein Datansammler, wie von Peda beschrieben.
Sammelt die Daten nebenläufig.
Einen Timeout hat es allerdings (noch) nicht.

von EAF (Gast)


Lesenswert?

Peter D. schrieb:
> René hat bisher noch nicht gesagt, welchen konkreten Arduino er
> überhaupt benutzt.

Mein Vorschlag funktioniert mit allen Arduinos und Arduino fähiger 
Hardware, welcher HardwareSerial (uart) nutzt.
Alle!
Von 8 Bit über 32 Bit bis 64 Bit Kesselchen.
Egal welcher µC Type/Hersteller.

von J. S. (jojos)


Lesenswert?

EAF schrieb:
> Das sind selbst auferlegte, willkürliche, Grenzen im Kopf.

Nein, die Portabilität ist die Grenze. Es ist sicher vieles machbar, 
aber dann sehr aufwändig wenn es für mehrere Platformen implementiert 
sein soll. Wenn man die Eigenheiten der STM nutzt, dann läuft es nicht 
auf Xtensa und umgekehrt. Das ist immer ein Abwägen ob es schnell 
funktionieren soll oder ob es möglichst plattformunabhängig laufen soll.
In Serial und den darunterliegenden Streams sehe ich kein asynchrones 
API für den Benutzer. Aber vielleicht übersehe ich das ja auch nur.

edit:
ok, serialEvent() ist ein Ansatz.

EAF schrieb:
> Mein Vorschlag funktioniert mit allen Arduinos und Arduino fähiger
> Hardware, welcher HardwareSerial (uart) nutzt.
> Alle!

Dagegen sagt die Doku:
1
Notes and Warnings
2
serialEvent() doesn’t work on the Leonardo, Micro, or Yún.
3
serialEvent() and serialEvent1() don’t work on the Arduino SAMD Boards
4
serialEvent(), serialEvent1(), serialEvent2(), and serialEvent3() don’t work on the Arduino Due.

also nichtmal die Arduino Hardware ist da konsistent.

: Bearbeitet durch User
von J. S. (jojos)


Lesenswert?

Peter D. schrieb:
> René hat bisher noch nicht gesagt, welchen konkreten Arduino er
> überhaupt benutzt.

doch:

René schrieb:
> Ich versuche eine serielle Kommunikation zum NINA B312 herzustellen und
> verwende als Enticklungsboard für meinen Brototypen Mikroe BLE 4 click.

Ich kenne die allerdings nicht, hatte erst angenommen das wäre ein 
anderer Click 4 mit STM32F7.

: Bearbeitet durch User
von EAF (Gast)


Lesenswert?

J. S. schrieb:
> Dagegen sagt die Doku:
> Notes and Warnings
> serialEvent() doesn’t work on the Leonardo, Micro, or Yún.
> serialEvent() and serialEvent1() don’t work on the Arduino SAMD Boards
> serialEvent(), serialEvent1(), serialEvent2(), and serialEvent3() don’t
> work on the Arduino Due.
>
> also nichtmal die Arduino Hardware ist da konsistent.

Dann hast du mich nicht verstanden!
Oder du ziehst Argumente an den Haaren herbei, weil du recht behalten 
willst, unter Aufgabe jeglicher Ehre.
Ich sprach eindeutig und klar von HardwareSerial!
Die von dir aufgeführten verwenden Serial über USB auf dem µC.

Übrigens, der TO möchte nicht Serial über nativ USB nutzen.

Aber selbst dann ist ein Nachrüsten von serialEvent() nun wirklich nicht 
das Problem.
Eine einzige und dann noch einfache Zeile in loop().
Damit bist du überfordert?
Das kann ich mir nicht vorstellen!

J. S. schrieb:
> Nein, die Portabilität ist die Grenze. Es ist sicher vieles machbar,
> aber dann sehr aufwändig wenn es für mehrere Platformen implementiert
> sein soll. Wenn man die Eigenheiten der STM nutzt, dann läuft es nicht
> auf Xtensa und umgekehrt.

Das Problem dass unterschiedliche Hardware eben unterschiedlich ist, das 
wird man wohl nie aus der Welt schaffen können.
Deine STM32 HAL ist auch nur eine API, und die läuft auch nicht auf 
einem ESP o.ä., wie du schon ganz korrekt bemerkt hast.
Da ist das Arduino Framework schon deutlich universeller. Eine API zu 
entwickeln, welche ALLE Unterschiede der verschiedensten µC abdeckt ist 
schlicht unmöglich, aus meiner Sicht. Der Versuch ist zum scheitern 
verurteilt.

Und damit ist es immer recht aufwendig portablen Code zu erzeugen.
Ob mit, oder ohne Arduino.

von J. S. (jojos)


Lesenswert?

Serial ist eine Instanz von HardwareSerial.
Aber mit solchen Streitsüchtigen diskutiere ich nicht weiter. Falk hatte 
schon Recht.

von EAF (Gast)


Lesenswert?

J. S. schrieb:
> Serial ist eine Instanz von HardwareSerial.
Nein, nicht immer, nur wenn die/eine UART in Hardware genutzt wird.
Für µC mit USB Interface sieht das anders aus.

J. S. schrieb:
> Streitsüchtigen
Streitsüchtig?
Wie kommst du darauf?
Möchtest du die Unwahrheit hier unwidersprochen herausblähen wollen?

von EAF (Gast)


Lesenswert?

Eine Nachrüstung:
1
// serialEvent() Nachrüstung für ATMega32U4 USB CDC Serial (z.B. Leonardo/Micro)
2
// Entspricht weitestgehend der Implementierung wie bei HardwareSerial
3
4
5
void serialEvent() __attribute__((weak));
6
// void serialEvent(){} // implementieren, wenn gewünscht
7
8
void setup() 
9
{
10
  Serial.begin(9600);
11
} 
12
13
void loop() 
14
{
15
  if(serialEvent && Serial && Serial.available()) serialEvent();
16
}

Auf die "weiche" Deklaration kann natürlich verzichtet werden, wenn 
serialEvent wirklich implementiert wird.

von W.S. (Gast)


Lesenswert?

Émile schrieb:
> Die bekommen wir hier auch, wenn Du weiterhin komplett ingnorierst, daß
> Arduinos in C++ programmiert werden und String eine Klasse ist, die
> einiges an Dir völlig unbekannter Funktionalität enthält.

Was geht einen Treiber für einen UART eine C++ Klasse an? Natürlich 
garnichts. Die Übertragung erfolgt zeichenweise und wenn man mehrere 
Zeichen empfangen und auswerten will, dann muß man eben SELBER so 
viele sammeln, bis es einem reicht und anderweitige Kriterien 
(Protokoll) erfüllt sind.

Es war noch nie die Aufgabe eines Treibers, jemandem seine eigentlichen 
Arbeiten in den Algorithmen zu erledigen, sondern der Treiber soll bloß 
für einen geordneten Transport der betreffenden Daten sorgen. Und das 
eigentlich ganz ohne Bezug auf irgendeine spezielle Programmiersprache.

Lerne du mal lieber, strukturiert zu denken, anstatt nur als 
vermeintliches Totschlag-Argument "C++" zu sagen. Und wenn man meint, 
daß es allemal besser sei, für sein spezielles Vorhaben eine Quelle zum 
Kopieren zu suchen anstatt das, was das Spezielle am Vorhaben ausmacht, 
selbst zu schreiben, dann ist das eine falsche Ansicht und nur eine Art 
anzufragen "wer macht mir für lau meine Arbeit ?".

W.S.

von J. S. (jojos)


Lesenswert?

W.S. schrieb:
> Was geht einen Treiber für einen UART eine C++ Klasse an? Natürlich
> garnichts

Deine Ahnungslosigkeit ist wirklich grenzenlos.

von W.S. (Gast)


Lesenswert?

J. S. schrieb:
> edit:
> ok, serialEvent() ist ein Ansatz.

EAF schrieb:
> Ich sprach eindeutig und klar von HardwareSerial!

Ihr beiden streitet über des Kaisers Bart.
Mal ne Frage: Was soll eigentlich "SerialEvent()" oder gar 
"HardwareSerial"?
Wichtig für die Portabilität ist eigentlich, daß das Interface von 
Treibern dem Problem angepaßt ist und nicht einem bestimmten Chip oder 
Hersteller. Und ob nun der Zeichen-Verkehr über einen echten UART oder 
eine USB-Verbindung oder per reitendem Boten erfolgt, ist der 
aufrufenden Firmware eigentlich egal.

Für die Sendeseite sollte lediglich eine Funktion zum Entgegennehmen der 
Daten, zum Abfragen, ob der Kanal noch etwas aufnehmen kann (der 
Transport dauert ja auch) und zum Abfragen, ob bereits alles gesendet 
ist bereitstehen und für die Empfangsseite wären es eine Funktion zum 
Ermitteln, ob etwas da ist, eine zum Abholen dessen was da gekommen ist 
und ggf. noch eine, die angibt, ob es einen Empfangsfehler gegeben hat.

Da ist eigentlich kein Platz für 'SerialEvents' und 'HardwareSerial' 
oder ähnliches Zeug, das bloß irgend eine Bindung der Benutzer an die 
herstellende Firma bewirken soll.

W.S.

von J. S. (jojos)


Lesenswert?

W.S. schrieb:
> Da ist eigentlich kein Platz für 'SerialEvents' und 'HardwareSerial'
> oder ähnliches Zeug,

du schriebst mal in einem anderen Thread das du ein C++ Buch hast. Das 
sollte nicht dazu dienen den Monitor etwas höher zu stellen, da muss man 
mal reingucken.

W.S. schrieb:
> das bloß irgend eine Bindung der Benutzer an die
> herstellende Firma bewirken soll.

Paranoia.

In C++ hat eine Klasse ein Interface und eine Implementierung. 
HardwareSerial hat ein Inteface, einen Vertrag wie etwas aussehen soll. 
Die Implementierung kann und sieht für verschiedene Controller 
unterschiedlich aus. Also Perfekt auch für die Implementierung von 
Treibern.
serialEvent ist ein Callback, auch sowas macht Sinn.

Für den Rest bemühe dein Buch und versuche mal dich einen mm 
weiterzubilden.

von EAF (Gast)


Lesenswert?

W.S. schrieb:
> lieber, strukturiert zu denken, anstatt nur als
> vermeintliches Totschlag-Argument "C++"

Ohne jetzt den C++ Priester spielen zu wollen, aber C++ Jünger denken 
anders, als C Fans.
Alleine schon die OOP führt dazu, dass sich andere Denkstrukturen 
einstellen.

Die Prozedurale Denke, oder auch das hangeln an KontrollFlussDiagrammen, 
hindert oft mehr, als es nützlich ist.

von EAF (Gast)


Lesenswert?

W.S. schrieb:
> Ihr beiden streitet über des Kaisers Bart.

Wir sind schon fertig mit miteinander streiten, jetzt verbrüdern wir uns 
gerade und machen dich fertig. ;-)

von J. S. (jojos)


Lesenswert?

Na ganz so weit würde ich da in beiden Punkten nicht gehen, aber bei 
solchen komischen Ansichten kriege ich Pickel.
In C++ kann man Dinge unterschiedlich lösen und da darf man 
unterschiedlicher Meinung sein.

von EAF (Gast)


Lesenswert?

J. S. schrieb:
> In C++ kann man Dinge unterschiedlich lösen
Waren es 4 oder 5 Paradigmen, die C++ unterstützt?
Und C nur ein einzelnes?

von Günther (Gast)


Lesenswert?

Hat jemand von euch schon einmal Firmata zur Kommunikation benutzt?

https://docs.arduino.cc/hacking/software/FirmataLibrary

von Steve van de Grens (roehrmond)


Lesenswert?

W.S. schrieb:
> Was geht einen Treiber für einen UART eine C++ Klasse an?

Ich sehe keinen Grund, warum man Treiber nicht in C++ programmieren 
soll.

Sogar der Linux Kernel erlaubt inzwischen Treiber (Module) in C++.
https://olegkutkov.me/2019/11/10/cpp-in-linux-kernel/

von W.S. (Gast)


Lesenswert?

EAF schrieb:
> Wir sind schon fertig mit miteinander streiten, jetzt verbrüdern wir uns
> gerade und machen dich fertig.

Na grandios. Dann macht mal - aber vergeßt darüber nicht, daß es hier 
einen TO gibt, der mit seinem Latein am Ende ist, weil ihm keiner seine 
Zeichen einsammelt und nirgendwo etwas passendes zum Kopieren sich 
findet.

Steve van de Grens schrieb:
> W.S. schrieb:
>> Was geht einen Treiber für einen UART eine C++ Klasse an?
>
> Ich sehe keinen Grund, warum man Treiber nicht in C++ programmieren
> soll.

Das ist dir auch völlig freigestellt. Programmiere deine Treiber in 
einer Programmiersprache deiner Wahl. Aber eines bleibt: deinem 
selbstprogrammierten Treiber sollte es egal sein, ob jemand, der ihn 
benutzt und empfangene Zeichen geliefert haben will, die gleiche oder 
eine andere Programmiesprache benutzt. Und laß dir genau deshalb das 
Schicksal von Symbian eine Warnung sein.

W.S.

von EAF (Gast)


Lesenswert?

W.S. schrieb:
> aber vergeßt darüber nicht, daß es hier
> einen TO gibt, der mit seinem Latein am Ende ist, weil ihm keiner seine
> Zeichen einsammelt und nirgendwo etwas passendes zum Kopieren sich
> findet.

Wenn du nicht erkennst, dass serialEvent() nützlich sein kann, dann kann 
man auch nicht von dir erwarten, dass du ein Mundgerecht serviertes 
Beispiel als solches erkennst.

Wenn du irgend was nicht verstehst, wäre es im ersten Schritt für dich 
wichtig zu erkennen dass du irgendwas nicht verstehst.
In einem zweiten Schritt könntest du dann dich kundig machen, z.B. 
Fragen stellen.

Aber:
Offensichtlich urteilst du lieber über Dinge und Menschen, ohne die 
Hintergründe zu erfassen.
Das ist sehr schade, wie ich finde.

von René (ren_r)


Angehängte Dateien:

Lesenswert?

Ich erläutere noch mal mein Problem.
Sende ich ein normalen String (char Array) oder ein String-Objekt mit 
der Funktion Serial1.println() oder Serial1.write() dann reagiert mein 
BLE-Modul nicht. Sende ich jedoch 
Serial1.write((uint8_t*)"AT+UBTLN=\"u-blox NINA René\"", 
strlen("AT+UBTLN=\"u-blox NINA René\"")); funktioniert es.
Beim Lesen bekomme ich mit Serial1.readBytes() oder Serial1.readString() 
nur siehe Bild. Dies soll Conn heißen.

Womöglich bin ich dumm, aber wie sollte ich es anterst machen.
Ach ja verwende ich einen anderen Mikrochip und arbeite ich mit der 
HallUart bib funktioniert es.

Bitte nicht nochmals mir erklären, was ein Ringbuffer, wie ein String 
Objekt aufgebaut ist und wie ein char Array bzw. ein String in C 
aufgebaut ist. Ich habe in reinen C auf einem Linux System schon einige 
Projekte umgesetzt. Also Variablentypen und co kann ich unterscheiden 
und das Verständnis, wie eine serielle Kommunikation aufgebaut ist, habe 
ich auch. C++ ist jedoch etwas neuer für mich (muss ich zugeben). Ich 
möchte keinen 50 Zeilen Code schreiben und im reinen C bleiben, sondern 
möchte die Funktionalitäten, welche mir C++ oder Ardoino-LIB zur 
Verfügung stellt, stellt nutzen. Würde mich Freuen.


LG. René

von M. K. (sylaina)


Lesenswert?

EAF schrieb:
> Wenn du irgend was nicht verstehst, wäre es im ersten Schritt für dich
> wichtig zu erkennen dass du irgendwas nicht verstehst.
> In einem zweiten Schritt könntest du dann dich kundig machen, z.B.
> Fragen stellen.

Die großen drei Gruppen:

1. Dinge, von denen wir wissen, dass wir sie wissen.
2. Dinge, von denen wir wissen, das wir sie nicht wissen.
3. Dinge, von denen wir nicht wissen, dass wir sie nicht wissen.

Du hoffst auf 2. aber allzuoft haben wir hier im Forum 3. aufploppen. ;)

von René (ren_r)


Lesenswert?

Ok Ich gebe es auf.
Ich schreibe es auf basic c noch in eine sauber Funkt und erledigt.

Ich glaubte, ich hätte immer wieder darauf aufmerksam gemacht.
Das ich weiß wie es grundsätzlich funktioniert und ich nicht weiß wie 
ich es am besten in der Atrduino umgebung umsetzen kann. Ohne dies 
hartcoded zu implementieren.

Dud mir leid dann möchte ich mich für die Fragestellung entschuldigen:

Da es mir nicht im klaren ist wie ich es anderst Formulieren sollte. 
Lasse ich es. Möchte euch ja nicht verergern.

Vielen lieben Dank.

Liebe Grüße
Ramsauer René

: Bearbeitet durch User
von J. S. (jojos)


Lesenswert?

fehlt da nicht beim Senden das CR+LF im string? Also "\r\n" ist im HAL 
Beispiel drin, hier nicht.
Und beim Empfang sieht es nach falscher Schnittstellengeschwindigkeit 
aus. Die muss für Serial1 dann auch mit begin(speed) eingestellt werden.

von Stefan F. (stefanus)


Lesenswert?

René schrieb:
> Sende ich ein normalen String (char Array) oder ein String-Objekt mit
> der Funktion Serial1.println() oder Serial1.write() dann reagiert mein
> BLE-Modul nicht.

> aber wie sollte ich es anterst machen.

Zeige erst mal diesen Source code, dann können wir 
Verbesserungsvorschläge dazu machen.

von Axel S. (a-za-z0-9)


Lesenswert?

René schrieb:
> Ich erläutere noch mal mein Problem.

Was heißt hier noch mal? Das ist das allererste Mal.

> Sende ich ein normalen String (char Array) oder ein String-Objekt mit
> der Funktion Serial1.println() oder Serial1.write() dann reagiert mein
> BLE-Modul nicht. Sende ich jedoch
> Serial1.write((uint8_t*)"AT+UBTLN=\"u-blox NINA René\"",
> strlen("AT+UBTLN=\"u-blox NINA René\"")); funktioniert es.

Dann würde ich als erstes einen Logikanalyzer an den UART klemmen und 
gucken, was da gesendet wird. Notfalls den seriellen Port vom PC.
Sonst ist das bloß blindes Stochern im Nebel.

Oh, und zuallererst würde ich das "é" durch ein "e" ersetzen.

von René (ren_r)


Lesenswert?

Nun habe ich es gefunden:

Das Problem ist das die AruinoIDE je nach Board eine eigene Seriell bzw. 
IO Konfiguration mit gibt. Da ich einen zweiten Mikrocontroller am PC 
angesteckt hatte. Hat die IDE zwar oben Ardoino nano angezeigt, jedoch 
die Falsche config in der Ardoino ide geladen. Nun war meine zweite 
serielle Schnittstelle als USB definiert. Fahrzit TI-Mikrochip am PC 
gefällt der IDE nicht. Dies ist natürlich im reinen C Code nicht 
aufgefallen, da ich dort nicht auf die Bibliothek zugegriffen habe.

So dumm hätte ich gleich sehen können.

Ich schaue nochmal nach, wie der geheißen hat. Sende das dann Arduino 
Support. (Muss das Gehäuse meines Reglers zerlegen und gebe euch danach 
die Info welcher yC es war.)

Danke.

Nochmals wäre fast verzweifelt.

Danke an den letzten Kommentar, das hat mich dazu veranlasst nochmals in 
die lib zu schauen.

Sorry, falls ich einige Tippfehler habe. Bin jetzt einfach nur noch 
müde.
Und Sorry das ich manchen nicht geglaubt habe. Kaum ist die Richtige 
Config drinnen, macht er alles richtig(TI abgesteckt und Arduino neu 
angesteckt). Da habe ich mich geirrt und am falschen Platz nach Lösungen 
gesucht.
Die Seriel libarry convertiert den String sowiso von selber in einen 
unsigned char.

Das war wirklich dumm von mir.

: Bearbeitet durch User
von EAF (Gast)


Lesenswert?

Ich verstehe dich immer noch nicht....
Kaum.

Aber schön, dass es jetzt funktioniert.

von René (ren_r)


Lesenswert?

Die ArduinoIDE hat die falsche Arduino.h geladen.
Deswegen war bei Serriel1 standartmäßig USB configuriert.
Dies war wegen einen zweiten yC welcher auf meinen PC angeschlossen war.
Ein TexasInstrument yC.
Ein BUG der IDE.

Ales was ich vorher gefragt habe ist in der Serial.cpp implementiert.

: Bearbeitet durch User

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]
  • [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.

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