Serielle Schnittstelle langsam?!

Hallo,

ich bin Neuling was Arduino angeht.
Ich möchte gerne ein Modellbau Auto steuern mit einem Raspberry Pi und einem Arduino, der die Leistungselektronik bzw. Lenkservo ansteuert.
Diese beiden sollen über die USB serielle Schnittstelle miteinander kontinuierlich kommunizieren, aktuell nur das Senden des Lenkwinkels (60≤x≤120).

Hier mal mein “kleines Arudino Programm”:

//Header

#include <Wire.h>
#include <Servo.h>

//-------------------------------------------------------
// const
//-------------------------------------------------------
const int8_t maxAngle = 120; 
const int8_t minAngle = 60; 
const uint8_t maxPower = 100; 
const uint8_t maxTimer = 255; 
const uint16_t minDistance = 30;

bool safetybit = true;

//-------------------------------------------------------
// Com
//-------------------------------------------------------
String com; 
uint8_t comMotorPWM = 0; 
uint8_t comServoAngle = 0; 
boolean comMotorDir = 0; 
boolean comReset = 0; 
uint8_t timer = maxTimer; //

//-------------------------------------------------------
// Pin Setup
//-------------------------------------------------------
const uint8_t motorDir = 2; 
const uint8_t motorPWM = 3;
const uint8_t servoPWM = 11;
const uint8_t resetPin = 6;
Servo steering;
//-------------------------------------------------------
// Setup
//-------------------------------------------------------
void setup() {
  // put your setup code here, to run once:
  pinMode(motorDir,OUTPUT);
  pinMode(motorPWM,OUTPUT);
  pinMode(resetPin,OUTPUT);
  steering.attach(servoPWM);
  Serial.begin(9600);
}
//-------------------------------------------------------
// Loop
//-------------------------------------------------------
void loop() {
  
 //Serial.println("-----------------------------------");
  if(safetybit == true){ 
 //Serial.println("-----------------------------------");

    if((Serial.available()>0)){ 
      readCommands();
      setServo(comServoAngle);
      setMotor(comMotorPWM,comMotorDir);
      setReset(false);
       
    }else{
      setReset(true);

      }
      
      timer--;
    }else{
      safetybit = false;
      }
    
  //Serial.println("-----------------------------------");
  }
  

//-------------------------------------------------------
// Methoden
//-------------------------------------------------------
void readCommands(){
  if(Serial.available()>0){
      com = Serial.readString();
      
      comMotorPWM= com.substring(0,3).toInt();
     // Serial.println(comMotorPWM);
      comServoAngle= com.substring(3,6).toInt();
      //Serial.println(comServoAngle);
      comMotorDir= (boolean) com.substring(6,7).toInt();
      //Serial.println(comMotorDir);
      comReset= (boolean) com.substring(7,8).toInt();
      //Serial.println(comReset);
    }
  }

  
//-------------------------------------------------------
// setServo angle in Grad-90° ist gerade aus
//-------------------------------------------------------
void setServo(int angle){

  if(angle>maxAngle){
    angle = maxAngle;
    }
   if(angle<minAngle){
    angle = minAngle;
    }
    steering.write(angle);

  }


void setMotor(int power, int dir){
  comMotorDir = dir;
  if(power>maxPower){
    power = maxPower;
  }
  digitalWrite(motorDir,comMotorDir);
  
  analogWrite(motorPWM,power);

  }


void setReset(bool var){

  if(var==false){
    digitalWrite(resetPin,1);
  }else{
    digitalWrite(resetPin,0);
    }
  }

Der Raspberry Pi 3 schreib mit einem Python-Programm die Daten auf die serielle Schnittstelle.
Die Daten, Variable “com”, teilen sich auf in:

1.-3. Zeichen: Motor-PWM 0≤x≤100
4.-6. Zeichen: Winkel 60≤x≤120
7. Zeichen: Motor-Drehrichtung
8. Zeichen: Motor-Reset

Hier der Python Code des Raspberry Pi 3:

Import serial
Import time

Port = serial.Serial("/dev/ttyACM0, baudrate=9600)
Port.close()
Port.open()
Time.sleep(5)

While True:
	Port.write("00012001")
	Time.sleep(1)
	Port.write("00011001")
	Time.sleep(1)
	Port.write("00010001")
	Time.sleep(1)
	Port.write("00009001")
Time.sleep(1)

Jedoch klappt dies nur, wenn der “sleep” Wert größer 2 Sekunden ist, ansonsten reagiert der Lenkservo nicht.
An was kann dies liegen? An der seriellen Schnittstelle? Ziel soll es alle 0,5 Sekunden oder weniger den Lenkwinkel oder Motor-PWM zu beeinflussen.

Ich würde mich sehr über einen Tipp freuen :smiley:

Verzichte auf blockierenden Schrott wie readString():
https://www.arduino.cc/en/Serial/ReadString

Diese Methode hat hier gar nichts zu suchen. Oder lasse die Verzögerung auf der Sende-Seite weg und setzte auf dem Arduino die Timeout Zeit nach unten. Aber besser man gewöhnt sich das gleich ab. Macht auf lange Sicht nur Probleme

Am vernünftigsten ist es immer man schließt die Zeichenkette mit einem Linefeed ab und liest alles bis dahin alles Zeichen für Zeichen ein. Und wertet erst aus wenn das Linefeed angekommen ist. Das kann man nebenbei bequem andere Dinge erledigen.

const int SERIAL_BUFFER_SIZE = 25;
char serialBuffer[SERIAL_BUFFER_SIZE];

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  if (readSerial(Serial) == true)
  {
    unsigned int var1 = atoi(strtok(serialBuffer, ",;"));
    unsigned int var2 = atoi(strtok(NULL, ",;"));
    unsigned int var3 = atoi(strtok(NULL, ",;"));
    unsigned int var4 = atoi(strtok(NULL, ",;"));

    Serial.println(var1);
    Serial.println(var2);
    Serial.println(var3);
    Serial.println(var4);
    Serial.println("----");
  }
}

bool readSerial(Stream& stream)
{
  static byte index;

  while (stream.available())
  {
    char c = stream.read();

    if (c >= 32 && index < SERIAL_BUFFER_SIZE - 1)
    {
      serialBuffer[index++] = c;
    }
    else if (c == '\n' && index > 0)
    {
      serialBuffer[index] = '\0';
      index = 0;
      return true;
    }
  }
  return false;
}

Das liest 4 Zahlen ein die durch ein Komma oder Strichpunkt getrennt sind. Also z.B. “100,200,300,400” und dann ein LF am Ende. Wie lange einzelnen Zahlen ist ist egal, da einfach an den Kommas getrennt wird. Der Puffer muss nur groß genug für alles + 1 sein

Die serielle Geschwindigkeit von 9600 Baud ergibt netto ca 1000 Zeichen pro Sekunde. Bei 8 Zeichen pro Datenpacket sind also ca 250 Datenpackete pro Sekunde möglich.

Verbesserungsvorschläge:

Da die übertragenen Werte alle unter 255 sind brauchst Du für PWM und Lenkeinschlag jeweis nur 1 Byte. Da entfällt jegliches Stringmanipulation und Zeichen-Zahl-Umrechnung. Die Motor-Drehrichtung und reset kannst Du auch noch in die Zahl einbauen:

PWM: 0-100 Vorwärz 0-100%; PWM: 101-201 Rückwerts 0-100% WERT größer als 250 Reset.

Grüße Uwe

Ja, auf die String Behandlung lässt sich auch verzichten

Man kann aber aber die Baudrate nach oben setzen. 250.000 oder 500.000 Baud sind auch möglich. Auch wenn das vom Serial Monitor in der IDE nicht unterstützt wird.

hi,

ich kann mich meinen vorrednern in fast allem nur anschließen.

überlege Dir, was Du wirklichg übertragen willst. 3 bit eines byte können 8 verschiedene werte annehmen, 4 byte 16 usw..

wenn Du Dir jetzt überlegst, wieviele stufen Du pro funktion wirklich brauchst, und zb. 64 genügen, kannst Du alles in einem byte unterbringen:

die ersten zwei bit: funktion: 00: vorwärts 01: rückwärts 10: lenkwinkel 11: pwm

die anderen 6 bit dann werte von 000000 bis 111111, also 0 - 63. beim sonderfall rückwärts voll, also 01111111 einen reset.

falls Dir 64 werte nicht genügen, kein problem, nimm 2 byte (z.b. ein byte funktion, zweites byte wert 0 - 255), und jetzt der unterschied zu serenifly: verwende readBytes(buffer, 2), ABER Du mußt mit Serial.setTimeout die zeit heruntersetzen, die der arduino wartet (sonst wartet er bei jedem loop-durchlauf sinnlos eine sekunde, das ist Deine bremse).

das kannst Du Dir ausrechnen. bei 9600 baud kann er 9600 / 9 byte pro sekunde schicken, ein byte dauert also ca. 1 millisekunde, zwei bytes eben 2. setz' das timeout auf 3 oder 4 millisekunden, dann haste zeit genug.

gruß stefan

EDIT: nicht mitgedacht. mit vorwärts-rückwärts schickst Du die PWM ja als wert mit. 11 könnte also reset sein.

Mir kommt der Python Code verdächtig vor. Ein time.sleep() ist da nur nach einem kompletten Ausgabezyklus (alle Werte) notwendig, nicht nach jedem einzelnen Byte oder Zeichen. Sonst dauert so ein Zyklus viel zu lang, und Dein Arduino Sketch wartet so lange völlig unnötig.

Die Trennung der Werte durch sleep() ist völlig unbrauchbar, stattdessen solltest Du Trennzeichen zwischen den Werten einfügen, die der Arduino erkennen kann, und ein Ende-Zeichen (LF...) am Ende eines Zyklus. Dann kannst Du auf das Ende-Zeichen warten, und erst wenn das angekommen ist, die übertragenen Daten umwandeln und weiterverwenden.

Hallo Ihr,

Vielen Dank!

Der Tipp von Serenifly hat geholfen!

Topic kann geschlossen werden :-D

Schade, daß ich nicht geholfen habe :'( :'(

Hallo,

eine Baudrate von 250.000 kann man im IDE seriellen Monitor schon einstellen. Man muß den Wert nur von Hand eintragen. 500.000 habe ich noch nicht probiert ob das überhaupt möglich ist. Das als Nebeninfo.

hoodrobin: Topic kann geschlossen werden :-D

Wenn Du das selbst erledigst (Betreff des ersten Postings um [erledigt] ergänzen z. B.), bekommst Du von mir ein Fleißbildchen.

Gruß

Gregor